From d10b0cde1139694b088dfc208a7fae0f08ace841 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Sun, 13 Aug 2023 23:11:57 +0300 Subject: [PATCH 1/4] add precompile bind tests --- .../precompilebind/precompile_bind_test.go | 183 ++++++++++++--- .../precompile_contract_template.go | 2 + .../precompile_contract_test_template.go | 211 ++++++++++-------- cmd/precompilegen/main.go | 28 +-- 4 files changed, 277 insertions(+), 147 deletions(-) diff --git a/accounts/abi/bind/precompilebind/precompile_bind_test.go b/accounts/abi/bind/precompilebind/precompile_bind_test.go index 4c98a14e62..eb0688dad8 100644 --- a/accounts/abi/bind/precompilebind/precompile_bind_test.go +++ b/accounts/abi/bind/precompilebind/precompile_bind_test.go @@ -27,83 +27,192 @@ package precompilebind import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" "testing" "github.com/ava-labs/subnet-evm/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" ) -var bindFailedTests = []struct { +var bindTests = []struct { name string contract string - bytecode []string abi []string - errorMsg string - fsigs []map[string]string - libs map[string]string - aliases map[string]string - types []string + imports string + tester string + errMsg string }{ { - `AnonOutputChecker`, ``, - []string{``}, + "AnonOutputChecker", + "", []string{` [ {"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", - nil, - nil, - nil, - nil, }, - { - `AnonOutputsChecker`, ``, - []string{``}, + "AnonOutputsChecker", + "", []string{` [ {"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", - nil, - nil, - nil, - nil, }, - { - `MixedOutputsChecker`, ``, - []string{``}, + "MixedOutputsChecker", + "", []string{` [ {"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", - nil, - nil, - nil, - nil, }, -} + // Test that module is generated correctly + { + `EmptyContract`, + `contract EmptyContract {}`, + []string{`[]`}, + `"github.com/stretchr/testify/require"`, + ` + require.Equal(t, "emptyContractConfig", Module.ConfigKey) + `, + "", + }, + { + `HelloWorld`, + `interface IHelloWorld is IAllowList { + // sayHello returns the stored greeting string + function sayHello() external view returns (string calldata result); -func TestPrecompileBindings(t *testing.T) { - golangBindingsFailure(t) + // setGreeting stores the greeting string + function setGreeting(string calldata response) external; + } + `, + []string{`[{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"readAllowList","outputs":[{"internalType":"uint256","name":"role","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"sayHello","outputs":[{"internalType":"string","name":"result","type":"string"}],"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":"string","name":"response","type":"string"}],"name":"setGreeting","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setNone","outputs":[],"stateMutability":"nonpayable","type":"function"}]`}, + `"github.com/stretchr/testify/require"`, + ` + testGreeting := "test" + method := HelloWorldABI.Methods["setGreeting"] + packedGreeting, err := method.Inputs.Pack(testGreeting) + require.NoError(t, err) + unpackedGreeting, err := UnpackSetGreetingInput(packedGreeting) + require.NoError(t, err) + require.Equal(t, testGreeting, unpackedGreeting) + `, + "", + }, } -func golangBindingsFailure(t *testing.T) { +// Tests that packages generated by the binder can be successfully compiled and +// the requested tester run against it. +func TestPrecompileBind(t *testing.T) { + // Skip the test if no Go command can be found + gocmd := runtime.GOROOT() + "/bin/go" + if !common.FileExist(gocmd) { + t.Skip("go sdk not found for testing") + } + // Create a temporary workspace for the test suite + ws := t.TempDir() + + pkg := filepath.Join(ws, "bindtest") + if err := os.MkdirAll(pkg, 0o700); err != nil { + t.Fatalf("failed to create package: %v", err) + } // Generate the test suite for all the contracts - for i, tt := range bindFailedTests { + for i, tt := range bindTests { 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, "", true) - if err == nil { - t.Fatalf("test %d: no error occurred but was expected", i) + types := []string{tt.name} + + // Generate the binding and create a Go source file in the workspace + bindedFiles, err := PrecompileBind(types, tt.abi, []string{""}, nil, tt.name, bind.LangGo, nil, nil, "contract.abi", true) + if tt.errMsg != "" { + require.ErrorContains(t, err, tt.errMsg) + return + } + if err != nil { + t.Fatalf("test %d: failed to generate binding: %v", i, err) + } + precompilePath := filepath.Join(pkg, tt.name) + if err := os.MkdirAll(precompilePath, 0o700); err != nil { + t.Fatalf("failed to create package: %v", err) + } + // change address to a suitable one for testing + bindedFiles.Module = strings.Replace(bindedFiles.Module, `common.HexToAddress("{ASUITABLEHEXADDRESS}")`, `common.HexToAddress("0x03000000000000000000000000000000000000ff")`, 1) + if err = os.WriteFile(filepath.Join(precompilePath, "module.go"), []byte(bindedFiles.Module), 0o600); err != nil { + t.Fatalf("test %d: failed to write binding: %v", i, err) + } + if err = os.WriteFile(filepath.Join(precompilePath, "config.go"), []byte(bindedFiles.Config), 0o600); err != nil { + t.Fatalf("test %d: failed to write binding: %v", i, err) + } + if err = os.WriteFile(filepath.Join(precompilePath, "contract.go"), []byte(bindedFiles.Contract), 0o600); err != nil { + t.Fatalf("test %d: failed to write binding: %v", i, err) + } + if err = os.WriteFile(filepath.Join(precompilePath, "contract_test.go"), []byte(bindedFiles.ConfigTest), 0o600); err != nil { + t.Fatalf("test %d: failed to write binding: %v", i, err) + } + if err = os.WriteFile(filepath.Join(precompilePath, "contract_test.go"), []byte(bindedFiles.ContractTest), 0o600); err != nil { + t.Fatalf("test %d: failed to write binding: %v", i, err) + } + if err = os.WriteFile(filepath.Join(precompilePath, "contract.abi"), []byte(tt.abi[0]), 0o600); err != nil { + t.Fatalf("test %d: failed to write binding: %v", i, err) + } + + // Generate the test file with the injected test code + code := fmt.Sprintf(` + package %s + + import ( + "testing" + %s + ) + + func Test%s(t *testing.T) { + %s + } + `, tt.name, tt.imports, tt.name, tt.tester) + if err := os.WriteFile(filepath.Join(precompilePath, strings.ToLower(tt.name)+"_test.go"), []byte(code), 0o600); err != nil { + t.Fatalf("test %d: failed to write tests: %v", i, err) } - require.ErrorContains(t, err, tt.errorMsg) }) } + + moder := exec.Command(gocmd, "mod", "init", "bindtest") + moder.Dir = pkg + if out, err := moder.CombinedOutput(); err != nil { + t.Fatalf("failed to convert binding test to modules: %v\n%s", err, out) + } + pwd, _ := os.Getwd() + replacer := exec.Command(gocmd, "mod", "edit", "-x", "-require", "github.com/ava-labs/subnet-evm@v0.0.0", "-replace", "github.com/ava-labs/subnet-evm="+filepath.Join(pwd, "..", "..", "..", "..")) // Repo root + replacer.Dir = pkg + if out, err := replacer.CombinedOutput(); err != nil { + t.Fatalf("failed to replace binding test dependency to current source tree: %v\n%s", err, out) + } + tidier := exec.Command(gocmd, "mod", "tidy", "-compat=1.19") + tidier.Dir = pkg + if out, err := tidier.CombinedOutput(); err != nil { + t.Fatalf("failed to tidy Go module file: %v\n%s", err, out) + } + // Test the entire package and report any failures + cmd := exec.Command(gocmd, "test", "./...", "-v", "-count", "1") + cmd.Dir = pkg + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("failed to run binding test: %v\n%s", err, out) + } } diff --git a/accounts/abi/bind/precompilebind/precompile_contract_template.go b/accounts/abi/bind/precompilebind/precompile_contract_template.go index d4e5695a61..f8f1736f17 100644 --- a/accounts/abi/bind/precompilebind/precompile_contract_template.go +++ b/accounts/abi/bind/precompilebind/precompile_contract_template.go @@ -67,6 +67,8 @@ var ( _ = abi.JSON _ = errors.New _ = big.NewInt + _ = vmerrs.ErrOutOfGas + _ = common.Big0 ) // Singleton StatefulPrecompiledContract and signatures. diff --git a/accounts/abi/bind/precompilebind/precompile_contract_test_template.go b/accounts/abi/bind/precompilebind/precompile_contract_test_template.go index eb4c2f3f56..b9e31e7306 100644 --- a/accounts/abi/bind/precompilebind/precompile_contract_test_template.go +++ b/accounts/abi/bind/precompilebind/precompile_contract_test_template.go @@ -23,106 +23,129 @@ import ( "github.com/stretchr/testify/require" ) - // 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 tests are for general cases like - // allowlist, readOnly behaviour, and gas cost. You should write your own - // tests for specific cases. - var( - 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 { - {{- 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. +var ( + _ = vmerrs.ErrOutOfGas + _ = common.Big0 + _ = require.New +) + +// 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 tests are for general cases like +// allowlist, readOnly behaviour, and gas cost. You should write your own +// tests for specific cases. +var( + 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 { + {{- if len $func.Normalized.Inputs | lt 1}} // CUSTOM CODE STARTS HERE - ExpectedRes: []byte{}, + // 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}} - SuppliedGas: {{$func.Normalized.Name}}GasCost, - ReadOnly: false, - ExpectedErr: {{if $fail}} ErrCannot{{$func.Normalized.Name}}.Error() {{- else}} "" {{- 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: func() []byte{ + {{- if len $func.Normalized.Outputs | eq 0}} + // this function does not return an output, leave this one as is + packedOutput := []byte{} + {{- else}} + {{- if len $func.Normalized.Outputs | lt 1}} + var output {{capitalise $func.Normalized.Name}}Output // CUSTOM CODE FOR AN OUTPUT + {{- else }} + {{$output := index $func.Normalized.Outputs 0}} + var output {{bindtype $output.Type $structs}} // CUSTOM CODE FOR AN OUTPUT + {{- end}} + packedOutput, err := Pack{{$func.Normalized.Name}}Output(output) + if err != nil { + panic(err) + } + {{- end}} + return packedOutput + }(), {{- 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(), + 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 }, - {{- 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(), + 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 }, - {{- end}} - } - ) + SuppliedGas: {{$func.Normalized.Name}}GasCost - 1, + ReadOnly: false, + ExpectedErr: vmerrs.ErrOutOfGas.Error(), + }, + {{- end}} + } +) // Test{{.Contract.Type}}Run tests the Run function of the precompile contract. func Test{{.Contract.Type}}Run(t *testing.T) { diff --git a/cmd/precompilegen/main.go b/cmd/precompilegen/main.go index 29e5bf8135..f51008b1c7 100644 --- a/cmd/precompilegen/main.go +++ b/cmd/precompilegen/main.go @@ -43,10 +43,8 @@ import ( "github.com/urfave/cli/v2" ) -var ( - //go:embed template-readme.md - readme string -) +//go:embed template-readme.md +var readme string var ( // Flags needed by abigen @@ -204,18 +202,16 @@ func precompilegen(c *cli.Context) error { } // Write the test code to the output folder - if generateTests { - configTestCode := bindedFiles.ConfigTest - configTestCodeOut := filepath.Join(outFlagStr, "config_test.go") - if err := os.WriteFile(configTestCodeOut, []byte(configTestCode), 0o600); err != nil { - utils.Fatalf("Failed to write generated test code: %v", err) - } - - contractTestCode := bindedFiles.ContractTest - contractTestCodeOut := filepath.Join(outFlagStr, "contract_test.go") - if err := os.WriteFile(contractTestCodeOut, []byte(contractTestCode), 0o600); err != nil { - utils.Fatalf("Failed to write generated test code: %v", err) - } + configTestCode := bindedFiles.ConfigTest + configTestCodeOut := filepath.Join(outFlagStr, "config_test.go") + if err := os.WriteFile(configTestCodeOut, []byte(configTestCode), 0o600); err != nil { + utils.Fatalf("Failed to write generated test code: %v", err) + } + + contractTestCode := bindedFiles.ContractTest + contractTestCodeOut := filepath.Join(outFlagStr, "contract_test.go") + if err := os.WriteFile(contractTestCodeOut, []byte(contractTestCode), 0o600); err != nil { + utils.Fatalf("Failed to write generated test code: %v", err) } fmt.Println("Precompile files generated successfully at: ", outFlagStr) From 7a03995eb766aefd97b543ad8547a8dd497a76af Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Mon, 14 Aug 2023 13:45:35 +0300 Subject: [PATCH 2/4] bump avalanchego version --- README.md | 2 +- go.mod | 6 +++--- go.sum | 12 ++++++------ scripts/versions.sh | 4 +--- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 62da6ce1dc..c1bae98b91 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ The Subnet EVM runs in a separate process from the main AvalancheGo process and [v0.5.1] AvalancheGo@v1.10.1-v1.10.4 (Protocol Version: 26) [v0.5.2] AvalancheGo@v1.10.1-v1.10.4 (Protocol Version: 26) [v0.5.3] AvalancheGo@v1.10.5-v1.10.6 (Protocol Version: 27) -[v0.5.4] AvalancheGo@v1.10.5-v1.10.6 (Protocol Version: 27) +[v0.5.4] AvalancheGo@v1.10.5-v1.10.8 (Protocol Version: 27) ``` ## API diff --git a/go.mod b/go.mod index 9036dd0935..aa386e491a 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.19 require ( github.com/VictoriaMetrics/fastcache v1.10.0 github.com/ava-labs/avalanche-network-runner v1.7.1 - github.com/ava-labs/avalanchego v1.10.6-rc.4 + github.com/ava-labs/avalanchego v1.10.8 github.com/cespare/cp v0.1.0 github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 github.com/davecgh/go-spew v1.1.1 @@ -53,7 +53,7 @@ require ( github.com/DataDog/zstd v1.5.2 // indirect github.com/Microsoft/go-winio v0.5.2 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect - github.com/ava-labs/coreth v0.12.5-rc.0 // indirect + github.com/ava-labs/coreth v0.12.5-rc.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.7.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect @@ -124,7 +124,7 @@ require ( github.com/spf13/afero v1.8.2 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.3.0 // indirect - github.com/supranational/blst v0.3.11-0.20230406105308-e9dfc5ee724b // indirect + github.com/supranational/blst v0.3.11 // indirect github.com/tklauser/go-sysconf v0.3.5 // indirect github.com/tklauser/numcpus v0.2.2 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect diff --git a/go.sum b/go.sum index 403a519063..b0494727c9 100644 --- a/go.sum +++ b/go.sum @@ -61,10 +61,10 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/ava-labs/avalanche-network-runner v1.7.1 h1:XRC2NcchESSxSlJEgb47lAkTG5eF1t2sMs8CoJKeAuE= github.com/ava-labs/avalanche-network-runner v1.7.1/go.mod h1:q/2ws64daRXXsiq08bVA1iESRY1CNOQQQDKPeMauZrc= -github.com/ava-labs/avalanchego v1.10.6-rc.4 h1:MRQ0wGoLrvL2iYzGmhfee/j3uCY6epXyzpUMTa3Y9ww= -github.com/ava-labs/avalanchego v1.10.6-rc.4/go.mod h1:e8LdGy0xM+QejpMoEK6wOFu2O5HMlAHPJiBPjhoTG78= -github.com/ava-labs/coreth v0.12.5-rc.0 h1:9eqJS30tIDtBWzhQ2cXMe1Ul8AUabI89CVhdXUWU4E8= -github.com/ava-labs/coreth v0.12.5-rc.0/go.mod h1:Wwctj2j/x4FVhePD5jlVbQ2TTOetihP6iJlQ9j8NLhw= +github.com/ava-labs/avalanchego v1.10.8 h1:fUudA4J37y8wyNG3iiX0kpoZXunsWpCgvsGDgIsi0NY= +github.com/ava-labs/avalanchego v1.10.8/go.mod h1:2zuce+beHe25wQ5RlrEeDpa+SqY/sjEOjDky+Q1NxfU= +github.com/ava-labs/coreth v0.12.5-rc.1 h1:rLJ6mT/44jgc+vPpKaspGpxDIdQGLkSK6wDSmKgYPxY= +github.com/ava-labs/coreth v0.12.5-rc.1/go.mod h1:K7Xm2jqx90wxKZXfLvkLEL+zlM5843gGq9XkqVDwKds= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -589,8 +589,8 @@ github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gt github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI= github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs= -github.com/supranational/blst v0.3.11-0.20230406105308-e9dfc5ee724b h1:u49mjRnygnB34h8OKbnNJFVUtWSKIKb1KukdV8bILUM= -github.com/supranational/blst v0.3.11-0.20230406105308-e9dfc5ee724b/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= +github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a h1:1ur3QoCqvE5fl+nylMaIr9PVV1w343YRDtsy+Rwu7XI= github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= diff --git a/scripts/versions.sh b/scripts/versions.sh index be00d9e554..8e167a6bb3 100644 --- a/scripts/versions.sh +++ b/scripts/versions.sh @@ -1,9 +1,7 @@ #!/usr/bin/env bash -# Set up the versions to be used - populate ENV variables only if they are not already populated -SUBNET_EVM_VERSION=${SUBNET_EVM_VERSION:-'v0.5.4'} # Don't export them as they're used in the context of other calls -AVALANCHE_VERSION=${AVALANCHE_VERSION:-'v1.10.6-rc.4'} +AVALANCHE_VERSION=${AVALANCHE_VERSION:-'v1.10.8'} AVALANCHEGO_VERSION=${AVALANCHEGO_VERSION:-$AVALANCHE_VERSION} GINKGO_VERSION=${GINKGO_VERSION:-'v2.2.0'} From f49b607c2b862ce2c0fa8f09855f194226169745 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 15 Aug 2023 00:31:12 +0300 Subject: [PATCH 3/4] add more tests --- accounts/abi/argument.go | 8 + accounts/abi/bind/bind.go | 44 ++- .../bind/precompilebind/precompile_bind.go | 21 +- .../precompilebind/precompile_bind_test.go | 349 +++++++++++++++++- .../precompile_contract_template.go | 22 +- .../precompile_contract_test_template.go | 32 ++ cmd/precompilegen/main.go | 6 +- 7 files changed, 459 insertions(+), 23 deletions(-) diff --git a/accounts/abi/argument.go b/accounts/abi/argument.go index 6245ca62ea..e2be3a4bfe 100644 --- a/accounts/abi/argument.go +++ b/accounts/abi/argument.go @@ -279,5 +279,13 @@ func ToCamelCase(input string) string { parts[i] = strings.ToUpper(s[:1]) + s[1:] } } + // preserve leading underscore + if input[0] == '_' { + parts = append([]string{"_"}, parts...) + } + // preserve trailing underscore + if input[len(input)-1] == '_' { + parts = append(parts, "_") + } return strings.Join(parts, "") } diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index f40dcbea04..c45ad34f42 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -53,7 +53,7 @@ const ( LangGo Lang = iota ) -func isKeyWord(arg string) bool { +func IsKeyWord(arg string) bool { switch arg { case "break": case "case": @@ -165,7 +165,7 @@ func BindHelper(types []string, abis []string, bytecodes []string, fsigs []map[s normalized.Inputs = make([]abi.Argument, len(original.Inputs)) copy(normalized.Inputs, original.Inputs) for j, input := range normalized.Inputs { - if input.Name == "" || isKeyWord(input.Name) { + if input.Name == "" || IsKeyWord(input.Name) { normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j) } if hasStruct(input.Type) { @@ -209,7 +209,7 @@ func BindHelper(types []string, abis []string, bytecodes []string, fsigs []map[s normalized.Inputs = make([]abi.Argument, len(original.Inputs)) copy(normalized.Inputs, original.Inputs) for j, input := range normalized.Inputs { - if input.Name == "" || isKeyWord(input.Name) { + if input.Name == "" || IsKeyWord(input.Name) { normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j) } // Event is a bit special, we need to define event struct in binding, @@ -298,6 +298,7 @@ func BindHelper(types []string, abis []string, bytecodes []string, fsigs []map[s funcs := map[string]interface{}{ "bindtype": bindType[lang], + "bindtypenew": bindTypeNew[lang], "bindtopictype": bindTopicType[lang], "namedtype": namedType[lang], "capitalise": capitalise, @@ -328,6 +329,10 @@ var bindType = map[Lang]func(kind abi.Type, structs map[string]*TmplStruct) stri LangGo: bindTypeGo, } +var bindTypeNew = map[Lang]func(kind abi.Type, structs map[string]*TmplStruct) string{ + LangGo: bindTypeNewGo, +} + // bindBasicTypeGo converts basic solidity types(except array, slice and tuple) to Go ones. func bindBasicTypeGo(kind abi.Type) string { switch kind.T { @@ -352,6 +357,39 @@ func bindBasicTypeGo(kind abi.Type) string { } } +// bindNewTypeNewGo converts new types to Go ones. +func bindTypeNewGo(kind abi.Type, structs map[string]*TmplStruct) string { + switch kind.T { + case abi.TupleTy: + return structs[kind.TupleRawName+kind.String()].Name + "{}" + case abi.ArrayTy: + return fmt.Sprintf("[%d]", kind.Size) + bindTypeGo(*kind.Elem, structs) + "{}" + case abi.SliceTy: + return "nil" + case abi.AddressTy: + return "common.Address{}" + case abi.IntTy, abi.UintTy: + parts := regexp.MustCompile(`(u)?int([0-9]*)`).FindStringSubmatch(kind.String()) + switch parts[2] { + case "8", "16", "32", "64": + return "0" + } + return "new(big.Int)" + case abi.FixedBytesTy: + return fmt.Sprintf("[%d]byte", kind.Size) + "{}" + case abi.BytesTy: + return "[]byte{}" + case abi.FunctionTy: + return "[24]byte{}" + case abi.BoolTy: + return "false" + case abi.StringTy: + return `""` + default: + return "nil" + } +} + // bindTypeGo converts solidity types to Go ones. Since there is no clear mapping // from all Solidity types to Go ones (e.g. uint17), those that cannot be exactly // mapped will use an upscaled type (e.g. BigDecimal). diff --git a/accounts/abi/bind/precompilebind/precompile_bind.go b/accounts/abi/bind/precompilebind/precompile_bind.go index 483470a295..6b2d937a5a 100644 --- a/accounts/abi/bind/precompilebind/precompile_bind.go +++ b/accounts/abi/bind/precompilebind/precompile_bind.go @@ -33,7 +33,9 @@ package precompilebind import ( "errors" "fmt" + "strings" + "github.com/ava-labs/subnet-evm/accounts/abi" "github.com/ava-labs/subnet-evm/accounts/abi/bind" ) @@ -55,13 +57,30 @@ type BindedFiles struct { } // 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, generateTests bool) (BindedFiles, error) { +func PrecompileBind(types []string, abiData 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) + // check abi first + evmABI, err := abi.JSON(strings.NewReader(abiData)) + if err != nil { + return BindedFiles{}, err + } + if len(evmABI.Methods) == 0 { + return BindedFiles{}, errors.New("no ABI methods found") + } + for _, original := range evmABI.Methods { + for _, input := range original.Inputs { + if bind.IsKeyWord(input.Name) { + return BindedFiles{}, fmt.Errorf("input name %s is a keyword", input.Name) + } + } + } + + abis := []string{abiData} configBind, err := bind.BindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, configHook) if err != nil { diff --git a/accounts/abi/bind/precompilebind/precompile_bind_test.go b/accounts/abi/bind/precompilebind/precompile_bind_test.go index eb0688dad8..3286cf8747 100644 --- a/accounts/abi/bind/precompilebind/precompile_bind_test.go +++ b/accounts/abi/bind/precompilebind/precompile_bind_test.go @@ -43,7 +43,7 @@ import ( var bindTests = []struct { name string contract string - abi []string + abi string imports string tester string errMsg string @@ -51,11 +51,11 @@ var bindTests = []struct { { "AnonOutputChecker", "", - []string{` + ` [ {"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", @@ -63,11 +63,11 @@ var bindTests = []struct { { "AnonOutputsChecker", "", - []string{` + ` [ {"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", @@ -75,11 +75,11 @@ var bindTests = []struct { { "MixedOutputsChecker", "", - []string{` + ` [ {"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", @@ -88,13 +88,309 @@ var bindTests = []struct { { `EmptyContract`, `contract EmptyContract {}`, - []string{`[]`}, - `"github.com/stretchr/testify/require"`, + "[]", + "", + "", + "no ABI methods found", + }, + // Test that named and anonymous inputs are handled correctly + { + `InputChecker`, ``, + ` + [ + {"type":"function","name":"noInput","constant":true,"inputs":[],"outputs":[]}, + {"type":"function","name":"namedInput","constant":true,"inputs":[{"name":"str","type":"string"}],"outputs":[]}, + {"type":"function","name":"namedInputs","constant":true,"inputs":[{"name":"str1","type":"string"},{"name":"str2","type":"string"}],"outputs":[]} + ] + `, + ` + "github.com/stretchr/testify/require" + `, ` - require.Equal(t, "emptyContractConfig", Module.ConfigKey) + testInput := "test" + packedInput, err := PackNamedInput(testInput) + require.NoError(t, err) + // remove the first 4 bytes of the packed input + packedInput = packedInput[4:] + unpackedInput, err := UnpackNamedInputInput(packedInput) + require.NoError(t, err) + require.Equal(t, testInput, unpackedInput) + + testInputStruct := NamedInputsInput{ + Str1: "test1", + Str2: "test2", + } + packedInputStruct, err := PackNamedInputs(testInputStruct) + require.NoError(t, err) + // remove the first 4 bytes of the packed input + packedInputStruct = packedInputStruct[4:] + unpackedInputStruct, err := UnpackNamedInputsInput(packedInputStruct) + require.NoError(t, err) + require.Equal(t, unpackedInputStruct, testInputStruct) + `, + "", + }, + // Test that named and anonymous outputs are handled correctly + { + `OutputChecker`, ``, + ` + [ + {"type":"function","name":"noOutput","constant":true,"inputs":[],"outputs":[]}, + {"type":"function","name":"namedOutput","constant":true,"inputs":[],"outputs":[{"name":"str","type":"string"}]}, + {"type":"function","name":"namedOutputs","constant":true,"inputs":[],"outputs":[{"name":"str1","type":"string"},{"name":"str2","type":"string"}]} + ] + `, + ` + "github.com/stretchr/testify/require" + `, + ` + testOutput := "test" + packedOutput, err := PackNamedOutputOutput(testOutput) + require.NoError(t, err) + unpackedOutput, err := UnpackNamedOutputOutput(packedOutput) + require.NoError(t, err) + require.Equal(t, testOutput, unpackedOutput) + + testNamedOutputs := NamedOutputsOutput{ + Str1: "test1", + Str2: "test2", + } + packedNamedOutputs, err := PackNamedOutputsOutput(testNamedOutputs) + require.NoError(t, err) + unpackedNamedOutputs, err := UnpackNamedOutputsOutput(packedNamedOutputs) + require.NoError(t, err) + require.Equal(t, testNamedOutputs, unpackedNamedOutputs) + `, + "", + }, + { + `Tupler`, + ` + interface Tupler { + function tuple() constant returns (string a, int b, bytes32 c); + } + `, + `[{"constant":true,"inputs":[],"name":"tuple","outputs":[{"name":"a","type":"string"},{"name":"b","type":"int256"},{"name":"c","type":"bytes32"}],"type":"function"}]`, + ` + "math/big" + "github.com/stretchr/testify/require" + `, + ` + testOutput := TupleOutput{"Hi", big.NewInt(123), [32]byte{1, 2, 3}} + packedOutput, err := PackTupleOutput(testOutput) + require.NoError(t, err) + unpackedOutput, err := UnpackTupleOutput(packedOutput) + require.NoError(t, err) + require.Equal(t, testOutput, unpackedOutput) + `, + "", + }, + { + `Slicer`, + ` + interface Slicer { + function echoAddresses(address[] input) constant returns (address[] output); + function echoInts(int[] input) constant returns (int[] output); + function echoFancyInts(uint8[23] input) constant returns (uint8[23] output); + function echoBools(bool[] input) constant returns (bool[] output); + } + `, + `[{"constant":true,"inputs":[{"name":"input","type":"address[]"}],"name":"echoAddresses","outputs":[{"name":"output","type":"address[]"}],"type":"function"},{"constant":true,"inputs":[{"name":"input","type":"uint8[23]"}],"name":"echoFancyInts","outputs":[{"name":"output","type":"uint8[23]"}],"type":"function"},{"constant":true,"inputs":[{"name":"input","type":"int256[]"}],"name":"echoInts","outputs":[{"name":"output","type":"int256[]"}],"type":"function"},{"constant":true,"inputs":[{"name":"input","type":"bool[]"}],"name":"echoBools","outputs":[{"name":"output","type":"bool[]"}],"type":"function"}]`, + ` + "math/big" + "github.com/stretchr/testify/require" + "github.com/ethereum/go-ethereum/common" + `, + ` + testArgs := []common.Address{common.HexToAddress("1"), common.HexToAddress("2"), common.HexToAddress("3")} + packedOutput, err := PackEchoAddressesOutput(testArgs) + require.NoError(t, err) + unpackedOutput, err := UnpackEchoAddressesOutput(packedOutput) + require.NoError(t, err) + require.Equal(t, testArgs, unpackedOutput) + packedInput, err := PackEchoAddresses(testArgs) + // remove the first 4 bytes of the packed input + packedInput = packedInput[4:] + require.NoError(t, err) + unpackedInput, err := UnpackEchoAddressesInput(packedInput) + require.NoError(t, err) + require.Equal(t, testArgs, unpackedInput) + + testArgs2 := []*big.Int{common.Big1, common.Big2, common.Big3} + packedOutput2, err := PackEchoIntsOutput(testArgs2) + require.NoError(t, err) + unpackedOutput2, err := UnpackEchoIntsOutput(packedOutput2) + require.NoError(t, err) + require.Equal(t, testArgs2, unpackedOutput2) + packedInput2, err := PackEchoInts(testArgs2) + // remove the first 4 bytes of the packed input + packedInput2 = packedInput2[4:] + require.NoError(t, err) + unpackedInput2, err := UnpackEchoIntsInput(packedInput2) + require.NoError(t, err) + require.Equal(t, testArgs2, unpackedInput2) + + testArgs3 := [23]uint8{1, 2, 3} + packedOutput3, err := PackEchoFancyIntsOutput(testArgs3) + require.NoError(t, err) + unpackedOutput3, err := UnpackEchoFancyIntsOutput(packedOutput3) + require.NoError(t, err) + require.Equal(t, testArgs3, unpackedOutput3) + packedInput3, err := PackEchoFancyInts(testArgs3) + // remove the first 4 bytes of the packed input + packedInput3 = packedInput3[4:] + require.NoError(t, err) + unpackedInput3, err := UnpackEchoFancyIntsInput(packedInput3) + require.NoError(t, err) + require.Equal(t, testArgs3, unpackedInput3) + + testArgs4 := []bool{true, false, true} + packedOutput4, err := PackEchoBoolsOutput(testArgs4) + require.NoError(t, err) + unpackedOutput4, err := UnpackEchoBoolsOutput(packedOutput4) + require.NoError(t, err) + require.Equal(t, testArgs4, unpackedOutput4) + packedInput4, err := PackEchoBools(testArgs4) + // remove the first 4 bytes of the packed input + packedInput4 = packedInput4[4:] + require.NoError(t, err) + unpackedInput4, err := UnpackEchoBoolsInput(packedInput4) + require.NoError(t, err) + require.Equal(t, testArgs4, unpackedInput4) `, "", }, + { + `Fallback`, + ` + interface Fallback { + fallback() external payable; + + receive() external payable; + function testFunction(uint t) external; + } + `, + `[{"stateMutability":"payable","type":"fallback"},{"inputs":[{"internalType":"uint256","name":"t","type":"uint256"}],"name":"testFunction","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]`, + ` + "github.com/stretchr/testify/require" + "math/big" + `, + ` + packedInput, err := PackTestFunction(big.NewInt(5)) + require.NoError(t, err) + // remove the first 4 bytes of the packed input + packedInput = packedInput[4:] + unpackedInput, err := UnpackTestFunctionInput(packedInput) + require.NoError(t, err) + require.Equal(t, big.NewInt(5), unpackedInput) + `, + "", + }, + { + `Structs`, + ` + interface Struct { + struct A { + bytes32 B; + } + function F() external view returns (A[] memory a, uint256[] memory c, bool[] memory d); + function G() external view returns (A[] memory a); + } + `, + `[{"inputs":[],"name":"F","outputs":[{"components":[{"internalType":"bytes32","name":"B","type":"bytes32"}],"internalType":"struct Structs.A[]","name":"a","type":"tuple[]"},{"internalType":"uint256[]","name":"c","type":"uint256[]"},{"internalType":"bool[]","name":"d","type":"bool[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"G","outputs":[{"components":[{"internalType":"bytes32","name":"B","type":"bytes32"}],"internalType":"struct Structs.A[]","name":"a","type":"tuple[]"}],"stateMutability":"view","type":"function"}]`, + ` + "github.com/stretchr/testify/require" + "math/big" + `, + ` + testOutput := FOutput{ + A: []StructsA{ + { + B: [32]byte{1}, + }, + }, + C: []*big.Int{big.NewInt(2)}, + D: []bool{true,false}, + } + packedOutput, err := PackFOutput(testOutput) + require.NoError(t, err) + unpackedInput, err := UnpackFOutput(packedOutput) + require.NoError(t, err) + require.Equal(t, testOutput, unpackedInput) + `, + "", + }, + { + `Underscorer`, + ` + interface Underscorer { + function UnderscoredOutput() external returns (int _int, string _string); + function LowerLowerCollision() external returns (int _res, int res, int res_); + function LowerUpperCollision() external returns (int _res, int Res); + function UpperLowerCollision() external returns (int _Res, int res); + function UpperUpperCollision() external returns (int _Res, int Res); + function _under_scored_func() external returns (int _int); + } + `, + `[{"inputs":[],"name":"LowerLowerCollision","outputs":[{"internalType":"int256","name":"_res","type":"int256"},{"internalType":"int256","name":"res","type":"int256"},{"internalType":"int256","name":"res_","type":"int256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"LowerUpperCollision","outputs":[{"internalType":"int256","name":"_res","type":"int256"},{"internalType":"int256","name":"Res","type":"int256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"UnderscoredOutput","outputs":[{"internalType":"int256","name":"_int","type":"int256"},{"internalType":"string","name":"_string","type":"string"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"UpperLowerCollision","outputs":[{"internalType":"int256","name":"_Res","type":"int256"},{"internalType":"int256","name":"res","type":"int256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"UpperUpperCollision","outputs":[{"internalType":"int256","name":"_Res","type":"int256"},{"internalType":"int256","name":"Res","type":"int256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"_under_scored_func","outputs":[{"internalType":"int256","name":"_int","type":"int256"}],"stateMutability":"nonpayable","type":"function"}]`, + ``, + ``, + "", + }, + { + `DeeplyNestedArray`, + ` + interface DeeplyNestedArray { + function storeDeepUintArray(uint64[3][4][5] arr) external public; + function retrieveDeepArray() public external view returns (uint64[3][4][5] arr); + } + `, + `[{"inputs":[],"name":"retrieveDeepArray","outputs":[{"internalType":"uint64[3][4][5]","name":"arr","type":"uint64[3][4][5]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint64[3][4][5]","name":"arr","type":"uint64[3][4][5]"}],"name":"storeDeepUintArray","outputs":[],"stateMutability":"nonpayable","type":"function"}]`, ` + "github.com/stretchr/testify/require" + `, + ` + testArr := [5][4][3]uint64{ + { + {1, 2, 3}, + {4, 5, 6}, + {7, 8, 9}, + {10, 11, 12}, + }, + { + {13, 14, 15}, + {16, 17, 18}, + {19, 20, 21}, + {22, 23, 24}, + }, + } + packedInput, err := PackStoreDeepUintArray(testArr) + require.NoError(t, err) + // remove the first 4 bytes of the packed input + packedInput = packedInput[4:] + unpackedInput, err := UnpackStoreDeepUintArrayInput(packedInput) + require.NoError(t, err) + require.Equal(t, testArr, unpackedInput) + + packedOutput, err := PackRetrieveDeepArrayOutput(testArr) + require.NoError(t, err) + unpackedOutput, err := UnpackRetrieveDeepArrayOutput(packedOutput) + require.NoError(t, err) + require.Equal(t, testArr, unpackedOutput) + `, + "", + }, + { + "RangeKeyword", + ` + interface keywordcontract { + function functionWithKeywordParameter(uint8 func, uint8 range) external pure; + } + `, + `[{"inputs":[{"internalType":"uint8","name":"func","type":"uint8"},{"internalType":"uint8","name":"range","type":"uint8"}],"name":"functionWithKeywordParameter","outputs":[],"stateMutability":"pure","type":"function"}]`, + "", + "", + "input name func is a keyword", + }, { `HelloWorld`, `interface IHelloWorld is IAllowList { @@ -105,13 +401,38 @@ var bindTests = []struct { function setGreeting(string calldata response) external; } `, - []string{`[{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"readAllowList","outputs":[{"internalType":"uint256","name":"role","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"sayHello","outputs":[{"internalType":"string","name":"result","type":"string"}],"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":"string","name":"response","type":"string"}],"name":"setGreeting","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":"readAllowList","outputs":[{"internalType":"uint256","name":"role","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"sayHello","outputs":[{"internalType":"string","name":"result","type":"string"}],"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":"string","name":"response","type":"string"}],"name":"setGreeting","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setNone","outputs":[],"stateMutability":"nonpayable","type":"function"}]`, + `"github.com/stretchr/testify/require"`, + ` + testGreeting := "test" + packedGreeting, err := PackSetGreeting(testGreeting) + require.NoError(t, err) + // remove the first 4 bytes of the packed greeting + packedGreeting = packedGreeting[4:] + unpackedGreeting, err := UnpackSetGreetingInput(packedGreeting) + require.NoError(t, err) + require.Equal(t, testGreeting, unpackedGreeting) + `, + "", + }, + { + `HelloWorldNoAL`, + `interface IHelloWorld{ + // sayHello returns the stored greeting string + function sayHello() external view returns (string calldata result); + + // setGreeting stores the greeting string + function setGreeting(string calldata response) external; + } + `, + `[{"inputs":[],"name":"sayHello","outputs":[{"internalType":"string","name":"result","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"response","type":"string"}],"name":"setGreeting","outputs":[],"stateMutability":"nonpayable","type":"function"}]`, `"github.com/stretchr/testify/require"`, ` testGreeting := "test" - method := HelloWorldABI.Methods["setGreeting"] - packedGreeting, err := method.Inputs.Pack(testGreeting) + packedGreeting, err := PackSetGreeting(testGreeting) require.NoError(t, err) + // remove the first 4 bytes of the packed greeting + packedGreeting = packedGreeting[4:] unpackedGreeting, err := UnpackSetGreetingInput(packedGreeting) require.NoError(t, err) require.Equal(t, testGreeting, unpackedGreeting) @@ -170,7 +491,7 @@ func TestPrecompileBind(t *testing.T) { if err = os.WriteFile(filepath.Join(precompilePath, "contract_test.go"), []byte(bindedFiles.ContractTest), 0o600); err != nil { t.Fatalf("test %d: failed to write binding: %v", i, err) } - if err = os.WriteFile(filepath.Join(precompilePath, "contract.abi"), []byte(tt.abi[0]), 0o600); err != nil { + if err = os.WriteFile(filepath.Join(precompilePath, "contract.abi"), []byte(tt.abi), 0o600); err != nil { t.Fatalf("test %d: failed to write binding: %v", i, err) } diff --git a/accounts/abi/bind/precompilebind/precompile_contract_template.go b/accounts/abi/bind/precompilebind/precompile_contract_template.go index f8f1736f17..cdb2da1871 100644 --- a/accounts/abi/bind/precompilebind/precompile_contract_template.go +++ b/accounts/abi/bind/precompilebind/precompile_contract_template.go @@ -161,7 +161,7 @@ func Pack{{.Normalized.Name}}(inputStruct {{capitalise .Normalized.Name}}Input) func Unpack{{capitalise .Normalized.Name}}Input(input []byte)({{$bindedType}}, error) { res, err := {{$contract.Type}}ABI.UnpackInput("{{$method.Original.Name}}", input) if err != nil { - return *new({{$bindedType}}), err + return {{bindtypenew $input.Type $structs}}, err } unpacked := *abi.ConvertType(res[0], new({{$bindedType}})).(*{{$bindedType}}) return unpacked, nil @@ -192,6 +192,15 @@ func Pack{{capitalise .Normalized.Name}}Output (outputStruct {{capitalise .Norma ) } +// 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}} @@ -201,6 +210,17 @@ func Pack{{capitalise .Normalized.Name}}Output (outputStruct {{capitalise .Norma 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) { diff --git a/accounts/abi/bind/precompilebind/precompile_contract_test_template.go b/accounts/abi/bind/precompilebind/precompile_contract_test_template.go index b9e31e7306..4592080544 100644 --- a/accounts/abi/bind/precompilebind/precompile_contract_test_template.go +++ b/accounts/abi/bind/precompilebind/precompile_contract_test_template.go @@ -12,6 +12,7 @@ package {{.Package}} import ( "testing" + "math/big" "github.com/ava-labs/subnet-evm/core/state" {{- if .Contract.AllowList}} @@ -25,6 +26,7 @@ import ( var ( _ = vmerrs.ErrOutOfGas + _ = big.NewInt _ = common.Big0 _ = require.New ) @@ -58,6 +60,7 @@ var( // CUSTOM CODE STARTS HERE // set test input to a value here var testInput {{bindtype $input.Type $structs}} + testInput = {{bindtypenew $input.Type $structs}} input, err := Pack{{$func.Normalized.Name}}(testInput) {{- else}} input, err := Pack{{$func.Normalized.Name}}() @@ -78,6 +81,7 @@ var( {{- else }} {{$output := index $func.Normalized.Outputs 0}} var output {{bindtype $output.Type $structs}} // CUSTOM CODE FOR AN OUTPUT + output = {{bindtypenew $output.Type $structs}} // CUSTOM CODE FOR AN OUTPUT {{- end}} packedOutput, err := Pack{{$func.Normalized.Name}}Output(output) if err != nil { @@ -107,6 +111,7 @@ var( // CUSTOM CODE STARTS HERE // set test input to a value here var testInput {{bindtype $input.Type $structs}} + testInput = {{bindtypenew $input.Type $structs}} input, err := Pack{{$func.Normalized.Name}}(testInput) {{- else}} input, err := Pack{{$func.Normalized.Name}}() @@ -132,6 +137,7 @@ var( // CUSTOM CODE STARTS HERE // set test input to a value here var testInput {{bindtype $input.Type $structs}} + testInput = {{bindtypenew $input.Type $structs}} input, err := Pack{{$func.Normalized.Name}}(testInput) {{- else}} input, err := Pack{{$func.Normalized.Name}}() @@ -144,6 +150,32 @@ var( ExpectedErr: vmerrs.ErrOutOfGas.Error(), }, {{- end}} + {{- if .Contract.Fallback}} + "insufficient gas for fallback should fail": { + Caller: common.Address{1}, + Input: []byte{}, + SuppliedGas: {{.Contract.Type}}FallbackGasCost - 1, + ReadOnly: false, + ExpectedErr: vmerrs.ErrOutOfGas.Error(), + }, + "readOnly fallback should fail": { + Caller: common.Address{1}, + Input: []byte{}, + SuppliedGas: {{.Contract.Type}}FallbackGasCost, + ReadOnly: true, + ExpectedErr: vmerrs.ErrWriteProtection.Error(), + }, + "fallback should succeed": { + Caller: common.Address{1}, + Input: []byte{}, + SuppliedGas: {{.Contract.Type}}FallbackGasCost, + ReadOnly: false, + ExpectedErr: "", + // CUSTOM CODE STARTS HERE + // set expected output here + ExpectedRes: []byte{}, + }, + {{- end}} } ) diff --git a/cmd/precompilegen/main.go b/cmd/precompilegen/main.go index f51008b1c7..e4273a1e45 100644 --- a/cmd/precompilegen/main.go +++ b/cmd/precompilegen/main.go @@ -89,7 +89,6 @@ func precompilegen(c *cli.Context) error { lang := bind.LangGo // If the entire solidity code was specified, build and bind based on that var ( - abis []string bins []string types []string sigs []map[string]string @@ -114,7 +113,6 @@ func precompilegen(c *cli.Context) error { if err != nil { utils.Fatalf("Failed to read input ABI: %v", err) } - abis = append(abis, string(abi)) bins = append(bins, "") @@ -147,7 +145,7 @@ func precompilegen(c *cli.Context) error { generateTests := !isOutStdout // Generate the contract precompile - bindedFiles, err := precompilebind.PrecompileBind(types, abis, bins, sigs, pkg, lang, libs, aliases, abifilename, generateTests) + bindedFiles, err := precompilebind.PrecompileBind(types, string(abi), bins, sigs, pkg, lang, libs, aliases, abifilename, generateTests) if err != nil { utils.Fatalf("Failed to generate precompile: %v", err) } @@ -191,7 +189,7 @@ func precompilegen(c *cli.Context) error { } // Write the ABI to the output folder - if err := os.WriteFile(abipath, []byte(abis[0]), 0o600); err != nil { + if err := os.WriteFile(abipath, abi, 0o600); err != nil { utils.Fatalf("Failed to write ABI: %v", err) } From 4897b495afcac83303b94497c759f4ae2ac2b93e Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 15 Aug 2023 13:08:26 +0300 Subject: [PATCH 4/4] revert extra underscore handling --- accounts/abi/argument.go | 8 --- .../bind/precompilebind/precompile_bind.go | 60 ++++++++++++------- .../precompilebind/precompile_bind_test.go | 45 +++++++++++--- 3 files changed, 75 insertions(+), 38 deletions(-) diff --git a/accounts/abi/argument.go b/accounts/abi/argument.go index e2be3a4bfe..6245ca62ea 100644 --- a/accounts/abi/argument.go +++ b/accounts/abi/argument.go @@ -279,13 +279,5 @@ func ToCamelCase(input string) string { parts[i] = strings.ToUpper(s[:1]) + s[1:] } } - // preserve leading underscore - if input[0] == '_' { - parts = append([]string{"_"}, parts...) - } - // preserve trailing underscore - if input[len(input)-1] == '_' { - parts = append(parts, "_") - } return strings.Join(parts, "") } diff --git a/accounts/abi/bind/precompilebind/precompile_bind.go b/accounts/abi/bind/precompilebind/precompile_bind.go index 6b2d937a5a..199e6cf36d 100644 --- a/accounts/abi/bind/precompilebind/precompile_bind.go +++ b/accounts/abi/bind/precompilebind/precompile_bind.go @@ -64,21 +64,10 @@ func PrecompileBind(types []string, abiData string, bytecodes []string, fsigs [] moduleHook := createPrecompileHook(abifilename, tmplSourcePrecompileModuleGo) configTestHook := createPrecompileHook(abifilename, tmplSourcePrecompileConfigTestGo) contractTestHook := createPrecompileHook(abifilename, tmplSourcePrecompileContractTestGo) - // check abi first - evmABI, err := abi.JSON(strings.NewReader(abiData)) - if err != nil { + + if err := verifyABI(abiData); err != nil { return BindedFiles{}, err } - if len(evmABI.Methods) == 0 { - return BindedFiles{}, errors.New("no ABI methods found") - } - for _, original := range evmABI.Methods { - for _, input := range original.Inputs { - if bind.IsKeyWord(input.Name) { - return BindedFiles{}, fmt.Errorf("input name %s is a keyword", input.Name) - } - } - } abis := []string{abiData} @@ -133,16 +122,10 @@ func createPrecompileHook(abifilename string, template string) bind.BindHook { 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) @@ -182,11 +165,42 @@ func allowListEnabled(funcs map[string]*bind.TmplMethod) bool { return true } -func checkOutputName(method bind.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) +func verifyABI(abiData string) error { + // check abi first + evmABI, err := abi.JSON(strings.NewReader(abiData)) + if err != nil { + return err + } + if len(evmABI.Methods) == 0 { + return errors.New("no ABI methods found") + } + for _, method := range evmABI.Methods { + names := make(map[string]bool) + for _, input := range method.Inputs { + if bind.IsKeyWord(input.Name) { + return fmt.Errorf("input name %s is a keyword", input.Name) + } + name := abi.ToCamelCase(input.Name) + if names[name] { + return fmt.Errorf("normalized input name is duplicated: %s", name) + } + names[name] = true + } + names = make(map[string]bool) + for _, output := range method.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.Name) + } + if bind.IsKeyWord(output.Name) { + return fmt.Errorf("output name %s is a keyword", output.Name) + } + name := abi.ToCamelCase(output.Name) + if names[name] { + return fmt.Errorf("normalized output name is duplicated: %s", name) + } + names[name] = true } } + return nil } diff --git a/accounts/abi/bind/precompilebind/precompile_bind_test.go b/accounts/abi/bind/precompilebind/precompile_bind_test.go index 3286cf8747..ee4e56763d 100644 --- a/accounts/abi/bind/precompilebind/precompile_bind_test.go +++ b/accounts/abi/bind/precompilebind/precompile_bind_test.go @@ -325,17 +325,48 @@ var bindTests = []struct { ` interface Underscorer { function UnderscoredOutput() external returns (int _int, string _string); + } + `, + `[{"inputs":[],"name":"UnderscoredOutput","outputs":[{"internalType":"int256","name":"_int","type":"int256"},{"internalType":"string","name":"_string","type":"string"}],"stateMutability":"nonpayable","type":"function"}]`, + ` + "github.com/stretchr/testify/require" + "math/big" + `, + ` + testOutput := UnderscoredOutputOutput{ + Int: big.NewInt(5), + String: "hello", + } + packedOutput, err := PackUnderscoredOutputOutput(testOutput) + require.NoError(t, err) + unpackedInput, err := UnpackUnderscoredOutputOutput(packedOutput) + require.NoError(t, err) + require.Equal(t, testOutput, unpackedInput) + `, + "", + }, + { + `OutputCollision`, + ` + interface Collision { function LowerLowerCollision() external returns (int _res, int res, int res_); - function LowerUpperCollision() external returns (int _res, int Res); - function UpperLowerCollision() external returns (int _Res, int res); - function UpperUpperCollision() external returns (int _Res, int Res); - function _under_scored_func() external returns (int _int); + `, + `[{"inputs":[],"name":"LowerLowerCollision","outputs":[{"internalType":"int256","name":"_res","type":"int256"},{"internalType":"int256","name":"res","type":"int256"},{"internalType":"int256","name":"res_","type":"int256"}],"stateMutability":"nonpayable","type":"function"}]`, + "", + "", + "normalized output name is duplicated", + }, + + { + `InputCollision`, + ` + interface Collision { + function LowerUpperCollision(int _res, int Res) external; } `, - `[{"inputs":[],"name":"LowerLowerCollision","outputs":[{"internalType":"int256","name":"_res","type":"int256"},{"internalType":"int256","name":"res","type":"int256"},{"internalType":"int256","name":"res_","type":"int256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"LowerUpperCollision","outputs":[{"internalType":"int256","name":"_res","type":"int256"},{"internalType":"int256","name":"Res","type":"int256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"UnderscoredOutput","outputs":[{"internalType":"int256","name":"_int","type":"int256"},{"internalType":"string","name":"_string","type":"string"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"UpperLowerCollision","outputs":[{"internalType":"int256","name":"_Res","type":"int256"},{"internalType":"int256","name":"res","type":"int256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"UpperUpperCollision","outputs":[{"internalType":"int256","name":"_Res","type":"int256"},{"internalType":"int256","name":"Res","type":"int256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"_under_scored_func","outputs":[{"internalType":"int256","name":"_int","type":"int256"}],"stateMutability":"nonpayable","type":"function"}]`, - ``, - ``, + `[{"inputs":[{"internalType":"int256","name":"_res","type":"int256"},{"internalType":"int256","name":"Res","type":"int256"}],"name":"LowerUpperCollision","outputs":[],"stateMutability":"nonpayable","type":"function"}]`, "", "", + "normalized input name is duplicated", }, { `DeeplyNestedArray`,