diff --git a/pkg/rpc/server/client_test.go b/pkg/rpc/server/client_test.go index efa18d793..112344349 100644 --- a/pkg/rpc/server/client_test.go +++ b/pkg/rpc/server/client_test.go @@ -18,6 +18,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/require" @@ -314,6 +315,76 @@ func TestAddNetworkFeeCalculateNetworkFee(t *testing.T) { }) } +func TestCalculateNetworkFee(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) + require.NoError(t, c.Init()) + + t.Run("ContractWithArgs", func(t *testing.T) { + check := func(t *testing.T, extraFee int64) { + h, err := util.Uint160DecodeStringLE(verifyWithArgsContractHash) + require.NoError(t, err) + priv := testchain.PrivateKeyByID(0) + acc0 := wallet.NewAccountFromPrivateKey(priv) + tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0) + require.NoError(t, err) + tx.ValidUntilBlock = chain.BlockHeight() + 10 + tx.Signers = []transaction.Signer{ + { + Account: acc0.PrivateKey().GetScriptHash(), + Scopes: transaction.CalledByEntry, + }, + { + Account: h, + Scopes: transaction.Global, + }, + } + + bw := io.NewBufBinWriter() + emit.Bool(bw.BinWriter, false) + emit.Int(bw.BinWriter, int64(4)) + emit.String(bw.BinWriter, "good_string") // contract's `verify` return `true` with this string + require.NoError(t, bw.Err) + contractInv := bw.Bytes() + // we need to fill standard verification scripts to use CalculateNetworkFee. + tx.Scripts = []transaction.Witness{ + {VerificationScript: acc0.GetVerificationScript()}, + {InvocationScript: contractInv}, + } + tx.NetworkFee, err = c.CalculateNetworkFee(tx) + require.NoError(t, err) + tx.NetworkFee += extraFee + tx.Scripts = nil + + require.NoError(t, acc0.SignTx(testchain.Network(), tx)) + tx.Scripts = append(tx.Scripts, transaction.Witness{InvocationScript: contractInv}) + err = chain.VerifyTx(tx) + if extraFee < 0 { + require.Error(t, err) + } else { + require.NoError(t, err) + } + } + + t.Run("with extra fee", func(t *testing.T) { + // check that calculated network fee with extra value is enough + check(t, extraFee) + }) + t.Run("without extra fee", func(t *testing.T) { + // check that calculated network fee without extra value is enough + check(t, 0) + }) + t.Run("exactFee-1", func(t *testing.T) { + // check that we don't add unexpected extra GAS + check(t, -1) + }) + }) +} func TestSignAndPushInvocationTx(t *testing.T) { chain, rpcSrv, httpSrv := initServerWithInMemoryChain(t) defer chain.Close() diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index 8f10088ca..a39bb4612 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -584,7 +584,7 @@ func (s *Server) calculateNetworkFee(reqParams request.Params) (interface{}, *re } if verificationScript == nil { // then it still might be a contract-based verification verificationErr := fmt.Sprintf("contract verification for signer #%d failed", i) - res, respErr := s.runScriptInVM(trigger.Verification, []byte{}, signer.Account, tx) + res, respErr := s.runScriptInVM(trigger.Verification, tx.Scripts[i].InvocationScript, signer.Account, tx) if respErr != nil && errors.Is(respErr.Cause, core.ErrUnknownVerificationContract) { // it's neither a contract-based verification script nor a standard witness attached to // the tx, so the user did not provide enough data to calculate fee for that witness => @@ -612,7 +612,8 @@ func (s *Server) calculateNetworkFee(reqParams request.Params) (interface{}, *re return 0, response.NewRPCError(verificationErr, cause.Error(), cause) } netFee += res.GasConsumed - size += io.GetVarSize([]byte{}) * 2 // both scripts are empty + size += io.GetVarSize([]byte{}) + // verification script is empty (contract-based witness) + io.GetVarSize(tx.Scripts[i].InvocationScript) // invocation script might not be empty (args for `verify`) continue }