Files

708 lines
27 KiB
Go

// (c) 2020-2020, Lux Industries, 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 precompilebind
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/luxfi/evm/accounts/abi/bind"
"github.com/luxfi/geth/common"
"github.com/stretchr/testify/require"
)
var bindTests = []struct {
name string
contract string
abi string
imports string
tester string
errMsg string
expectAllowlist bool
}{
{
"AnonOutputChecker",
"",
`
[
{"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",
false,
},
{
"AnonOutputsChecker",
"",
`
[
{"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",
false,
},
{
"MixedOutputsChecker",
"",
`
[
{"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",
false,
},
// Test that module is generated correctly
{
`EmptyContract`,
`contract EmptyContract {}`,
"[]",
"",
"",
"no ABI methods found",
false,
},
// 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"
`,
`
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)
`,
"",
false,
},
// 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)
`,
"",
false,
},
{
`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)
`,
"",
false,
},
{
`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/luxfi/geth/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)
`,
"",
false,
},
{
`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)
`,
"",
false,
},
{
`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)
`,
"",
false,
},
{
`Underscorer`,
`
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)
`,
"",
false,
},
{
`OutputCollision`,
`
interface Collision {
function LowerLowerCollision() external returns (int _res, int res, int res_);
`,
`[{"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",
false,
},
{
`InputCollision`,
`
interface Collision {
function LowerUpperCollision(int _res, int Res) external;
}
`,
`[{"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",
false,
},
{
`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)
`,
"",
false,
},
{
"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",
false,
},
{
`HelloWorld`,
`interface IHelloWorld is IAllowList {
// 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":[{"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":"setManager","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"
"math/big"
"github.com/luxfi/geth/common"
"github.com/luxfi/evm/core/state"
"github.com/luxfi/evm/precompile/allowlist"
`,
`
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)
// test that the allow list is generated correctly
stateDB := extstate.NewTestStateDB(t)
address := common.BigToAddress(big.NewInt(1))
SetHelloWorldAllowListStatus(stateDB, address, allowlist.EnabledRole)
role := GetHelloWorldAllowListStatus(stateDB, address)
require.Equal(t, role, allowlist.EnabledRole)
`,
"",
true,
},
{
`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;
}
`,
// This ABI does not contain readAllowlist and setEnabled.
`[{"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":"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)
`,
"",
false,
},
{
`IEventer`,
`
interface IEventer {
event test(address indexed addressTest, uint indexed intTest, bytes bytesTest);
event empty();
event indexed(address addr, int8 indexed num);
event mixed(address indexed addr, int8 num);
event dynamic(string indexed idxStr, bytes indexed idxDat, string str, bytes dat);
event unnamed(uint8 indexed, uint8 indexed);
function eventTest() external view returns (string memory result);
}
`,
`[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"addressTest","type":"address"},{"indexed":true,"internalType":"uint8","name":"intTest","type":"uint8"},{"indexed":false,"internalType":"bytes","name":"bytesTest","type":"bytes"}],"name":"test","type":"event"},{"inputs":[],"name":"eventTest","outputs":[{"internalType":"string","name":"result","type":"string"}],"stateMutability":"view","type":"function"},{"type":"event","name":"empty","inputs":[]},{"type":"event","name":"indexed","inputs":[{"name":"addr","type":"address","indexed":true},{"name":"num","type":"int8","indexed":true}]},{"type":"event","name":"mixed","inputs":[{"name":"addr","type":"address","indexed":true},{"name":"num","type":"int8"}]},{"type":"event","name":"dynamic","inputs":[{"name":"idxStr","type":"string","indexed":true},{"name":"idxDat","type":"bytes","indexed":true},{"name":"str","type":"string"},{"name":"dat","type":"bytes"}]},{"type":"event","name":"unnamed","inputs":[{"name":"","type":"uint8","indexed":true},{"name":"","type":"uint8","indexed":true}]}]`,
`"github.com/stretchr/testify/require"
"github.com/luxfi/geth/common"
"github.com/luxfi/evm/precompile/contract"
`,
`
testAddr := common.Address{1}
testInt := int8(5)
testUint := uint8(5)
testBytes := []byte{1, 2, 3}
testEventData := TestEventData{
BytesTest: testBytes,
}
topics, data, err := PackTestEvent(testAddr, testUint, testEventData)
require.NoError(t, err)
eventID := IEventerABI.Events["test"].ID
require.Equal(t, eventID, topics[0])
unpacked, err := UnpackTestEventData(data)
require.NoError(t, err)
require.Equal(t, testBytes, unpacked.BytesTest)
gasCost := GetTestEventGasCost(testEventData)
require.Equal(t, contract.LogGas + 3 * contract.LogTopicGas + contract.LogDataGas, gasCost)
topics, data, err = PackEmptyEvent()
require.NoError(t, err)
eventID = IEventerABI.Events["empty"].ID
require.Len(t, topics, 1)
require.Equal(t, eventID, topics[0])
require.Equal(t, 0, len(data))
require.Equal(t, contract.LogGas, GetEmptyEventGasCost())
topics, data, err = PackIndexedEvent(testAddr, testInt)
require.NoError(t, err)
eventID = IEventerABI.Events["indexed"].ID
require.Len(t, topics, 3)
require.Equal(t, eventID, topics[0])
require.Equal(t, common.BytesToHash(testAddr[:]), topics[1])
require.Equal(t, 0, len(data))
require.Equal(t, contract.LogGas + 3 * contract.LogTopicGas, GetIndexedEventGasCost())
testMixedData := MixedEventData{
Num: testInt,
}
topics, data, err = PackMixedEvent(testAddr, testMixedData)
require.NoError(t, err)
eventID = IEventerABI.Events["mixed"].ID
require.Len(t, topics, 2)
require.Equal(t, eventID, topics[0])
require.Equal(t, common.BytesToHash(testAddr[:]), topics[1])
unpackedMixedData, err := UnpackMixedEventData(data)
require.NoError(t, err)
require.Equal(t, testMixedData, unpackedMixedData)
require.Equal(t, contract.LogGas + 2 * contract.LogTopicGas + contract.LogDataGas, GetMixedEventGasCost(testMixedData))
testDynamicData := DynamicEventData{
Str: "test",
Dat: testBytes,
}
topics, data, err = PackDynamicEvent("test", testBytes, testDynamicData)
require.NoError(t, err)
eventID = IEventerABI.Events["dynamic"].ID
require.Len(t, topics, 3)
require.Equal(t, eventID, topics[0])
unpackedDynamicData, err := UnpackDynamicEventData(data)
require.NoError(t, err)
require.Equal(t, testDynamicData, unpackedDynamicData)
require.Equal(t, contract.LogGas + 3 * contract.LogTopicGas + 2 * contract.LogDataGas, GetDynamicEventGasCost(testDynamicData))
topics, data, err = PackUnnamedEvent(testUint, testUint)
require.NoError(t, err)
eventID = IEventerABI.Events["unnamed"].ID
require.Len(t, topics, 3)
require.Equal(t, eventID, topics[0])
require.Equal(t, 0, len(data))
require.Equal(t, contract.LogGas + 3 * contract.LogTopicGas, GetUnnamedEventGasCost())
`,
"",
false,
},
{
`IEventerAnonymous`,
`
interface IEventer {
event Anonymous(address indexed, uint indexed, bytes) anonymous;
function eventTest() external view returns (string memory result);
}
`,
`[{"anonymous":true,"inputs":[{"indexed":true,"internalType":"address","name":"","type":"address"},{"indexed":true,"internalType":"uint256","name":"","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"","type":"bytes"}],"name":"Anonymous","type":"event"},{"inputs":[],"name":"eventTest","outputs":[{"internalType":"string","name":"result","type":"string"}],"stateMutability":"view","type":"function"}]`,
``,
``,
errNoAnonymousEvent.Error(),
false,
},
}
// 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, "precompilebindtest")
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 bindTests {
t.Run(tt.name, func(t *testing.T) {
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)
}
for _, file := range bindedFiles {
switch file.FileName {
case ContractFileName:
// check if the allowlist functions are generated
if tt.expectAllowlist {
require.Contains(t, file.Content, "allowlist.CreateAllowListFunctions(", "generated contract does not contain AllowListFunctions")
} else {
require.NotContains(t, file.Content, "allowlist.CreateAllowListFunctions(", "generated contract contains AllowListFunctions")
}
case ModuleFileName:
// change address to a suitable one for testing
file.Content = strings.Replace(file.Content, `common.HexToAddress("{ASUITABLEHEXADDRESS}")`, `common.HexToAddress("0x03000000000000000000000000000000000000ff")`, 1)
}
if err = os.WriteFile(filepath.Join(precompilePath, file.FileName), []byte(file.Content), 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), 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 (
%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)
}
})
}
moder := exec.Command(gocmd, "mod", "init", "precompilebindtest")
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/luxfi/[email protected]", "-replace", "github.com/luxfi/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.23")
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)
}
}