go-ethereum/contractverifier/contract_verifier_test.go
Zhong Qiu 034b2bf6ad
patch (#3)
* Add verifier for controlling contract method whether can be called.

* change code name

Co-authored-by: BananaLF <864685021@qq.com>
2021-12-13 10:55:33 +08:00

233 lines
7.1 KiB
Go

package contractverifier
import (
"encoding/json"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/rlp"
"io/ioutil"
"math/big"
"path/filepath"
"strings"
"testing"
"unicode"
)
const (
UNKNOWN = iota
SELFDESTRUCT
NORMAL_CALL
)
type contractVerifierTest struct {
verifiers []verifierTest
}
func NewContractVerifier() *contractVerifierTest {
return &contractVerifierTest{verifiers: make([]verifierTest, 0)}
}
type verifierTest struct {
VerifierType int
Op vm.OpCode
From common.Address
To common.Address
Input string
Value string
}
func NewVerifierTest(verifierType int, op vm.OpCode, from, to common.Address, input []byte, value *big.Int) verifierTest {
return verifierTest{
VerifierType: verifierType,
Op: op,
From: from,
To: to,
Input: hexutil.Encode(input),
Value: fmt.Sprintf("0x%s", value.Text(16)),
}
}
func (cv *contractVerifierTest) Verify(stateDB vm.StateDB, op vm.OpCode, from, to common.Address, input []byte, value *big.Int) error {
verifierTest := NewVerifierTest(UNKNOWN, op, from, to, input, value)
if op == vm.SELFDESTRUCT {
verifierTest.VerifierType = SELFDESTRUCT
} else if op == vm.CALL || op == vm.DELEGATECALL || op == vm.STATICCALL || op == vm.CALLCODE {
verifierTest.VerifierType = NORMAL_CALL
}
cv.verifiers = append(cv.verifiers, verifierTest)
return nil
}
func convertVerifierType(op vm.OpCode) int {
result := UNKNOWN
if op == vm.SELFDESTRUCT {
result = SELFDESTRUCT
} else if op == vm.CALL || op == vm.DELEGATECALL || op == vm.STATICCALL || op == vm.CALLCODE {
result = NORMAL_CALL
}
return result
}
// callTrace is the result of a callTracer run.
type callTrace struct {
Type string `json:"type"`
From common.Address `json:"from"`
To common.Address `json:"to"`
Input hexutil.Bytes `json:"input"`
Output hexutil.Bytes `json:"output"`
Gas *hexutil.Uint64 `json:"gas,omitempty"`
GasUsed *hexutil.Uint64 `json:"gasUsed,omitempty"`
Value *hexutil.Big `json:"value,omitempty"`
Error string `json:"error,omitempty"`
Calls []callTrace `json:"calls,omitempty"`
}
func (ct *callTrace) GetArrayCall() []verifierTest {
results := make([]verifierTest, 0)
self := NewVerifierTest(convertVerifierType(vm.StringToOp(ct.Type)), vm.StringToOp(ct.Type), ct.From, ct.To, nil, nil)
self.Input = ct.Input.String()
self.Value = ct.Value.String()
results = append(results, self)
callsNumber := len(ct.Calls)
if callsNumber == 0 {
return results
}
for i, _ := range ct.Calls {
nextResults := ct.Calls[i].GetArrayCall()
results = append(results, nextResults...)
}
return results
}
type callContext struct {
Number math.HexOrDecimal64 `json:"number"`
Difficulty *math.HexOrDecimal256 `json:"difficulty"`
Time math.HexOrDecimal64 `json:"timestamp"`
GasLimit math.HexOrDecimal64 `json:"gasLimit"`
Miner common.Address `json:"miner"`
}
// callTracerTest defines a single test to check the call tracer against.
type callTracerTest struct {
Genesis *core.Genesis `json:"genesis"`
Context *callContext `json:"context"`
Input string `json:"input"`
Result *callTrace `json:"result"`
}
func (ct callTracerTest) GetArrayResult() []verifierTest {
return ct.Result.GetArrayCall()
}
// camel converts a snake cased input string into a camel cased output.
func camel(str string) string {
pieces := strings.Split(str, "_")
for i := 1; i < len(pieces); i++ {
pieces[i] = string(unicode.ToUpper(rune(pieces[i][0]))) + pieces[i][1:]
}
return strings.Join(pieces, "")
}
func makePreState(db ethdb.Database, accounts core.GenesisAlloc, snapshotter bool) (*snapshot.Tree, *state.StateDB) {
sdb := state.NewDatabase(db)
statedb, _ := state.New(common.Hash{}, sdb, nil)
for addr, a := range accounts {
statedb.SetCode(addr, a.Code)
statedb.SetNonce(addr, a.Nonce)
statedb.SetBalance(addr, a.Balance)
for k, v := range a.Storage {
statedb.SetState(addr, k, v)
}
}
// Commit and re-open to start with a clean state.
root, _ := statedb.Commit(false)
var snaps *snapshot.Tree
if snapshotter {
snaps, _ = snapshot.New(db, sdb.TrieDB(), 1, root, false, true, false)
}
statedb, _ = state.New(root, sdb, snaps)
return snaps, statedb
}
// Iterates over all the input-output datasets in the tracer test harness and
// runs the JavaScript tracers against them.
func TestOKVerify(t *testing.T) {
files, err := ioutil.ReadDir("testdata")
if err != nil {
t.Fatalf("failed to retrieve tracer test suite: %v", err)
}
//index := 0
for _, file := range files {
//if i < index {
// continue
//} else if i > index {
// return
//} else {
// fmt.Println("@@@@@@@@@@@@@@@@@@@@", file.Name())
//}
file := file // capture range variable
t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) {
t.Parallel()
// Call tracer test found, read if from disk
blob, err := ioutil.ReadFile(filepath.Join("testdata", file.Name()))
if err != nil {
t.Fatalf("failed to read testcase: %v", err)
}
test := new(callTracerTest)
if err := json.Unmarshal(blob, test); err != nil {
t.Fatalf("failed to parse testcase: %v", err)
}
// Configure a blockchain with the given prestate
tx := new(types.Transaction)
if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil {
t.Fatalf("failed to parse testcase input: %v", err)
}
signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)))
origin, _ := signer.Sender(tx)
txContext := vm.TxContext{
Origin: origin,
GasPrice: tx.GasPrice(),
}
context := vm.BlockContext{
CanTransfer: core.CanTransfer,
Transfer: core.Transfer,
Coinbase: test.Context.Miner,
BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)),
Time: new(big.Int).SetUint64(uint64(test.Context.Time)),
Difficulty: (*big.Int)(test.Context.Difficulty),
GasLimit: uint64(test.Context.GasLimit),
}
_, statedb := makePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
verifierTest := NewContractVerifier()
evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{ContractVerifier: verifierTest})
msg, err := tx.AsMessage(signer, nil)
if err != nil {
t.Fatalf("failed to prepare transaction for tracing: %v", err)
}
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
if _, err = st.TransitionDb(); err != nil {
t.Fatalf("failed to execute transaction: %v", err)
}
actual := verifierTest.verifiers
excepted := test.GetArrayResult()
require.Equal(t, excepted, actual)
})
}
}