From 6a5768b521b9c40750b3648f52d93c2d5acf8466 Mon Sep 17 00:00:00 2001 From: steven Date: Fri, 18 Oct 2019 18:10:16 +0800 Subject: [PATCH 1/6] add cli tool --- localtool/cli.go | 200 +++++++++++ wasmtest/common/common.go | 15 +- wasmtest/common/testframe.go | 628 +++++++++++++++++++++++++++++++++++ wasmtest/run-wasm-tests.sh | 5 +- wasmtest/wasm-test.go | 441 +----------------------- 5 files changed, 856 insertions(+), 433 deletions(-) create mode 100644 localtool/cli.go create mode 100644 wasmtest/common/testframe.go diff --git a/localtool/cli.go b/localtool/cli.go new file mode 100644 index 0000000000..1384a1ebb4 --- /dev/null +++ b/localtool/cli.go @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2018 The ontology Authors + * This file is part of The ontology library. + * + * The ontology 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 ontology 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 ontology. If not, see . + */ + +package main + +import ( + "encoding/json" + //"fmt" + "io/ioutil" + "os" + "runtime" + + "github.com/ontio/ontology/cmd/utils" + "github.com/ontio/ontology/common/log" + "github.com/ontio/ontology/errors" + "github.com/ontio/ontology/wasmtest/common" + "github.com/urfave/cli" +) + +type TestConfigElt struct { + Contract string `json:"contract"` + Balanceaddr []common.BalanceAddr `json:"balanceaddr"` + Testcase [][]common.TestCase `json:"testcase"` +} + +type TestConfig struct { + Testconfigelt []TestConfigElt `json:"testconfig"` +} + +func ontologyCLI(ctx *cli.Context) error { + // Get the deployObject. + args := ctx.Args() + if len(args) != 1 { + cli.ShowAppHelp(ctx) + return errors.NewErr("[Option Error]: need arg.") + } + deployobject := args[0] + + // Load the deployObject. Assert deployObject is directory or not. + contracts, objIsDir, err := common.GetContact(deployobject) + if err != nil { + cli.ShowAppHelp(ctx) + return err + } + + // check option. + config, paramsStr, err := parseOption(ctx, objIsDir) + if err != nil { + cli.ShowAppHelp(ctx) + return err + } + + acct, database := common.InitOntologyLedger() + err = common.DeployContract(acct, database, contracts) + if err != nil { + return err + } + + testContext := common.MakeTestContext(acct, contracts) + + if config != nil { + for _, configelt := range config.Testconfigelt { + err := common.InitBalanceAddress(configelt.Balanceaddr, acct, database) + if err != nil { + return err + } + // currently only use the index zero array. + for _, testcase := range configelt.Testcase[0] { + err := common.TestWithConfigElt(acct, database, configelt.Contract, testcase, testContext) + if err != nil { + return err + } + } + } + } else { + err = common.InvokeSpecifiedContract(acct, database, deployobject, paramsStr, testContext) + if err != nil { + return err + } + } + + return nil +} + +func parseOption(ctx *cli.Context, objIsDir bool) (*TestConfig, string, error) { + if ctx.IsSet(utils.GetFlagName(ContractParamsFlag)) && ctx.IsSet(utils.GetFlagName(ConfigFlag)) { + return nil, "", errors.NewErr("[Option Error]: You can only specify --param or --config") + } + + LogLevel := ctx.Uint(utils.GetFlagName(LogLevelFlag)) + log.InitLog(int(LogLevel), log.PATH, log.Stdout) + + if objIsDir { + if ctx.IsSet(utils.GetFlagName(ContractParamsFlag)) { + return nil, "", errors.NewErr("[Option Error]: Can not specify --param when input is a directory") + } + + if !ctx.IsSet((utils.GetFlagName(ConfigFlag))) { + return nil, "", errors.NewErr("[Option Error]: Must specify --config config.json file when input is a directory") + } + } + + if ctx.IsSet(utils.GetFlagName(ConfigFlag)) { + configFileName := ctx.String(utils.GetFlagName(ConfigFlag)) + configBuff, err := ioutil.ReadFile(configFileName) + if err != nil { + return nil, "", err + } + + var config TestConfig + err = json.Unmarshal([]byte(configBuff), &config) + if err != nil { + return nil, "", err + } + + if len(config.Testconfigelt) == 0 { + return nil, "", errors.NewErr("No testcase in config file") + } + + for _, configelt := range config.Testconfigelt { + if len(configelt.Contract) == 0 { + return nil, "", errors.NewErr("[Config format error]: Do not specify contract name") + } + + if len(configelt.Testcase) == 0 { + return nil, "", errors.NewErr("[Config format error]: Do not specify testcase") + } + } + + return &config, "", nil + } + + if ctx.IsSet(utils.GetFlagName(ContractParamsFlag)) { + paramsStr := ctx.String(utils.GetFlagName(ContractParamsFlag)) + return nil, paramsStr, nil + } + + return nil, "", nil +} + +func main() { + if err := setupAPP().Run(os.Args); err != nil { + os.Exit(1) + } +} + +var ( + ConfigFlag = cli.StringFlag{ + Name: "config,c", + Usage: "the contract filename to be tested.", + } + ContractParamsFlag = cli.StringFlag{ + Name: "param,p", + Usage: "specify contract param when input is a file.", + } + LogLevelFlag = cli.UintFlag{ + Name: "loglevel,l", + Usage: "set the log levela.", + Value: log.InfoLog, + } +) + +func setupAPP() *cli.App { + app := cli.NewApp() + app.Usage = "cli" + app.UsageText = "cli [option] input" + app.Action = ontologyCLI + app.Version = "1.0.0" + app.Copyright = "Copyright in 2019 The Ontology Authors" + app.Flags = []cli.Flag{ + ConfigFlag, + ContractParamsFlag, + LogLevelFlag, + } + app.Before = func(context *cli.Context) error { + runtime.GOMAXPROCS(runtime.NumCPU()) + return nil + } + app.ExitErrHandler = func(context *cli.Context, err error) { + if err != nil { + log.Fatalf("%v", err) + } + } + return app +} diff --git a/wasmtest/common/common.go b/wasmtest/common/common.go index 0411dcd5bb..9b79e36662 100644 --- a/wasmtest/common/common.go +++ b/wasmtest/common/common.go @@ -19,6 +19,7 @@ package common import ( "bytes" + "fmt" "encoding/json" utils2 "github.com/ontio/ontology/cmd/utils" @@ -86,11 +87,14 @@ type TestContext struct { } func GenWasmTransaction(testCase TestCase, contract common.Address, testConext *TestContext) (*types.Transaction, error) { + var allParam []interface{} params, err := utils2.ParseParams(testCase.Param) if err != nil { - return nil, err + return nil, fmt.Errorf("[%s]: You follow example \"int:2,string:\"hello world\",address:Ab1z3Sxy7ovn4AuScdmMh4PRMvcwCMzSNV,[int:1,int:2],bytearray:60c56b6a00527a\" ", err) + } + if len(testCase.Method) != 0 { + allParam = append([]interface{}{}, testCase.Method) } - allParam := append([]interface{}{}, testCase.Method) allParam = append(allParam, params...) tx, err := utils.NewWasmVMInvokeTransaction(0, 100000000, contract, allParam) if err != nil { @@ -162,11 +166,14 @@ func buildTestConextForNeo(testConext *TestContext) []byte { } func GenNeoVMTransaction(testCase TestCase, contract common.Address, testConext *TestContext) (*types.Transaction, error) { + var allParam []interface{} params, err := utils2.ParseParams(testCase.Param) if err != nil { - return nil, err + return nil, fmt.Errorf("[%s]: You follow example \"int:2,string:\"hello world\",address:Ab1z3Sxy7ovn4AuScdmMh4PRMvcwCMzSNV,[int:1,int:2],bytearray:60c56b6a00527a\" ", err) + } + if len(testCase.Method) != 0 { + allParam = append([]interface{}{}, testCase.Method) } - allParam := append([]interface{}{}, testCase.Method) allParam = append(allParam, params...) tx, err := common2.NewNeovmInvokeTransaction(0, 100000000, contract, allParam) if err != nil { diff --git a/wasmtest/common/testframe.go b/wasmtest/common/testframe.go new file mode 100644 index 0000000000..c3fb57d8ee --- /dev/null +++ b/wasmtest/common/testframe.go @@ -0,0 +1,628 @@ +/* + * Copyright (C) 2018 The ontology Authors + * This file is part of The ontology library. + * + * The ontology 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 ontology 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 ontology. If not, see . + */ + +package common + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path" + "path/filepath" + "reflect" + "runtime" + "strings" + + "github.com/go-interpreter/wagon/exec" + "github.com/go-interpreter/wagon/wasm" + "github.com/ontio/ontology-crypto/keypair" + "github.com/ontio/ontology/account" + "github.com/ontio/ontology/cmd/utils" + "github.com/ontio/ontology/common" + "github.com/ontio/ontology/common/config" + "github.com/ontio/ontology/common/constants" + "github.com/ontio/ontology/common/log" + "github.com/ontio/ontology/core/genesis" + "github.com/ontio/ontology/core/ledger" + "github.com/ontio/ontology/core/payload" + "github.com/ontio/ontology/core/signature" + "github.com/ontio/ontology/core/types" + utils2 "github.com/ontio/ontology/core/utils" + "github.com/ontio/ontology/events" + common2 "github.com/ontio/ontology/http/base/common" + "github.com/ontio/ontology/smartcontract/service/native/ont" + utils3 "github.com/ontio/ontology/smartcontract/service/native/utils" + "github.com/ontio/ontology/smartcontract/service/wasmvm" + "github.com/ontio/ontology/smartcontract/states" + vmtypes "github.com/ontio/ontology/vm/neovm/types" +) + +const ( + testcaseMethod = "testcase" +) + +func init() { + runtime.GOMAXPROCS(4) +} + +func checkErr(err error) { + if err != nil { + panic(err) + } +} + +func InitOntologyLedger() (*account.Account, *ledger.Ledger) { + datadir := "testdata" + err := os.RemoveAll(datadir) + defer func() { + _ = os.RemoveAll(datadir) + _ = os.RemoveAll(log.PATH) + }() + + acct := account.NewAccount("") + + buf := keypair.SerializePublicKey(acct.PublicKey) + config.DefConfig.Genesis.ConsensusType = "solo" + config.DefConfig.Genesis.SOLO.GenBlockTime = 3 + config.DefConfig.Genesis.SOLO.Bookkeepers = []string{hex.EncodeToString(buf)} + config.DefConfig.P2PNode.NetworkId = 0 + + bookkeepers := []keypair.PublicKey{acct.PublicKey} + //Init event hub + events.Init() + + //log.Info("1. Loading the Ledger") + database, err := ledger.NewLedger(datadir, 1000000) + checkErr(err) + ledger.DefLedger = database + genblock, err := genesis.BuildGenesisBlock(bookkeepers, config.DefConfig.Genesis) + checkErr(err) + err = database.Init(bookkeepers, genblock) + checkErr(err) + return acct, database +} + +type BalanceAddr struct { + Address string `json:"address"` + Balance uint64 `json:"balance"` +} + +func InitBalanceAddress(balanceAddr []BalanceAddr, acct *account.Account, database *ledger.Ledger) error { + fmt.Printf("========%v\n", balanceAddr) + for _, elt := range balanceAddr { + to, err := common.AddressFromBase58(elt.Address) + if err != nil { + return err + } + var sts []*ont.State + sts = append(sts, &ont.State{ + From: acct.Address, + To: to, + Value: elt.Balance, + }) + + mutable, err := common2.NewNativeInvokeTransaction(0, 200000000, utils3.OntContractAddress, 0, ont.TRANSFER_NAME, []interface{}{sts}) + + tx, err := mutable.IntoImmutable() + if err != nil { + panic("build ont tansfer tx failed") + } + + tx.SignedAddr = append(tx.SignedAddr, acct.Address) + + block, _ := makeBlock(acct, []*types.Transaction{tx}) + err = database.AddBlock(block, common.UINT256_EMPTY) + checkErr(err) + event, err := database.GetEventNotifyByTx(tx.Hash()) + js, _ := json.Marshal(event.Notify) + log.Infof("Notify info : %s", string(js)) + } + return nil +} + +type ExecEnv struct { + Contract common.Address + Time uint32 + Height uint32 + Tx *types.Transaction + BlockHash common.Uint256 +} + +func checkExecResult(testCase TestCase, result *states.PreExecResult, execEnv ExecEnv) { + assertEq(result.State, byte(1)) + ret := result.Result.(string) + switch testCase.Method { + case "timestamp": + sink := common.NewZeroCopySink(nil) + sink.WriteUint64(uint64(execEnv.Time)) + assertEq(ret, hex.EncodeToString(sink.Bytes())) + case "block_height": + sink := common.NewZeroCopySink(nil) + sink.WriteUint32(uint32(execEnv.Height)) + assertEq(ret, hex.EncodeToString(sink.Bytes())) + case "self_address", "entry_address": + assertEq(ret, hex.EncodeToString(execEnv.Contract[:])) + case "caller_address": + assertEq(ret, hex.EncodeToString(common.ADDRESS_EMPTY[:])) + case "current_txhash": + hash := execEnv.Tx.Hash() + assertEq(ret, hex.EncodeToString(hash[:])) + case "current_blockhash": + assertEq(ret, hex.EncodeToString(execEnv.BlockHash[:])) + //case "sha256": + // let data :&[u8]= source.read().unwrap(); + // sink.write(runtime::sha256(&data)) + //} + default: + if len(testCase.Expect) != 0 { + expect, err := utils.ParseParams(testCase.Expect) + checkErr(err) + if execEnv.Tx.TxType == types.InvokeNeo { + val := buildNeoVmValueFromExpect(expect) + cv, err := val.ConvertNeoVmValueHexString() + checkErr(err) + assertEq(cv, result.Result) + } else if execEnv.Tx.TxType == types.InvokeWasm { + exp, err := utils2.BuildWasmContractParam(expect) + checkErr(err) + assertEq(ret, hex.EncodeToString(exp)) + } else { + panic("error tx type") + } + } + if len(testCase.Notify) != 0 { + js, _ := json.Marshal(result.Notify) + assertEq(true, strings.Contains(string(js), testCase.Notify)) + } + } +} + +func buildNeoVmValueFromExpect(expectlist []interface{}) *vmtypes.VmValue { + if len(expectlist) > 1 { + panic("only support return one value") + } + expect := expectlist[0] + + switch expect.(type) { + case string: + val, err := vmtypes.VmValueFromBytes([]byte(expect.(string))) + if err != nil { + panic(err) + } + return &val + case []byte: + val, err := vmtypes.VmValueFromBytes(expect.([]byte)) + if err != nil { + panic(err) + } + return &val + case int64: + val := vmtypes.VmValueFromInt64(expect.(int64)) + return &val + case bool: + val := vmtypes.VmValueFromBool(expect.(bool)) + return &val + case common.Address: + addr := expect.(common.Address) + val, err := vmtypes.VmValueFromBytes(addr[:]) + if err != nil { + panic(err) + } + return &val + default: + fmt.Printf("unspport param type %s", reflect.TypeOf(expect)) + panic("unspport param type") + } +} + +func makeBlock(acc *account.Account, txs []*types.Transaction) (*types.Block, error) { + nextBookkeeper, err := types.AddressFromBookkeepers([]keypair.PublicKey{acc.PublicKey}) + if err != nil { + return nil, fmt.Errorf("GetBookkeeperAddress error:%s", err) + } + prevHash := ledger.DefLedger.GetCurrentBlockHash() + height := ledger.DefLedger.GetCurrentBlockHeight() + + nonce := uint64(height) + var txHash []common.Uint256 + for _, t := range txs { + txHash = append(txHash, t.Hash()) + } + + txRoot := common.ComputeMerkleRoot(txHash) + + blockRoot := ledger.DefLedger.GetBlockRootWithNewTxRoots(height+1, []common.Uint256{txRoot}) + header := &types.Header{ + Version: 0, + PrevBlockHash: prevHash, + TransactionsRoot: txRoot, + BlockRoot: blockRoot, + Timestamp: constants.GENESIS_BLOCK_TIMESTAMP + height + 1, + Height: height + 1, + ConsensusData: nonce, + NextBookkeeper: nextBookkeeper, + } + block := &types.Block{ + Header: header, + Transactions: txs, + } + + blockHash := block.Hash() + + sig, err := signature.Sign(acc, blockHash[:]) + if err != nil { + return nil, fmt.Errorf("signature, Sign error:%s", err) + } + + block.Header.Bookkeepers = []keypair.PublicKey{acc.PublicKey} + block.Header.SigData = [][]byte{sig} + return block, nil +} + +func assertEq(a interface{}, b interface{}) { + if reflect.DeepEqual(a, b) == false { + panic(fmt.Sprintf("not equal: a= %v, b=%v", a, b)) + } +} + +func MakeTestContext(acct *account.Account, contract map[string][]byte) *TestContext { + addrMap := make(map[string]common.Address) + for file, code := range contract { + addrMap[path.Base(file)] = common.AddressFromVmCode(code) + } + + testContext := TestContext{ + Admin: acct.Address, + AddrMap: addrMap, + } + return &testContext +} + +func ExecTxCheckRes(tx *types.Transaction, testCase TestCase, database *ledger.Ledger, addr common.Address, acct *account.Account) error { + res, err := database.PreExecuteContract(tx) + log.Infof("testcase consume gas: %d", res.Gas) + if err != nil { + return err + } + + height := database.GetCurrentBlockHeight() + header, err := database.GetHeaderByHeight(height) + checkErr(err) + blockTime := header.Timestamp + 1 + + execEnv := ExecEnv{Time: blockTime, Height: height + 1, Tx: tx, BlockHash: header.Hash(), Contract: addr} + checkExecResult(testCase, res, execEnv) + + block, _ := makeBlock(acct, []*types.Transaction{tx}) + err = database.AddBlock(block, common.UINT256_EMPTY) + checkErr(err) + return nil +} + +func loadContractsByDir(dir string, contracts map[string][]byte) error { + fnames, err := filepath.Glob(filepath.Join(dir, "*")) + if err != nil { + return err + } + + for _, name := range fnames { + if !strings.HasSuffix(name, ".wasm") && !strings.HasSuffix(name, ".avm") && !strings.HasSuffix(name, ".wasm.str") && !strings.HasSuffix(name, ".avm.str") { + continue + } + raw, err := ioutil.ReadFile(name) + if err != nil { + return err + } + + if strings.HasSuffix(name, ".str") { + code, err := hex.DecodeString(strings.TrimSpace(string(raw))) + if err != nil { + return err + } + contracts[path.Base(name)] = code + } else { + contracts[path.Base(name)] = raw + } + } + + return nil +} + +func DeployContract(acct *account.Account, database *ledger.Ledger, contract map[string][]byte) error { + txes := make([]*types.Transaction, 0, len(contract)) + for file, cont := range contract { + var tx *types.Transaction + var err error + if strings.HasSuffix(file, ".wasm") || strings.HasSuffix(file, ".wasm.str") { + tx, err = NewDeployWasmContract(acct, cont) + } else if strings.HasSuffix(file, ".avm") || strings.HasSuffix(file, ".avm.str") { + tx, err = NewDeployNeoContract(acct, cont) + } else { + panic("error file suffix") + } + + if err != nil { + return err + } + + res, err := database.PreExecuteContract(tx) + log.Infof("deploy %s consume gas: %d", file, res.Gas) + if err != nil { + return err + } + txes = append(txes, tx) + } + + block, err := makeBlock(acct, txes) + checkErr(err) + err = database.AddBlock(block, common.UINT256_EMPTY) + checkErr(err) + return nil +} + +func NewDeployWasmContract(signer *account.Account, code []byte) (*types.Transaction, error) { + mutable, err := utils.NewDeployCodeTransaction(0, 100000000, code, payload.WASMVM_TYPE, "name", "version", + "author", "email", "desc") + if err != nil { + return nil, err + } + err = utils.SignTransaction(signer, mutable) + if err != nil { + return nil, err + } + tx, err := mutable.IntoImmutable() + return tx, err +} + +func NewDeployNeoContract(signer *account.Account, code []byte) (*types.Transaction, error) { + mutable, err := utils.NewDeployCodeTransaction(0, 100000000, code, payload.NEOVM_TYPE, "name", "version", + "author", "email", "desc") + if err != nil { + return nil, err + } + err = utils.SignTransaction(signer, mutable) + if err != nil { + return nil, err + } + tx, err := mutable.IntoImmutable() + return tx, err +} + +func InvokeSpecifiedContract(acct *account.Account, database *ledger.Ledger, contractfile string, paramsStr string, testContext *TestContext) error { + var tx *types.Transaction + if strings.HasSuffix(contractfile, ".avm") || strings.HasSuffix(contractfile, ".avm.str") { + testCase := TestCase{Param: paramsStr} + t, err := GenNeoVMTransaction(testCase, testContext.AddrMap[contractfile], testContext) + tx = t + if err != nil { + return err + } + } else if strings.HasSuffix(contractfile, ".wasm") || strings.HasSuffix(contractfile, ".wasm.str") { + testCase := TestCase{Param: paramsStr} + t, err := GenWasmTransaction(testCase, testContext.AddrMap[contractfile], testContext) + tx = t + if err != nil { + return err + } + } else { + panic("error suffix type") + } + + res, err := database.PreExecuteContract(tx) + log.Infof("testcase consume gas: %d", res.Gas) + if err != nil { + return err + } + block, err := makeBlock(acct, []*types.Transaction{tx}) + checkErr(err) + err = database.AddBlock(block, common.UINT256_EMPTY) + checkErr(err) + if len(res.Notify) != 0 { + js, _ := json.Marshal(res.Notify) + log.Infof("Notify info : %s", string(js)) + } + + if res.Result != nil { + js, _ := json.Marshal(res.Result) + log.Infof("Return result: %s", string(js)) + } + + return nil +} + +func GetContact(deployobject string) (map[string][]byte, bool, error) { + var objIsDir bool + obj, err := os.Stat(deployobject) + if err != nil { + return nil, false, err + } + + contract := make(map[string][]byte) + if obj.IsDir() { + objIsDir = true + err := loadContractsByDir(deployobject, contract) + if err != nil { + return nil, false, err + } + } else { + objIsDir = false + if !strings.HasSuffix(deployobject, ".wasm") && !strings.HasSuffix(deployobject, ".avm") && !strings.HasSuffix(deployobject, ".wasm.str") && !strings.HasSuffix(deployobject, ".avm.str") { + return nil, false, fmt.Errorf("[contract file suffix error]: filename \"%s\" must be .wasm/.avm/.wasm.str/.avm.str.", deployobject) + } + raw, err := ioutil.ReadFile(deployobject) + if err != nil { + return nil, false, err + } + if strings.HasSuffix(deployobject, ".str") { + code, err := hex.DecodeString(strings.TrimSpace(string(raw))) + if err != nil { + return nil, false, err + } + contract[path.Base(deployobject)] = code + } else { + contract[path.Base(deployobject)] = raw + } + } + + if len(contract) == 0 { + return nil, false, fmt.Errorf("no contract to test.") + } + + return contract, objIsDir, nil +} + +func TestWithConfigElt(acct *account.Account, database *ledger.Ledger, file string, testCase TestCase, testContext *TestContext) error { + var tx *types.Transaction + val, _ := json.Marshal(testCase) + log.Info("executing testcase: ", string(val)) + addr, exist := testContext.AddrMap[file] + if !exist { + return fmt.Errorf("Contract %s not exist. ", file) + } + + if strings.HasSuffix(file, ".avm") || strings.HasSuffix(file, ".avm.str") { + t, err := GenNeoVMTransaction(testCase, addr, testContext) + if err != nil { + return err + } + tx = t + } else if strings.HasSuffix(file, ".wasm") || strings.HasSuffix(file, ".wasm.str") { + t, err := GenWasmTransaction(testCase, addr, testContext) + if err != nil { + return err + } + tx = t + } + + err := ExecTxCheckRes(tx, testCase, database, addr, acct) + if err != nil { + return err + } + + return nil +} + +// ==================================== +func TestWithbatchMode(acct *account.Account, database *ledger.Ledger, contract map[string][]byte, testContext *TestContext) { + for file, cont := range contract { + testSpecifiedContractWithbatchMode(acct, database, file, cont, testContext) + } + + log.Info("contract test succeed") +} + +func testSpecifiedContractWithbatchMode(acct *account.Account, database *ledger.Ledger, file string, cont []byte, testContext *TestContext) { + log.Infof("exacting testcase from %s", file) + addr := testContext.AddrMap[file] + + if strings.HasSuffix(file, ".avm") || strings.HasSuffix(file, ".avm.str") { + testCases := GenNeoTextCaseTransaction(addr, database) + for _, testCase := range testCases[0] { // only handle group 0 currently + TestWithConfigElt(acct, database, file, testCase, testContext) + } + } else if strings.HasSuffix(file, ".wasm") || strings.HasSuffix(file, ".wasm.str") { + testCases := ExactTestCase(cont) + for _, testCase := range testCases[0] { // only handle group 0 currently + TestWithConfigElt(acct, database, file, testCase, testContext) + } + } else { + panic("testSpecifiedContractWithbatchMode: error suffix contract name") + } +} + +func ExactTestCase(code []byte) [][]TestCase { + m, err := wasm.ReadModule(bytes.NewReader(code), func(name string) (*wasm.Module, error) { + switch name { + case "env": + return wasmvm.NewHostModule(), nil + } + return nil, fmt.Errorf("module %q unknown", name) + }) + checkErr(err) + + compiled, err := exec.CompileModule(m) + checkErr(err) + + vm, err := exec.NewVMWithCompiled(compiled, 10*1024*1024) + checkErr(err) + + param := common.NewZeroCopySink(nil) + param.WriteString(testcaseMethod) + host := &wasmvm.Runtime{Input: param.Bytes()} + vm.HostData = host + vm.RecoverPanic = true + envGasLimit := uint64(100000000000000) + envExecStep := uint64(100000000000000) + vm.AvaliableGas = &exec.Gas{GasLimit: &envGasLimit, GasPrice: 0, GasFactor: 5, ExecStep: &envExecStep} + vm.CallStackDepth = 1024 + + entry := compiled.RawModule.Export.Entries["invoke"] + index := int64(entry.Index) + _, err = vm.ExecCode(index) + checkErr(err) + + var testCase [][]TestCase + source := common.NewZeroCopySource(host.Output) + jsonCase, _, _, _ := source.NextString() + + if len(jsonCase) == 0 { + panic("failed to get testcase data from contract") + } + + err = json.Unmarshal([]byte(jsonCase), &testCase) + checkErr(err) + + return testCase +} + +func GenNeoTextCaseTransaction(contract common.Address, database *ledger.Ledger) [][]TestCase { + params := make([]interface{}, 0) + method := string("testcase") + // neovm entry api is def Main(method, args). and testcase method api need no other args, so pass a random args to entry api. + operation := 1 + params = append(params, method) + params = append(params, operation) + tx, err := common2.NewNeovmInvokeTransaction(0, 100000000, contract, params) + imt, err := tx.IntoImmutable() + if err != nil { + panic(err) + } + res, err := database.PreExecuteContract(imt) + if err != nil { + panic(err) + } + + ret := res.Result.(string) + jsonCase, err := common.HexToBytes(ret) + + if err != nil { + panic(err) + } + if len(jsonCase) == 0 { + panic("failed to get testcase data from contract") + } + var testCase [][]TestCase + err = json.Unmarshal([]byte(jsonCase), &testCase) + if err != nil { + panic("failed Unmarshal") + } + return testCase +} diff --git a/wasmtest/run-wasm-tests.sh b/wasmtest/run-wasm-tests.sh index 4bb2d6f18f..ff30087cfe 100644 --- a/wasmtest/run-wasm-tests.sh +++ b/wasmtest/run-wasm-tests.sh @@ -16,14 +16,15 @@ fi rustup target add wasm32-unknown-unknown which ontio-wasm-build || cargo install --git=https://github.com/ontio/ontio-wasm-build +contractdir="testwasmdata" # build rust wasm contracts -mkdir -p testwasmdata +mkdir -p $contractdir cd contracts-rust && bash travis.build.sh && cd ../ cd contracts-cplus && bash travis.build.bash && cd ../ # verify and optimize wasm contract -for wasm in testwasmdata/*.wasm ; do +for wasm in $contractdir/*.wasm ; do ontio-wasm-build $wasm $wasm done diff --git a/wasmtest/wasm-test.go b/wasmtest/wasm-test.go index 34646edfaa..ad1f49d556 100644 --- a/wasmtest/wasm-test.go +++ b/wasmtest/wasm-test.go @@ -19,446 +19,33 @@ package main import ( - "bytes" - "encoding/hex" - "encoding/json" - "fmt" - "io/ioutil" - "os" - "path" - "path/filepath" - "reflect" - "runtime" - "strings" - - "github.com/go-interpreter/wagon/exec" - "github.com/go-interpreter/wagon/wasm" - "github.com/ontio/ontology-crypto/keypair" - "github.com/ontio/ontology/account" - "github.com/ontio/ontology/cmd/utils" - "github.com/ontio/ontology/common" - "github.com/ontio/ontology/common/config" - "github.com/ontio/ontology/common/constants" "github.com/ontio/ontology/common/log" - "github.com/ontio/ontology/core/genesis" - "github.com/ontio/ontology/core/ledger" - "github.com/ontio/ontology/core/payload" - "github.com/ontio/ontology/core/signature" - "github.com/ontio/ontology/core/types" - utils2 "github.com/ontio/ontology/core/utils" - "github.com/ontio/ontology/events" - common2 "github.com/ontio/ontology/http/base/common" - "github.com/ontio/ontology/smartcontract/service/wasmvm" - "github.com/ontio/ontology/smartcontract/states" - vmtypes "github.com/ontio/ontology/vm/neovm/types" - common3 "github.com/ontio/ontology/wasmtest/common" + "github.com/ontio/ontology/wasmtest/common" ) -const contractDir = "testwasmdata" -const testcaseMethod = "testcase" - -func NewDeployWasmContract(signer *account.Account, code []byte) (*types.Transaction, error) { - mutable, err := utils.NewDeployCodeTransaction(0, 100000000, code, payload.WASMVM_TYPE, "name", "version", - "author", "email", "desc") - if err != nil { - return nil, err - } - err = utils.SignTransaction(signer, mutable) - if err != nil { - return nil, err - } - tx, err := mutable.IntoImmutable() - return tx, err -} - -func NewDeployNeoContract(signer *account.Account, code []byte) (*types.Transaction, error) { - mutable, err := utils.NewDeployCodeTransaction(0, 100000000, code, payload.NEOVM_TYPE, "name", "version", - "author", "email", "desc") - if err != nil { - return nil, err - } - err = utils.SignTransaction(signer, mutable) - if err != nil { - return nil, err - } - tx, err := mutable.IntoImmutable() - return tx, err -} - -func GenNeoTextCaseTransaction(contract common.Address, database *ledger.Ledger) [][]common3.TestCase { - params := make([]interface{}, 0) - method := string("testcase") - // neovm entry api is def Main(method, args). and testcase method api need no other args, so pass a random args to entry api. - operation := 1 - params = append(params, method) - params = append(params, operation) - tx, err := common2.NewNeovmInvokeTransaction(0, 100000000, contract, params) - imt, err := tx.IntoImmutable() - if err != nil { - panic(err) - } - res, err := database.PreExecuteContract(imt) - if err != nil { - panic(err) - } +const ( + testdir = "testwasmdata" +) - ret := res.Result.(string) - jsonCase, err := common.HexToBytes(ret) +func main() { + acct, database := common.InitOntologyLedger() + log.Info("loading contract") + contract, objIsDir, err := common.GetContact(testdir) if err != nil { panic(err) } - if len(jsonCase) == 0 { - panic("failed to get testcase data from contract") - } - var testCase [][]common3.TestCase - err = json.Unmarshal([]byte(jsonCase), &testCase) - if err != nil { - panic("failed Unmarshal") - } - return testCase -} - -func ExactTestCase(code []byte) [][]common3.TestCase { - m, err := wasm.ReadModule(bytes.NewReader(code), func(name string) (*wasm.Module, error) { - switch name { - case "env": - return wasmvm.NewHostModule(), nil - } - return nil, fmt.Errorf("module %q unknown", name) - }) - checkErr(err) - - compiled, err := exec.CompileModule(m) - checkErr(err) - - vm, err := exec.NewVMWithCompiled(compiled, 10*1024*1024) - checkErr(err) - - param := common.NewZeroCopySink(nil) - param.WriteString(testcaseMethod) - host := &wasmvm.Runtime{Input: param.Bytes()} - vm.HostData = host - vm.RecoverPanic = true - envGasLimit := uint64(100000000000000) - envExecStep := uint64(100000000000000) - vm.AvaliableGas = &exec.Gas{GasLimit: &envGasLimit, GasPrice: 0, GasFactor: 5, ExecStep: &envExecStep} - vm.CallStackDepth = 1024 - - entry := compiled.RawModule.Export.Entries["invoke"] - index := int64(entry.Index) - _, err = vm.ExecCode(index) - checkErr(err) - - var testCase [][]common3.TestCase - source := common.NewZeroCopySource(host.Output) - jsonCase, _, _, _ := source.NextString() - - if len(jsonCase) == 0 { - panic("failed to get testcase data from contract") + if !objIsDir { + panic("testwasmdata is not a dir") } - err = json.Unmarshal([]byte(jsonCase), &testCase) - checkErr(err) - - return testCase -} - -func LoadContracts(dir string) (map[string][]byte, error) { - contracts := make(map[string][]byte) - fnames, err := filepath.Glob(filepath.Join(dir, "*")) - if err != nil { - return nil, err - } - for _, name := range fnames { - if !(strings.HasSuffix(name, ".wasm") || strings.HasSuffix(name, ".avm")) { - continue - } - raw, err := ioutil.ReadFile(name) - if err != nil { - return nil, err - } - contracts[path.Base(name)] = raw - } - - return contracts, nil -} - -func init() { - log.InitLog(log.InfoLog, log.PATH, log.Stdout) - runtime.GOMAXPROCS(4) -} - -func checkErr(err error) { + log.Infof("deploying %d contracts", len(contract)) + err = common.DeployContract(acct, database, contract) if err != nil { panic(err) } -} - -func execTxCheckRes(tx *types.Transaction, testCase common3.TestCase, database *ledger.Ledger, addr common.Address, acct *account.Account) { - res, err := database.PreExecuteContract(tx) - log.Infof("testcase consume gas: %d", res.Gas) - checkErr(err) - - height := database.GetCurrentBlockHeight() - header, err := database.GetHeaderByHeight(height) - checkErr(err) - blockTime := header.Timestamp + 1 - execEnv := ExecEnv{Time: blockTime, Height: height + 1, Tx: tx, BlockHash: header.Hash(), Contract: addr} - checkExecResult(testCase, res, execEnv) + testContext := common.MakeTestContext(acct, contract) - block, _ := makeBlock(acct, []*types.Transaction{tx}) - err = database.AddBlock(block, common.UINT256_EMPTY) - checkErr(err) -} - -func main() { - datadir := "testdata" - err := os.RemoveAll(datadir) - defer func() { - _ = os.RemoveAll(datadir) - _ = os.RemoveAll(log.PATH) - }() - checkErr(err) - log.Trace("Node version: ", config.Version) - - acct := account.NewAccount("") - buf := keypair.SerializePublicKey(acct.PublicKey) - config.DefConfig.Genesis.ConsensusType = "solo" - config.DefConfig.Genesis.SOLO.GenBlockTime = 3 - config.DefConfig.Genesis.SOLO.Bookkeepers = []string{hex.EncodeToString(buf)} - config.DefConfig.P2PNode.NetworkId = 0 - - bookkeepers := []keypair.PublicKey{acct.PublicKey} - //Init event hub - events.Init() - - log.Info("1. Loading the Ledger") - database, err := ledger.NewLedger(datadir, 1000000) - checkErr(err) - ledger.DefLedger = database - genblock, err := genesis.BuildGenesisBlock(bookkeepers, config.DefConfig.Genesis) - checkErr(err) - err = database.Init(bookkeepers, genblock) - checkErr(err) - - log.Info("loading wasm contract") - contract, err := LoadContracts(contractDir) - checkErr(err) - - log.Infof("deploying %d wasm contracts", len(contract)) - txes := make([]*types.Transaction, 0, len(contract)) - for file, cont := range contract { - var tx *types.Transaction - var err error - if strings.HasSuffix(file, ".wasm") { - tx, err = NewDeployWasmContract(acct, cont) - } else if strings.HasSuffix(file, ".avm") { - tx, err = NewDeployNeoContract(acct, cont) - } - - checkErr(err) - - res, err := database.PreExecuteContract(tx) - log.Infof("deploy %s consume gas: %d", file, res.Gas) - checkErr(err) - txes = append(txes, tx) - } - - block, _ := makeBlock(acct, txes) - err = database.AddBlock(block, common.UINT256_EMPTY) - checkErr(err) - - addrMap := make(map[string]common.Address) - for file, code := range contract { - addrMap[path.Base(file)] = common.AddressFromVmCode(code) - } - - testContext := common3.TestContext{ - Admin: acct.Address, - AddrMap: addrMap, - } - - for file, cont := range contract { - log.Infof("exacting testcase from %s", file) - addr := common.AddressFromVmCode(cont) - if strings.HasSuffix(file, ".avm") { - testCases := GenNeoTextCaseTransaction(addr, database) - for _, testCase := range testCases[0] { // only handle group 0 currently - val, _ := json.Marshal(testCase) - log.Info("executing testcase: ", string(val)) - tx, err := common3.GenNeoVMTransaction(testCase, addr, &testContext) - checkErr(err) - - execTxCheckRes(tx, testCase, database, addr, acct) - } - } else if strings.HasSuffix(file, ".wasm") { - testCases := ExactTestCase(cont) - for _, testCase := range testCases[0] { // only handle group 0 currently - val, _ := json.Marshal(testCase) - log.Info("executing testcase: ", string(val)) - tx, err := common3.GenWasmTransaction(testCase, addr, &testContext) - checkErr(err) - - execTxCheckRes(tx, testCase, database, addr, acct) - } - } - } - - log.Info("contract test succeed") -} - -type ExecEnv struct { - Contract common.Address - Time uint32 - Height uint32 - Tx *types.Transaction - BlockHash common.Uint256 -} - -func checkExecResult(testCase common3.TestCase, result *states.PreExecResult, execEnv ExecEnv) { - assertEq(result.State, byte(1)) - ret := result.Result.(string) - switch testCase.Method { - case "timestamp": - sink := common.NewZeroCopySink(nil) - sink.WriteUint64(uint64(execEnv.Time)) - assertEq(ret, hex.EncodeToString(sink.Bytes())) - case "block_height": - sink := common.NewZeroCopySink(nil) - sink.WriteUint32(uint32(execEnv.Height)) - assertEq(ret, hex.EncodeToString(sink.Bytes())) - case "self_address", "entry_address": - assertEq(ret, hex.EncodeToString(execEnv.Contract[:])) - case "caller_address": - assertEq(ret, hex.EncodeToString(common.ADDRESS_EMPTY[:])) - case "current_txhash": - hash := execEnv.Tx.Hash() - assertEq(ret, hex.EncodeToString(hash[:])) - case "current_blockhash": - assertEq(ret, hex.EncodeToString(execEnv.BlockHash[:])) - //case "sha256": - // let data :&[u8]= source.read().unwrap(); - // sink.write(runtime::sha256(&data)) - //} - default: - if len(testCase.Expect) != 0 { - expect, err := utils.ParseParams(testCase.Expect) - checkErr(err) - if execEnv.Tx.TxType == types.InvokeNeo { - val := buildNeoVmValueFromExpect(expect) - cv, err := val.ConvertNeoVmValueHexString() - checkErr(err) - assertEq(cv, result.Result) - } else if execEnv.Tx.TxType == types.InvokeWasm { - exp, err := utils2.BuildWasmContractParam(expect) - checkErr(err) - assertEq(ret, hex.EncodeToString(exp)) - } else { - panic("error tx type") - } - } - if len(testCase.Notify) != 0 { - js, _ := json.Marshal(result.Notify) - assertEq(true, strings.Contains(string(js), testCase.Notify)) - } - } -} - -func buildNeoVmValueFromExpect(expectlist []interface{}) *vmtypes.VmValue { - if len(expectlist) > 1 { - panic("only support return one value") - } - expect := expectlist[0] - - switch expect.(type) { - case string: - val, err := vmtypes.VmValueFromBytes([]byte(expect.(string))) - if err != nil { - panic(err) - } - return &val - case []byte: - val, err := vmtypes.VmValueFromBytes(expect.([]byte)) - if err != nil { - panic(err) - } - return &val - case int64: - val := vmtypes.VmValueFromInt64(expect.(int64)) - return &val - case bool: - val := vmtypes.VmValueFromBool(expect.(bool)) - return &val - case common.Address: - addr := expect.(common.Address) - val, err := vmtypes.VmValueFromBytes(addr[:]) - if err != nil { - panic(err) - } - return &val - default: - fmt.Printf("unspport param type %s", reflect.TypeOf(expect)) - panic("unspport param type") - } -} - -func GenAccounts(num int) []*account.Account { - var accounts []*account.Account - for i := 0; i < num; i++ { - acc := account.NewAccount("") - accounts = append(accounts, acc) - } - return accounts -} - -func makeBlock(acc *account.Account, txs []*types.Transaction) (*types.Block, error) { - nextBookkeeper, err := types.AddressFromBookkeepers([]keypair.PublicKey{acc.PublicKey}) - if err != nil { - return nil, fmt.Errorf("GetBookkeeperAddress error:%s", err) - } - prevHash := ledger.DefLedger.GetCurrentBlockHash() - height := ledger.DefLedger.GetCurrentBlockHeight() - - nonce := uint64(height) - var txHash []common.Uint256 - for _, t := range txs { - txHash = append(txHash, t.Hash()) - } - - txRoot := common.ComputeMerkleRoot(txHash) - - blockRoot := ledger.DefLedger.GetBlockRootWithNewTxRoots(height+1, []common.Uint256{txRoot}) - header := &types.Header{ - Version: 0, - PrevBlockHash: prevHash, - TransactionsRoot: txRoot, - BlockRoot: blockRoot, - Timestamp: constants.GENESIS_BLOCK_TIMESTAMP + height + 1, - Height: height + 1, - ConsensusData: nonce, - NextBookkeeper: nextBookkeeper, - } - block := &types.Block{ - Header: header, - Transactions: txs, - } - - blockHash := block.Hash() - - sig, err := signature.Sign(acc, blockHash[:]) - if err != nil { - return nil, fmt.Errorf("signature, Sign error:%s", err) - } - - block.Header.Bookkeepers = []keypair.PublicKey{acc.PublicKey} - block.Header.SigData = [][]byte{sig} - return block, nil -} - -func assertEq(a interface{}, b interface{}) { - if reflect.DeepEqual(a, b) == false { - panic(fmt.Sprintf("not equal: a= %v, b=%v", a, b)) - } + common.TestWithbatchMode(acct, database, contract, testContext) } From 9d44d1617e6938be1291407568440e04f87238fa Mon Sep 17 00:00:00 2001 From: steven Date: Fri, 18 Oct 2019 18:13:59 +0800 Subject: [PATCH 2/6] add example config and testcase --- localtool/config.con | 28 +++++++++++++ localtool/test_native.cpp | 83 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 localtool/config.con create mode 100644 localtool/test_native.cpp diff --git a/localtool/config.con b/localtool/config.con new file mode 100644 index 0000000000..4198abcaba --- /dev/null +++ b/localtool/config.con @@ -0,0 +1,28 @@ +{ + "testconfig":[ + { + "contract" : "a.wasm", + "balanceaddr": [ + { + "address":"Ad4pjz2bqep4RhQrUAzMuZJkBC3qJ1tZuT", + "balance":30 + }, + { + "address":"Ab1z3Sxy7ovn4AuScdmMh4PRMvcwCMzSNV", + "balance":20 + } + ], + "testcase": [ + [{"method":"balanceOf", "param":"address:Ad4pjz2bqep4RhQrUAzMuZJkBC3qJ1tZuT"}, + {"method":"balanceOf", "param":"address:Ab1z3Sxy7ovn4AuScdmMh4PRMvcwCMzSNV"}, + {"needcontext":true, "method":"setadmin"}, + {"env":{"witness":["Ad4pjz2bqep4RhQrUAzMuZJkBC3qJ1tZuT","Ab1z3Sxy7ovn4AuScdmMh4PRMvcwCMzSNV"]}, "method":"transfertoowner", "param":"address:Ab1z3Sxy7ovn4AuScdmMh4PRMvcwCMzSNV,int:20"}, + {"env":{"witness":["Ad4pjz2bqep4RhQrUAzMuZJkBC3qJ1tZuT","Ab1z3Sxy7ovn4AuScdmMh4PRMvcwCMzSNV"]}, "method":"transfertoowner", "param":"address:Ad4pjz2bqep4RhQrUAzMuZJkBC3qJ1tZuT,int:30"}, + {"needcontext":true, "method":"test_native_ont", "param":"string:balanceOf,address:Ad4pjz2bqep4RhQrUAzMuZJkBC3qJ1tZuT,address:Ab1z3Sxy7ovn4AuScdmMh4PRMvcwCMzSNV,int:1000", "expected":"int:1"}, + {"env":{"witness":["Ad4pjz2bqep4RhQrUAzMuZJkBC3qJ1tZuT","Ab1z3Sxy7ovn4AuScdmMh4PRMvcwCMzSNV"]}, "needcontext":true, "method":"test_native_ont", "param":"string:transfer,address:Ad4pjz2bqep4RhQrUAzMuZJkBC3qJ1tZuT,address:Ab1z3Sxy7ovn4AuScdmMh4PRMvcwCMzSNV,int:1000", "expected":"int:1"}, + {"env":{"witness":["Ad4pjz2bqep4RhQrUAzMuZJkBC3qJ1tZuT","Ab1z3Sxy7ovn4AuScdmMh4PRMvcwCMzSNV"]}, "needcontext":true, "method":"test_native_ont", "param":"string:approve,address:Ad4pjz2bqep4RhQrUAzMuZJkBC3qJ1tZuT,address:Ab1z3Sxy7ovn4AuScdmMh4PRMvcwCMzSNV,int:1000", "expected":"int:1"} + ] + ] + } + ] +} diff --git a/localtool/test_native.cpp b/localtool/test_native.cpp new file mode 100644 index 0000000000..a028dc17cd --- /dev/null +++ b/localtool/test_native.cpp @@ -0,0 +1,83 @@ +#include +using std::string; +using std::vector; + +namespace ontio { + struct test_conext { + address admin; + std::map addrmap; + ONTLIB_SERIALIZE( test_conext, (admin) (addrmap)) + }; +}; + +using namespace ontio; + +class hello: public contract { + address owner; + key kn = make_key(string("owner")); + public: + using contract::contract; + uint128_t test_native_ont(string &method, address &from, address &to, asset &amount, test_conext &tc) { + if (method == "balanceOf") { + asset balance = ont::balanceof(tc.admin); + check(balance == 1000000000, "init balance wrong"); + } else if (method == "transfer") { + /*keep admin alway initbalance.*/ + check(ont::transfer(tc.admin, to, amount), "transfer failed"); + check(ont::balanceof(to) == amount, "transfer amount wrong"); + check(ont::transfer(to, tc.admin, amount), "transfer failed"); + check(ont::balanceof(to) == 0, "transfer amount wrong"); + } else if (method == "approve") { + /*keep admin alway initbalance.*/ + check(ont::approve(tc.admin, from, amount),"approve failed"); + check(ont::allowance(tc.admin, from) == amount, "allowance amount wrong"); + check(ont::transferfrom(from, tc.admin, to, amount),"transferfrom failed"); + check(ont::allowance(tc.admin, from) == 0, "allowance amount wrong"); + check(ont::balanceof(to) == amount, "transfer amount wrong"); + check(ont::transfer(to, tc.admin, amount), "transfer failed"); + check(ont::balanceof(to) == 0, "transfer amount wrong"); + check(ont::balanceof(from) == 0, "transfer amount wrong"); + } + + return 1; + } + + int128_t balanceOf(address &from) { + asset balance = ont::balanceof(from); + int64_t t = int64_t(balance.amount); + printf("balanceOf is : %lld", t); + return balance.amount; + } + + int128_t testranfer(address& from, address &to, asset &amount) { + check(ont::transfer(from, to, amount), "transfer failed"); + return 1; + } + + int128_t transfertoowner(address &from, asset &amount) { + check(storage_get(kn, owner), "get owner key failed"); + check(ont::transfer(from, owner, amount), "transfer failed"); + return 1; + } + + int128_t setadmin(test_conext &tc) { + storage_put(kn, tc.admin); + check(storage_get(kn, owner), "get owner key failed"); + check(owner == tc.admin, "storage failed"); + return 1; + } + + string testcase(void) { + return string(R"( + [ + [{"needcontext":true, "method":"test_native_ont", "param":"string:balanceOf,address:Ad4pjz2bqep4RhQrUAzMuZJkBC3qJ1tZuT,address:Ab1z3Sxy7ovn4AuScdmMh4PRMvcwCMzSNV,int:1000", "expected":"int:1"}, + {"env":{"witness":["Ad4pjz2bqep4RhQrUAzMuZJkBC3qJ1tZuT","Ab1z3Sxy7ovn4AuScdmMh4PRMvcwCMzSNV"]}, "needcontext":true, "method":"test_native_ont", "param":"string:transfer,address:Ad4pjz2bqep4RhQrUAzMuZJkBC3qJ1tZuT,address:Ab1z3Sxy7ovn4AuScdmMh4PRMvcwCMzSNV,int:1000", "expected":"int:1"}, + {"env":{"witness":["Ad4pjz2bqep4RhQrUAzMuZJkBC3qJ1tZuT","Ab1z3Sxy7ovn4AuScdmMh4PRMvcwCMzSNV"]}, "needcontext":true, "method":"test_native_ont", "param":"string:approve,address:Ad4pjz2bqep4RhQrUAzMuZJkBC3qJ1tZuT,address:Ab1z3Sxy7ovn4AuScdmMh4PRMvcwCMzSNV,int:1000", "expected":"int:1"} + ] + ] + )"); + } + +}; + +ONTIO_DISPATCH( hello,(testcase)(test_native_ont)(balanceOf)(testranfer)(transfertoowner)(setadmin)) From 42c0b8e81b3d76c183dbc7a0166f380fad1cfa8e Mon Sep 17 00:00:00 2001 From: steven Date: Tue, 22 Oct 2019 17:07:15 +0800 Subject: [PATCH 3/6] add native test --- smartcontract/test/nativetest/suite_test.go | 173 ++++++++++++++++++++ wasmtest/common/testframe.go | 10 +- 2 files changed, 178 insertions(+), 5 deletions(-) create mode 100644 smartcontract/test/nativetest/suite_test.go diff --git a/smartcontract/test/nativetest/suite_test.go b/smartcontract/test/nativetest/suite_test.go new file mode 100644 index 0000000000..eadc82901d --- /dev/null +++ b/smartcontract/test/nativetest/suite_test.go @@ -0,0 +1,173 @@ +package test + +import ( + "encoding/json" + "math" + "testing" + "time" + + "github.com/ontio/ontology/common" + "github.com/ontio/ontology/common/log" + "github.com/ontio/ontology/core/payload" + commons "github.com/ontio/ontology/core/store/common" + "github.com/ontio/ontology/core/store/overlaydb" + "github.com/ontio/ontology/core/types" + "github.com/ontio/ontology/errors" + common2 "github.com/ontio/ontology/http/base/common" + "github.com/ontio/ontology/smartcontract" + "github.com/ontio/ontology/smartcontract/event" + "github.com/ontio/ontology/smartcontract/service/native" + "github.com/ontio/ontology/smartcontract/service/native/ont" + utils3 "github.com/ontio/ontology/smartcontract/service/native/utils" + "github.com/ontio/ontology/smartcontract/service/neovm" + sstate "github.com/ontio/ontology/smartcontract/states" + "github.com/ontio/ontology/smartcontract/storage" + types2 "github.com/ontio/ontology/vm/neovm/types" + //"github.com/stretchr/testify/assert" +) + +func TestNativeContract(t *testing.T) { + InstallNativeContract(utils3.OntContractAddress, map[string]native.Handler{ + "OntInitTest": OntInitTest, + "transfer": ont.OntTransfer, + }) + BuildNativeTxsAndTest(t) +} + +func BuildNativeTxsAndTest(t *testing.T) { + var sts []*ont.State + from, _ := common.AddressFromBase58(string("Ad4pjz2bqep4RhQrUAzMuZJkBC3qJ1tZuT")) + to, _ := common.AddressFromBase58(string("Ab1z3Sxy7ovn4AuScdmMh4PRMvcwCMzSNV")) + sts = append(sts, &ont.State{ + From: from, + To: to, + Value: 30, + }) + + cache := NewNativeTxTest(nil, utils3.OntContractAddress, from, "OntInitTest", []interface{}{""}) + NewNativeTxTest(cache, utils3.OntContractAddress, from, ont.TRANSFER_NAME, []interface{}{sts}) +} + +func OntInitTest(native *native.NativeService) ([]byte, error) { + from, _ := common.AddressFromBase58(string("Ad4pjz2bqep4RhQrUAzMuZJkBC3qJ1tZuT")) + val := uint64(10000) + balanceKey := ont.GenBalanceKey(utils3.OntContractAddress, from) + item := utils3.GenUInt64StorageItem(val) + native.CacheDB.Put(balanceKey, item.ToArray()) + ont.AddNotifications(native, utils3.OntContractAddress, &ont.State{To: from, Value: val}) + + return utils3.BYTE_TRUE, nil +} + +func NewNativeTxTest(cache *storage.CacheDB, contractAddress common.Address, sigaddr common.Address, method string, params []interface{}) *storage.CacheDB { + mutable, err := common2.NewNativeInvokeTransaction(0, 2000000000, contractAddress, 0, method, params) + if err != nil { + panic(err) + } + + tx, err := mutable.IntoImmutable() + if err != nil { + panic(err) + } + tx.SignedAddr = append(tx.SignedAddr, sigaddr) + + if cache == nil { + overlay := NewOverlayDB() + cache = storage.NewCacheDB(overlay) + } + + res, err := executeTransaction(tx, cache) + if err != nil { + panic(err) + } + + if res.State == 0 { + panic("execute error") + } + + js, _ := json.Marshal(res.Notify) + log.Infof("Notify info : %s", string(js)) + return cache +} + +func executeTransaction(tx *types.Transaction, cache *storage.CacheDB) (*sstate.PreExecResult, error) { + stf := &sstate.PreExecResult{State: event.CONTRACT_STATE_FAIL, Result: nil} + sconfig := &smartcontract.Config{ + Time: uint32(time.Now().Unix()), + Height: 7000000, + Tx: tx, + } + + gasTable := make(map[string]uint64) + neovm.GAS_TABLE.Range(func(k, value interface{}) bool { + key := k.(string) + val := value.(uint64) + gasTable[key] = val + + return true + }) + if tx.TxType == types.InvokeNeo { + invoke := tx.Payload.(*payload.InvokeCode) + sc := smartcontract.SmartContract{ + Config: sconfig, + CacheDB: cache, + GasTable: gasTable, + Gas: math.MaxUint64, + PreExec: true, + } + engine, _ := sc.NewExecuteEngine(invoke.Code, tx.TxType) + result, err := engine.Invoke() + if err != nil { + return stf, err + } + var cv interface{} + if tx.TxType == types.InvokeNeo { //neovm + if result != nil { + val := result.(*types2.VmValue) + cv, err = val.ConvertNeoVmValueHexString() + if err != nil { + return stf, err + } + } + } else { //wasmvm + cv = common.ToHexString(result.([]byte)) + } + return &sstate.PreExecResult{State: event.CONTRACT_STATE_SUCCESS, Result: cv, Notify: sc.Notifications}, nil + } + + return stf, errors.NewErr("wrong tx type") +} + +func InstallNativeContract(addr common.Address, actions map[string]native.Handler) { + contract := func(native *native.NativeService) { + for name, fun := range actions { + native.Register(name, fun) + } + } + native.Contracts[addr] = contract +} + +type MockDB struct { + commons.PersistStore + db map[string]string +} + +func (self *MockDB) Get(key []byte) ([]byte, error) { + val, ok := self.db[string(key)] + if ok == false { + return nil, commons.ErrNotFound + } + return []byte(val), nil +} + +func (self *MockDB) BatchPut(key []byte, value []byte) { + self.db[string(key)] = string(value) +} + +func (self *MockDB) BatchDelete(key []byte) { + delete(self.db, string(key)) +} + +func NewOverlayDB() *overlaydb.OverlayDB { + return overlaydb.NewOverlayDB(&MockDB{nil, make(map[string]string)}) +} diff --git a/wasmtest/common/testframe.go b/wasmtest/common/testframe.go index c3fb57d8ee..922722adcf 100644 --- a/wasmtest/common/testframe.go +++ b/wasmtest/common/testframe.go @@ -128,7 +128,7 @@ func InitBalanceAddress(balanceAddr []BalanceAddr, acct *account.Account, databa tx.SignedAddr = append(tx.SignedAddr, acct.Address) - block, _ := makeBlock(acct, []*types.Transaction{tx}) + block, _ := MakeBlock(acct, []*types.Transaction{tx}) err = database.AddBlock(block, common.UINT256_EMPTY) checkErr(err) event, err := database.GetEventNotifyByTx(tx.Hash()) @@ -233,7 +233,7 @@ func buildNeoVmValueFromExpect(expectlist []interface{}) *vmtypes.VmValue { } } -func makeBlock(acc *account.Account, txs []*types.Transaction) (*types.Block, error) { +func MakeBlock(acc *account.Account, txs []*types.Transaction) (*types.Block, error) { nextBookkeeper, err := types.AddressFromBookkeepers([]keypair.PublicKey{acc.PublicKey}) if err != nil { return nil, fmt.Errorf("GetBookkeeperAddress error:%s", err) @@ -311,7 +311,7 @@ func ExecTxCheckRes(tx *types.Transaction, testCase TestCase, database *ledger.L execEnv := ExecEnv{Time: blockTime, Height: height + 1, Tx: tx, BlockHash: header.Hash(), Contract: addr} checkExecResult(testCase, res, execEnv) - block, _ := makeBlock(acct, []*types.Transaction{tx}) + block, _ := MakeBlock(acct, []*types.Transaction{tx}) err = database.AddBlock(block, common.UINT256_EMPTY) checkErr(err) return nil @@ -371,7 +371,7 @@ func DeployContract(acct *account.Account, database *ledger.Ledger, contract map txes = append(txes, tx) } - block, err := makeBlock(acct, txes) + block, err := MakeBlock(acct, txes) checkErr(err) err = database.AddBlock(block, common.UINT256_EMPTY) checkErr(err) @@ -431,7 +431,7 @@ func InvokeSpecifiedContract(acct *account.Account, database *ledger.Ledger, con if err != nil { return err } - block, err := makeBlock(acct, []*types.Transaction{tx}) + block, err := MakeBlock(acct, []*types.Transaction{tx}) checkErr(err) err = database.AddBlock(block, common.UINT256_EMPTY) checkErr(err) From 7e54f5cc767adb977fa74b0b3434b9a2ff89b288 Mon Sep 17 00:00:00 2001 From: steven Date: Thu, 24 Oct 2019 17:49:39 +0800 Subject: [PATCH 4/6] fixed wasm-test do not panic when preexe error --- wasmtest/common/testframe.go | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/wasmtest/common/testframe.go b/wasmtest/common/testframe.go index 922722adcf..6822abe9ca 100644 --- a/wasmtest/common/testframe.go +++ b/wasmtest/common/testframe.go @@ -106,7 +106,6 @@ type BalanceAddr struct { } func InitBalanceAddress(balanceAddr []BalanceAddr, acct *account.Account, database *ledger.Ledger) error { - fmt.Printf("========%v\n", balanceAddr) for _, elt := range balanceAddr { to, err := common.AddressFromBase58(elt.Address) if err != nil { @@ -188,8 +187,19 @@ func checkExecResult(testCase TestCase, result *states.PreExecResult, execEnv Ex panic("error tx type") } } + + var js []byte + if len(result.Notify) != 0 { + js, _ = json.Marshal(result.Notify) + log.Infof("Notify info : %s", string(js)) + } + + if result.Result != nil { + jsres, _ := json.Marshal(result.Result) + log.Infof("Return result: %s", string(jsres)) + } + if len(testCase.Notify) != 0 { - js, _ := json.Marshal(result.Notify) assertEq(true, strings.Contains(string(js), testCase.Notify)) } } @@ -536,12 +546,18 @@ func testSpecifiedContractWithbatchMode(acct *account.Account, database *ledger. if strings.HasSuffix(file, ".avm") || strings.HasSuffix(file, ".avm.str") { testCases := GenNeoTextCaseTransaction(addr, database) for _, testCase := range testCases[0] { // only handle group 0 currently - TestWithConfigElt(acct, database, file, testCase, testContext) + err := TestWithConfigElt(acct, database, file, testCase, testContext) + if err != nil { + panic(err) + } } } else if strings.HasSuffix(file, ".wasm") || strings.HasSuffix(file, ".wasm.str") { testCases := ExactTestCase(cont) for _, testCase := range testCases[0] { // only handle group 0 currently - TestWithConfigElt(acct, database, file, testCase, testContext) + err := TestWithConfigElt(acct, database, file, testCase, testContext) + if err != nil { + panic(err) + } } } else { panic("testSpecifiedContractWithbatchMode: error suffix contract name") From fb34b2f3eabe6b90dafe30b0f2f63c7528440a02 Mon Sep 17 00:00:00 2001 From: steven Date: Thu, 24 Oct 2019 17:51:56 +0800 Subject: [PATCH 5/6] remove suite_test --- smartcontract/test/nativetest/suite_test.go | 173 -------------------- 1 file changed, 173 deletions(-) delete mode 100644 smartcontract/test/nativetest/suite_test.go diff --git a/smartcontract/test/nativetest/suite_test.go b/smartcontract/test/nativetest/suite_test.go deleted file mode 100644 index eadc82901d..0000000000 --- a/smartcontract/test/nativetest/suite_test.go +++ /dev/null @@ -1,173 +0,0 @@ -package test - -import ( - "encoding/json" - "math" - "testing" - "time" - - "github.com/ontio/ontology/common" - "github.com/ontio/ontology/common/log" - "github.com/ontio/ontology/core/payload" - commons "github.com/ontio/ontology/core/store/common" - "github.com/ontio/ontology/core/store/overlaydb" - "github.com/ontio/ontology/core/types" - "github.com/ontio/ontology/errors" - common2 "github.com/ontio/ontology/http/base/common" - "github.com/ontio/ontology/smartcontract" - "github.com/ontio/ontology/smartcontract/event" - "github.com/ontio/ontology/smartcontract/service/native" - "github.com/ontio/ontology/smartcontract/service/native/ont" - utils3 "github.com/ontio/ontology/smartcontract/service/native/utils" - "github.com/ontio/ontology/smartcontract/service/neovm" - sstate "github.com/ontio/ontology/smartcontract/states" - "github.com/ontio/ontology/smartcontract/storage" - types2 "github.com/ontio/ontology/vm/neovm/types" - //"github.com/stretchr/testify/assert" -) - -func TestNativeContract(t *testing.T) { - InstallNativeContract(utils3.OntContractAddress, map[string]native.Handler{ - "OntInitTest": OntInitTest, - "transfer": ont.OntTransfer, - }) - BuildNativeTxsAndTest(t) -} - -func BuildNativeTxsAndTest(t *testing.T) { - var sts []*ont.State - from, _ := common.AddressFromBase58(string("Ad4pjz2bqep4RhQrUAzMuZJkBC3qJ1tZuT")) - to, _ := common.AddressFromBase58(string("Ab1z3Sxy7ovn4AuScdmMh4PRMvcwCMzSNV")) - sts = append(sts, &ont.State{ - From: from, - To: to, - Value: 30, - }) - - cache := NewNativeTxTest(nil, utils3.OntContractAddress, from, "OntInitTest", []interface{}{""}) - NewNativeTxTest(cache, utils3.OntContractAddress, from, ont.TRANSFER_NAME, []interface{}{sts}) -} - -func OntInitTest(native *native.NativeService) ([]byte, error) { - from, _ := common.AddressFromBase58(string("Ad4pjz2bqep4RhQrUAzMuZJkBC3qJ1tZuT")) - val := uint64(10000) - balanceKey := ont.GenBalanceKey(utils3.OntContractAddress, from) - item := utils3.GenUInt64StorageItem(val) - native.CacheDB.Put(balanceKey, item.ToArray()) - ont.AddNotifications(native, utils3.OntContractAddress, &ont.State{To: from, Value: val}) - - return utils3.BYTE_TRUE, nil -} - -func NewNativeTxTest(cache *storage.CacheDB, contractAddress common.Address, sigaddr common.Address, method string, params []interface{}) *storage.CacheDB { - mutable, err := common2.NewNativeInvokeTransaction(0, 2000000000, contractAddress, 0, method, params) - if err != nil { - panic(err) - } - - tx, err := mutable.IntoImmutable() - if err != nil { - panic(err) - } - tx.SignedAddr = append(tx.SignedAddr, sigaddr) - - if cache == nil { - overlay := NewOverlayDB() - cache = storage.NewCacheDB(overlay) - } - - res, err := executeTransaction(tx, cache) - if err != nil { - panic(err) - } - - if res.State == 0 { - panic("execute error") - } - - js, _ := json.Marshal(res.Notify) - log.Infof("Notify info : %s", string(js)) - return cache -} - -func executeTransaction(tx *types.Transaction, cache *storage.CacheDB) (*sstate.PreExecResult, error) { - stf := &sstate.PreExecResult{State: event.CONTRACT_STATE_FAIL, Result: nil} - sconfig := &smartcontract.Config{ - Time: uint32(time.Now().Unix()), - Height: 7000000, - Tx: tx, - } - - gasTable := make(map[string]uint64) - neovm.GAS_TABLE.Range(func(k, value interface{}) bool { - key := k.(string) - val := value.(uint64) - gasTable[key] = val - - return true - }) - if tx.TxType == types.InvokeNeo { - invoke := tx.Payload.(*payload.InvokeCode) - sc := smartcontract.SmartContract{ - Config: sconfig, - CacheDB: cache, - GasTable: gasTable, - Gas: math.MaxUint64, - PreExec: true, - } - engine, _ := sc.NewExecuteEngine(invoke.Code, tx.TxType) - result, err := engine.Invoke() - if err != nil { - return stf, err - } - var cv interface{} - if tx.TxType == types.InvokeNeo { //neovm - if result != nil { - val := result.(*types2.VmValue) - cv, err = val.ConvertNeoVmValueHexString() - if err != nil { - return stf, err - } - } - } else { //wasmvm - cv = common.ToHexString(result.([]byte)) - } - return &sstate.PreExecResult{State: event.CONTRACT_STATE_SUCCESS, Result: cv, Notify: sc.Notifications}, nil - } - - return stf, errors.NewErr("wrong tx type") -} - -func InstallNativeContract(addr common.Address, actions map[string]native.Handler) { - contract := func(native *native.NativeService) { - for name, fun := range actions { - native.Register(name, fun) - } - } - native.Contracts[addr] = contract -} - -type MockDB struct { - commons.PersistStore - db map[string]string -} - -func (self *MockDB) Get(key []byte) ([]byte, error) { - val, ok := self.db[string(key)] - if ok == false { - return nil, commons.ErrNotFound - } - return []byte(val), nil -} - -func (self *MockDB) BatchPut(key []byte, value []byte) { - self.db[string(key)] = string(value) -} - -func (self *MockDB) BatchDelete(key []byte) { - delete(self.db, string(key)) -} - -func NewOverlayDB() *overlaydb.OverlayDB { - return overlaydb.NewOverlayDB(&MockDB{nil, make(map[string]string)}) -} From 417e2eee183560522cac3963146264d4cfc64296 Mon Sep 17 00:00:00 2001 From: steven Date: Wed, 13 Nov 2019 11:27:17 +0800 Subject: [PATCH 6/6] fixed cli tool can not test contract with path --- localtool/cli.go | 3 ++- wasmtest/common/testframe.go | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/localtool/cli.go b/localtool/cli.go index 1384a1ebb4..61e05ccad6 100644 --- a/localtool/cli.go +++ b/localtool/cli.go @@ -23,6 +23,7 @@ import ( //"fmt" "io/ioutil" "os" + "path" "runtime" "github.com/ontio/ontology/cmd/utils" @@ -88,7 +89,7 @@ func ontologyCLI(ctx *cli.Context) error { } } } else { - err = common.InvokeSpecifiedContract(acct, database, deployobject, paramsStr, testContext) + err = common.InvokeSpecifiedContract(acct, database, path.Base(deployobject), paramsStr, testContext) if err != nil { return err } diff --git a/wasmtest/common/testframe.go b/wasmtest/common/testframe.go index 6822abe9ca..423ee9a5ed 100644 --- a/wasmtest/common/testframe.go +++ b/wasmtest/common/testframe.go @@ -418,6 +418,10 @@ func NewDeployNeoContract(signer *account.Account, code []byte) (*types.Transact func InvokeSpecifiedContract(acct *account.Account, database *ledger.Ledger, contractfile string, paramsStr string, testContext *TestContext) error { var tx *types.Transaction + if _, exist := testContext.AddrMap[contractfile]; !exist { + panic("Contract not exist") + } + if strings.HasSuffix(contractfile, ".avm") || strings.HasSuffix(contractfile, ".avm.str") { testCase := TestCase{Param: paramsStr} t, err := GenNeoVMTransaction(testCase, testContext.AddrMap[contractfile], testContext)