diff --git a/internal/fakechain/fakechain.go b/internal/fakechain/fakechain.go index f90ce2bde..9fed4b11b 100644 --- a/internal/fakechain/fakechain.go +++ b/internal/fakechain/fakechain.go @@ -80,6 +80,11 @@ func (chain *FakeChain) IsTxStillRelevant(t *transaction.Transaction, txpool *me panic("TODO") } +// InitVerificationVM initializes VM for witness check. +func (chain *FakeChain) InitVerificationVM(v *vm.VM, getContract func(util.Uint160) (*state.Contract, error), hash util.Uint160, witness *transaction.Witness) error { + panic("TODO") +} + // IsExtensibleAllowed implements Blockchainer interface. func (*FakeChain) IsExtensibleAllowed(uint160 util.Uint160) bool { return true diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 25c2ad6d7..5a7154045 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -1698,9 +1698,8 @@ var ( ErrInvalidVerificationContract = errors.New("verification contract is missing `verify` method") ) -// initVerificationVM initializes VM for witness check. -func (bc *Blockchain) initVerificationVM(ic *interop.Context, hash util.Uint160, witness *transaction.Witness) error { - v := ic.VM +// InitVerificationVM initializes VM for witness check. +func (bc *Blockchain) InitVerificationVM(v *vm.VM, getContract func(util.Uint160) (*state.Contract, error), hash util.Uint160, witness *transaction.Witness) error { if len(witness.VerificationScript) != 0 { if witness.ScriptHash() != hash { return ErrWitnessHashMismatch @@ -1714,7 +1713,7 @@ func (bc *Blockchain) initVerificationVM(ic *interop.Context, hash util.Uint160, } v.LoadScriptWithFlags(witness.VerificationScript, callflag.ReadOnly) } else { - cs, err := ic.GetContract(hash) + cs, err := getContract(hash) if err != nil { return ErrUnknownVerificationContract } @@ -1760,7 +1759,7 @@ func (bc *Blockchain) verifyHashAgainstScript(hash util.Uint160, witness *transa vm.SetPriceGetter(interopCtx.GetPrice) vm.LoadToken = contract.LoadToken(interopCtx) vm.GasLimit = gas - if err := bc.initVerificationVM(interopCtx, hash, witness); err != nil { + if err := bc.InitVerificationVM(vm, interopCtx.GetContract, hash, witness); err != nil { return 0, err } err := vm.Run() diff --git a/pkg/core/blockchainer/blockchainer.go b/pkg/core/blockchainer/blockchainer.go index bdfdc06d8..c9936e9d0 100644 --- a/pkg/core/blockchainer/blockchainer.go +++ b/pkg/core/blockchainer/blockchainer.go @@ -25,6 +25,7 @@ type Blockchainer interface { AddBlock(*block.Block) error CalculateClaimable(h util.Uint160, endHeight uint32) (*big.Int, error) Close() + InitVerificationVM(v *vm.VM, getContract func(util.Uint160) (*state.Contract, error), hash util.Uint160, witness *transaction.Witness) error IsTxStillRelevant(t *transaction.Transaction, txpool *mempool.Pool, isPartialTx bool) bool HeaderHeight() uint32 GetBlock(hash util.Uint256) (*block.Block, error) diff --git a/pkg/core/helper_test.go b/pkg/core/helper_test.go index 7ebfc6b88..661532dde 100644 --- a/pkg/core/helper_test.go +++ b/pkg/core/helper_test.go @@ -451,6 +451,15 @@ func initBasicChain(t *testing.T, bc *Blockchain) { bc.setNodesByRole(t, true, native.RoleP2PNotary, keys.PublicKeys{ntr.Accounts[0].PrivateKey().PublicKey()}) t.Logf("Designated Notary node: %s", hex.EncodeToString(ntr.Accounts[0].PrivateKey().PublicKey().Bytes())) + // Push verification contract with arguments into the chain. + txDeploy3, _ := newDeployTx(t, bc, priv0ScriptHash, prefix+"verification_with_args_contract.go", "VerifyWithArgs") + txDeploy3.Nonce = getNextNonce() + txDeploy3.ValidUntilBlock = validUntilBlock + require.NoError(t, addNetworkFee(bc, txDeploy3, acc0)) + require.NoError(t, acc0.SignTx(txDeploy3)) + b = bc.newBlock(txDeploy3) + require.NoError(t, bc.AddBlock(b)) + // Compile contract to test `invokescript` RPC call _, _ = newDeployTx(t, bc, priv0ScriptHash, prefix+"invokescript_contract.go", "ContractForInvokescriptTest") } diff --git a/pkg/rpc/request/txBuilder.go b/pkg/rpc/request/txBuilder.go index e43e8b1bd..2edb4ad1f 100644 --- a/pkg/rpc/request/txBuilder.go +++ b/pkg/rpc/request/txBuilder.go @@ -14,9 +14,9 @@ import ( "github.com/nspcc-dev/neo-go/pkg/vm/opcode" ) -// expandArrayIntoScript pushes all FuncParam parameters from the given array +// ExpandArrayIntoScript pushes all FuncParam parameters from the given array // into the given buffer in reverse order. -func expandArrayIntoScript(script *io.BinWriter, slice []Param) error { +func ExpandArrayIntoScript(script *io.BinWriter, slice []Param) error { for j := len(slice) - 1; j >= 0; j-- { fp, err := slice[j].GetFuncParam() if err != nil { @@ -87,7 +87,7 @@ func expandArrayIntoScript(script *io.BinWriter, slice []Param) error { if err != nil { return err } - err = expandArrayIntoScript(script, val) + err = ExpandArrayIntoScript(script, val) if err != nil { return err } @@ -119,7 +119,7 @@ func CreateFunctionInvocationScript(contract util.Uint160, method string, params if err != nil { return nil, err } - err = expandArrayIntoScript(script.BinWriter, slice) + err = ExpandArrayIntoScript(script.BinWriter, slice) if err != nil { return nil, err } diff --git a/pkg/rpc/request/tx_builder_test.go b/pkg/rpc/request/tx_builder_test.go index af0fcbaa7..b79b9d62e 100644 --- a/pkg/rpc/request/tx_builder_test.go +++ b/pkg/rpc/request/tx_builder_test.go @@ -105,7 +105,7 @@ func TestExpandArrayIntoScript(t *testing.T) { } for _, c := range testCases { script := io.NewBufBinWriter() - err := expandArrayIntoScript(script.BinWriter, c.Input) + err := ExpandArrayIntoScript(script.BinWriter, c.Input) require.NoError(t, err) require.Equal(t, c.Expected, script.Bytes()) } @@ -119,7 +119,7 @@ func TestExpandArrayIntoScript(t *testing.T) { } for _, c := range errorCases { script := io.NewBufBinWriter() - err := expandArrayIntoScript(script.BinWriter, c) + err := ExpandArrayIntoScript(script.BinWriter, c) require.Error(t, err) } } diff --git a/pkg/rpc/server/client_test.go b/pkg/rpc/server/client_test.go index 87932f0f6..c7c459a9e 100644 --- a/pkg/rpc/server/client_test.go +++ b/pkg/rpc/server/client_test.go @@ -71,6 +71,7 @@ func TestAddNetworkFee(t *testing.T) { chain, rpcSrv, httpSrv := initServerWithInMemoryChain(t) defer chain.Close() defer rpcSrv.Shutdown() + const extraFee = 10 c, err := client.New(context.Background(), httpSrv.URL, client.Options{}) require.NoError(t, err) @@ -95,7 +96,7 @@ func TestAddNetworkFee(t *testing.T) { Account: accs[0].PrivateKey().GetScriptHash(), Scopes: transaction.CalledByEntry, }} - require.Error(t, c.AddNetworkFee(tx, 10, accs[0], accs[1])) + require.Error(t, c.AddNetworkFee(tx, extraFee, accs[0], accs[1])) }) t.Run("Simple", func(t *testing.T) { tx := transaction.New(testchain.Network(), []byte{byte(opcode.PUSH1)}, 0) @@ -107,7 +108,7 @@ func TestAddNetworkFee(t *testing.T) { require.NoError(t, c.AddNetworkFee(tx, 10, accs[0])) require.NoError(t, accs[0].SignTx(tx)) cFee, _ := fee.Calculate(chain.GetBaseExecFee(), accs[0].Contract.Script) - require.Equal(t, int64(io.GetVarSize(tx))*feePerByte+cFee+10, tx.NetworkFee) + require.Equal(t, int64(io.GetVarSize(tx))*feePerByte+cFee+extraFee, tx.NetworkFee) }) t.Run("Multi", func(t *testing.T) { @@ -126,43 +127,69 @@ func TestAddNetworkFee(t *testing.T) { Scopes: transaction.Global, }, } - require.NoError(t, c.AddNetworkFee(tx, 10, accs[0], accs[1])) + require.NoError(t, c.AddNetworkFee(tx, extraFee, accs[0], accs[1])) require.NoError(t, accs[0].SignTx(tx)) require.NoError(t, accs[1].SignTx(tx)) require.NoError(t, accs[2].SignTx(tx)) cFee, _ := fee.Calculate(chain.GetBaseExecFee(), accs[0].Contract.Script) cFeeM, _ := fee.Calculate(chain.GetBaseExecFee(), accs[1].Contract.Script) - require.Equal(t, int64(io.GetVarSize(tx))*feePerByte+cFee+cFeeM+10, tx.NetworkFee) + require.Equal(t, int64(io.GetVarSize(tx))*feePerByte+cFee+cFeeM+extraFee, tx.NetworkFee) }) t.Run("Contract", func(t *testing.T) { - tx := transaction.New(testchain.Network(), []byte{byte(opcode.PUSH1)}, 0) - priv := testchain.PrivateKeyByID(0) - acc1 := wallet.NewAccountFromPrivateKey(priv) - acc1.Contract.Deployed = true - acc1.Contract.Script, err = base64.StdEncoding.DecodeString(verifyContractAVM) - require.NoError(t, err) h, err := util.Uint160DecodeStringLE(verifyContractHash) require.NoError(t, err) - tx.ValidUntilBlock = chain.BlockHeight() + 10 + priv := testchain.PrivateKeyByID(0) + acc0 := wallet.NewAccountFromPrivateKey(priv) + acc1 := wallet.NewAccountFromPrivateKey(priv) // contract account + acc1.Contract.Deployed = true + acc1.Contract.Script, err = base64.StdEncoding.DecodeString(verifyContractAVM) + + newTx := func(t *testing.T) *transaction.Transaction { + tx := transaction.New(testchain.Network(), []byte{byte(opcode.PUSH1)}, 0) + require.NoError(t, err) + tx.ValidUntilBlock = chain.BlockHeight() + 10 + return tx + } t.Run("Valid", func(t *testing.T) { - acc0 := wallet.NewAccountFromPrivateKey(priv) - tx.Signers = []transaction.Signer{ - { - Account: acc0.PrivateKey().GetScriptHash(), - Scopes: transaction.CalledByEntry, - }, - { - Account: h, - Scopes: transaction.Global, - }, + completeTx := func(t *testing.T) *transaction.Transaction { + tx := newTx(t) + tx.Signers = []transaction.Signer{ + { + Account: acc0.PrivateKey().GetScriptHash(), + Scopes: transaction.CalledByEntry, + }, + { + Account: h, + Scopes: transaction.Global, + }, + } + require.NoError(t, c.AddNetworkFee(tx, extraFee, acc0, acc1)) + return tx } - require.NoError(t, c.AddNetworkFee(tx, 10, acc0, acc1)) - require.NoError(t, acc0.SignTx(tx)) - tx.Scripts = append(tx.Scripts, transaction.Witness{}) - require.NoError(t, chain.VerifyTx(tx)) + + // check that network fee with extra value is enough + tx1 := completeTx(t) + require.NoError(t, acc0.SignTx(tx1)) + tx1.Scripts = append(tx1.Scripts, transaction.Witness{}) + require.NoError(t, chain.VerifyTx(tx1)) + + // check that network fee without extra value is enough + tx2 := completeTx(t) + tx2.NetworkFee -= extraFee + require.NoError(t, acc0.SignTx(tx2)) + tx2.Scripts = append(tx2.Scripts, transaction.Witness{}) + require.NoError(t, chain.VerifyTx(tx2)) + + // check that we don't add unexpected extra GAS + tx3 := completeTx(t) + tx3.NetworkFee -= extraFee + 1 + require.NoError(t, acc0.SignTx(tx3)) + tx3.Scripts = append(tx3.Scripts, transaction.Witness{}) + require.Error(t, chain.VerifyTx(tx3)) }) t.Run("Invalid", func(t *testing.T) { + tx := newTx(t) acc0, err := wallet.NewAccount() require.NoError(t, err) tx.Signers = []transaction.Signer{ @@ -178,6 +205,7 @@ func TestAddNetworkFee(t *testing.T) { require.Error(t, c.AddNetworkFee(tx, 10, acc0, acc1)) }) t.Run("InvalidContract", func(t *testing.T) { + tx := newTx(t) acc0 := wallet.NewAccountFromPrivateKey(priv) tx.Signers = []transaction.Signer{ { diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index b193746f4..fc299e9ef 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -36,9 +36,9 @@ import ( "github.com/nspcc-dev/neo-go/pkg/services/oracle" "github.com/nspcc-dev/neo-go/pkg/services/oracle/broadcaster" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" - "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/opcode" "go.uber.org/zap" ) @@ -1118,7 +1118,7 @@ func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *respons return nil, response.NewInternalServerError("can't create invocation script", err) } tx.Script = script - return s.runScriptInVM(trigger.Application, script, tx) + return s.runScriptInVM(trigger.Application, script, util.Uint160{}, tx) } // invokescript implements the `invokescript` RPC call. @@ -1144,7 +1144,7 @@ func (s *Server) invokescript(reqParams request.Params) (interface{}, *response. tx.Signers = []transaction.Signer{{Account: util.Uint160{}, Scopes: transaction.None}} } tx.Script = script - return s.runScriptInVM(trigger.Application, script, tx) + return s.runScriptInVM(trigger.Application, script, util.Uint160{}, tx) } // invokeContractVerify implements the `invokecontractverify` RPC call. @@ -1154,36 +1154,43 @@ func (s *Server) invokeContractVerify(reqParams request.Params) (interface{}, *r return nil, responseErr } - args := reqParams[1:2] - var tx *transaction.Transaction + bw := io.NewBufBinWriter() + if len(reqParams) > 1 { + args, err := reqParams[1].GetArray() // second `invokecontractverify` parameter is an array of arguments for `verify` method + if err != nil { + return nil, response.WrapErrorWithData(response.ErrInvalidParams, err) + } + if len(args) > 0 { + err := request.ExpandArrayIntoScript(bw.BinWriter, args) + if err != nil { + return nil, response.NewRPCError("can't create witness invocation script", err.Error(), err) + } + } + } + invocationScript := bw.Bytes() + + tx := &transaction.Transaction{Script: []byte{byte(opcode.RET)}} // need something in script if len(reqParams) > 2 { signers, witnesses, err := reqParams[2].GetSignersWithWitnesses() if err != nil { return nil, response.ErrInvalidParams } - tx = &transaction.Transaction{ - Signers: signers, - Scripts: witnesses, - } + tx.Signers = signers + tx.Scripts = witnesses + } else { // fill the only known signer - the contract with `verify` method + tx.Signers = []transaction.Signer{{Account: scriptHash}} + tx.Scripts = []transaction.Witness{{InvocationScript: invocationScript, VerificationScript: []byte{}}} } - cs := s.chain.GetContractState(scriptHash) - if cs == nil { - return nil, response.NewRPCError("unknown contract", scriptHash.StringBE(), nil) - } - script, err := request.CreateFunctionInvocationScript(cs.Hash, manifest.MethodVerify, args) - if err != nil { - return nil, response.NewInternalServerError("can't create invocation script", err) - } - if tx != nil { - tx.Script = script - } - return s.runScriptInVM(trigger.Verification, script, tx) + return s.runScriptInVM(trigger.Verification, invocationScript, scriptHash, tx) } // runScriptInVM runs given script in a new test VM and returns the invocation -// result. -func (s *Server) runScriptInVM(t trigger.Type, script []byte, tx *transaction.Transaction) (*result.Invoke, *response.Error) { +// result. The script is either a simple script in case of `application` trigger +// witness invocation script in case of `verification` trigger (it pushes `verify` +// arguments on stack before verification). In case of contract verification +// contractScriptHash should be specified. +func (s *Server) runScriptInVM(t trigger.Type, script []byte, contractScriptHash util.Uint160, tx *transaction.Transaction) (*result.Invoke, *response.Error) { // When transferring funds, script execution does no auto GAS claim, // because it depends on persisting tx height. // This is why we provide block here. @@ -1197,7 +1204,27 @@ func (s *Server) runScriptInVM(t trigger.Type, script []byte, tx *transaction.Tr vm := s.chain.GetTestVM(t, tx, b) vm.GasLimit = int64(s.config.MaxGasInvoke) - vm.LoadScriptWithFlags(script, callflag.All) + if t == trigger.Verification { + // We need this special case because witnesses verification is not the simple System.Contract.Call, + // and we need to define exactly the amount of gas consumed for a contract witness verification. + gasPolicy := s.chain.GetPolicer().GetMaxVerificationGAS() + if vm.GasLimit > gasPolicy { + vm.GasLimit = gasPolicy + } + + err := s.chain.InitVerificationVM(vm, func(h util.Uint160) (*state.Contract, error) { + res := s.chain.GetContractState(h) + if res == nil { + return nil, fmt.Errorf("unknown contract: %s", h.StringBE()) + } + return res, nil + }, contractScriptHash, &transaction.Witness{InvocationScript: script, VerificationScript: []byte{}}) + if err != nil { + return nil, response.NewInternalServerError("can't prepare verification VM", err) + } + } else { + vm.LoadScriptWithFlags(script, callflag.All) + } err = vm.Run() var faultException string if err != nil { diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 8f7c15157..e1c039647 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -35,6 +35,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/stretchr/testify/assert" @@ -60,11 +61,13 @@ type rpcTestCase struct { } const testContractHash = "1e1c3024bd955ff3baf7cb92e3b7608c7bb3712b" -const deploymentTxHash = "b67eb38f1e6805d60b68cbec9cf8db7e4a71313b6f53ff8545c578b51ce874c5" +const deploymentTxHash = "298092d1619585b2fcd3045c8e5a749ddbe14a6fe41569a69b50f47b812112d9" const genesisBlockHash = "5b60644c6c6f58faca72c70689d7ed1f40c2e795772bd0de5a88e983ad55080c" const verifyContractHash = "5bb4bac40e961e334ba7bd36d2496010f67e246e" const verifyContractAVM = "VwMAQS1RCDAhcAwUVVQtU+0PVUb61E1umZEoZwIvzl7bMHFoE87bKGnbKJdA" +const verifyWithArgsContractHash = "59b08e81dcf94f6ddbef5c2d84a4c1a098b9a984" +const verifyWithArgsContractAVM = "VwIDeAwLZ29vZF9zdHJpbmeXJA15FSgJehHbIJciBRHbIHBoQA==" const invokescriptContractAVM = "VwcADBQBDAMOBQYMDQIODw0DDgcJAAAAANswcGhB+CfsjCGqJgQRQAwUDQ8DAgkAAgEDBwMEBQIBAA4GDAnbMHFpQfgn7IwhqiYEEkATQA==" var rpcTestCases = map[string][]rpcTestCase{ @@ -652,7 +655,7 @@ var rpcTestCases = map[string][]rpcTestCase{ require.True(t, ok) expected := result.UnclaimedGas{ Address: testchain.MultisigScriptHash(), - Unclaimed: *big.NewInt(4500), + Unclaimed: *big.NewInt(5000), } assert.Equal(t, expected, *actual) }, @@ -815,7 +818,7 @@ var rpcTestCases = map[string][]rpcTestCase{ check: func(t *testing.T, e *executor, inv interface{}) { res, ok := inv.(*result.Invoke) require.True(t, ok) - assert.NotNil(t, res.Script) + assert.Nil(t, res.Script) // empty witness invocation script (pushes args of `verify` on stack, but this `verify` don't have args) assert.Equal(t, "HALT", res.State) assert.NotEqual(t, 0, res.GasConsumed) assert.Equal(t, true, res.Stack[0].Value().(bool), fmt.Sprintf("check address in verification_contract.go: expected %s", testchain.PrivateKeyByID(0).Address())) @@ -828,25 +831,74 @@ var rpcTestCases = map[string][]rpcTestCase{ check: func(t *testing.T, e *executor, inv interface{}) { res, ok := inv.(*result.Invoke) require.True(t, ok) - assert.NotNil(t, res.Script) + assert.Nil(t, res.Script) assert.Equal(t, "HALT", res.State, res.FaultException) assert.NotEqual(t, 0, res.GasConsumed) assert.Equal(t, false, res.Stack[0].Value().(bool)) }, }, { - name: "positive, with scripts", + name: "positive, no arguments", + params: fmt.Sprintf(`["%s"]`, verifyContractHash), + result: func(e *executor) interface{} { return &result.Invoke{} }, + check: func(t *testing.T, e *executor, inv interface{}) { + res, ok := inv.(*result.Invoke) + require.True(t, ok) + assert.Nil(t, res.Script) + assert.Equal(t, "HALT", res.State, res.FaultException) + assert.NotEqual(t, 0, res.GasConsumed) + assert.Equal(t, false, res.Stack[0].Value().(bool)) + }, + }, + { + name: "positive, with signers and scripts", params: fmt.Sprintf(`["%s", [], [{"account":"%s", "invocation":"MQo=", "verification": ""}]]`, verifyContractHash, testchain.PrivateKeyByID(0).PublicKey().GetScriptHash().StringLE()), result: func(e *executor) interface{} { return &result.Invoke{} }, check: func(t *testing.T, e *executor, inv interface{}) { res, ok := inv.(*result.Invoke) require.True(t, ok) - assert.NotNil(t, res.Script) + assert.Nil(t, res.Script) assert.Equal(t, "HALT", res.State) assert.NotEqual(t, 0, res.GasConsumed) assert.Equal(t, true, res.Stack[0].Value().(bool)) }, }, + { + name: "positive, with arguments, result=true", + params: fmt.Sprintf(`["%s", [{"type": "String", "value": "good_string"}, {"type": "Integer", "value": "4"}, {"type":"Boolean", "value": "false"}]]`, verifyWithArgsContractHash), + result: func(e *executor) interface{} { return &result.Invoke{} }, + check: func(t *testing.T, e *executor, inv interface{}) { + res, ok := inv.(*result.Invoke) + require.True(t, ok) + expectedInvScript := io.NewBufBinWriter() + emit.Int(expectedInvScript.BinWriter, 0) + emit.Int(expectedInvScript.BinWriter, int64(4)) + emit.String(expectedInvScript.BinWriter, "good_string") + require.NoError(t, expectedInvScript.Err) + assert.Equal(t, expectedInvScript.Bytes(), res.Script) // witness invocation script (pushes args of `verify` on stack) + assert.Equal(t, "HALT", res.State, res.FaultException) + assert.NotEqual(t, 0, res.GasConsumed) + assert.Equal(t, true, res.Stack[0].Value().(bool)) + }, + }, + { + name: "positive, with arguments, result=false", + params: fmt.Sprintf(`["%s", [{"type": "String", "value": "invalid_string"}, {"type": "Integer", "value": "4"}, {"type":"Boolean", "value": "false"}]]`, verifyWithArgsContractHash), + result: func(e *executor) interface{} { return &result.Invoke{} }, + check: func(t *testing.T, e *executor, inv interface{}) { + res, ok := inv.(*result.Invoke) + require.True(t, ok) + expectedInvScript := io.NewBufBinWriter() + emit.Int(expectedInvScript.BinWriter, 0) + emit.Int(expectedInvScript.BinWriter, int64(4)) + emit.String(expectedInvScript.BinWriter, "invalid_string") + require.NoError(t, expectedInvScript.Err) + assert.Equal(t, expectedInvScript.Bytes(), res.Script) + assert.Equal(t, "HALT", res.State, res.FaultException) + assert.NotEqual(t, 0, res.GasConsumed) + assert.Equal(t, false, res.Stack[0].Value().(bool)) + }, + }, { name: "unknown contract", params: fmt.Sprintf(`["%s", []]`, util.Uint160{}.String()), @@ -1370,7 +1422,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] require.NoErrorf(t, err, "could not parse response: %s", txOut) assert.Equal(t, *block.Transactions[0], actual.Transaction) - assert.Equal(t, 10, actual.Confirmations) + assert.Equal(t, 11, actual.Confirmations) assert.Equal(t, TXHash, actual.Transaction.Hash()) }) @@ -1488,12 +1540,12 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] require.NoError(t, json.Unmarshal(res, actual)) checkNep17TransfersAux(t, e, actual, sent, rcvd) } - t.Run("time frame only", func(t *testing.T) { testNEP17T(t, 4, 5, 0, 0, []int{5, 6, 7, 8}, []int{1, 2}) }) + t.Run("time frame only", func(t *testing.T) { testNEP17T(t, 4, 5, 0, 0, []int{6, 7, 8, 9}, []int{1, 2}) }) t.Run("no res", func(t *testing.T) { testNEP17T(t, 100, 100, 0, 0, []int{}, []int{}) }) - t.Run("limit", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 0, []int{2, 3}, []int{0}) }) - t.Run("limit 2", func(t *testing.T) { testNEP17T(t, 4, 5, 2, 0, []int{5}, []int{1}) }) - t.Run("limit with page", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 1, []int{4, 5}, []int{1}) }) - t.Run("limit with page 2", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 2, []int{6, 7}, []int{2}) }) + t.Run("limit", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 0, []int{3, 4}, []int{0}) }) + t.Run("limit 2", func(t *testing.T) { testNEP17T(t, 4, 5, 2, 0, []int{6}, []int{1}) }) + t.Run("limit with page", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 1, []int{5, 6}, []int{1}) }) + t.Run("limit with page 2", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 2, []int{7, 8}, []int{2}) }) }) } @@ -1598,8 +1650,8 @@ func checkNep17Balances(t *testing.T, e *executor, acc interface{}) { }, { Asset: e.chain.UtilityTokenHash(), - Amount: "78994302340", - LastUpdated: 8, + Amount: "68992647820", + LastUpdated: 10, }}, Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(), } @@ -1608,7 +1660,7 @@ func checkNep17Balances(t *testing.T, e *executor, acc interface{}) { } func checkNep17Transfers(t *testing.T, e *executor, acc interface{}) { - checkNep17TransfersAux(t, e, acc, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, []int{0, 1, 2, 3, 4, 5, 6}) + checkNep17TransfersAux(t, e, acc, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, []int{0, 1, 2, 3, 4, 5, 6}) } func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rcvd []int) { @@ -1617,12 +1669,17 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rc rublesHash, err := util.Uint160DecodeStringLE(testContractHash) require.NoError(t, err) + blockDeploy3, err := e.chain.GetBlock(e.chain.GetHeaderHash(10)) // deploy verification_with_args_contract.go + require.NoError(t, err) + require.Equal(t, 1, len(blockDeploy3.Transactions)) + txDeploy3 := blockDeploy3.Transactions[0] + blockDepositGAS, err := e.chain.GetBlock(e.chain.GetHeaderHash(8)) require.NoError(t, err) require.Equal(t, 1, len(blockDepositGAS.Transactions)) txDepositGAS := blockDepositGAS.Transactions[0] - blockDeploy2, err := e.chain.GetBlock(e.chain.GetHeaderHash(7)) + blockDeploy2, err := e.chain.GetBlock(e.chain.GetHeaderHash(7)) // deploy verification_contract.go require.NoError(t, err) require.Equal(t, 1, len(blockDeploy2.Transactions)) txDeploy2 := blockDeploy2.Transactions[0] @@ -1670,6 +1727,14 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rc // duplicate the Server method. expected := result.NEP17Transfers{ Sent: []result.NEP17Transfer{ + { + Timestamp: blockDeploy3.Timestamp, + Asset: e.chain.UtilityTokenHash(), + Address: "", // burn + Amount: big.NewInt(txDeploy3.SystemFee + txDeploy3.NetworkFee).String(), + Index: 10, + TxHash: blockDeploy3.Hash(), + }, { Timestamp: blockDepositGAS.Timestamp, Asset: e.chain.UtilityTokenHash(), diff --git a/pkg/rpc/server/testdata/testblocks.acc b/pkg/rpc/server/testdata/testblocks.acc index 618d99054..e787ddaee 100644 Binary files a/pkg/rpc/server/testdata/testblocks.acc and b/pkg/rpc/server/testdata/testblocks.acc differ diff --git a/pkg/rpc/server/testdata/verification_with_args_contract.go b/pkg/rpc/server/testdata/verification_with_args_contract.go new file mode 100644 index 000000000..bbd404166 --- /dev/null +++ b/pkg/rpc/server/testdata/verification_with_args_contract.go @@ -0,0 +1,7 @@ +package testdata + +// Verify is a verification contract method which takes several arguments. +func Verify(argString string, argInt int, argBool bool) bool { + isOK := argString == "good_string" || argInt == 5 || argBool == true + return isOK +}