parent
d27775acc5
commit
9a493dd2a0
6 changed files with 121 additions and 42 deletions
|
@ -7,6 +7,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
|
@ -34,8 +35,10 @@ func (c *Client) NEP5Decimals(tokenHash util.Uint160) (int64, error) {
|
|||
result, err := c.InvokeFunction(tokenHash, "decimals", []smartcontract.Parameter{}, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else if result.State != "HALT" || len(result.Stack) == 0 {
|
||||
return 0, errors.New("invalid VM state")
|
||||
}
|
||||
err = getInvocationError(result)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get NEP5 decimals: %w", err)
|
||||
}
|
||||
|
||||
return topIntFromStack(result.Stack)
|
||||
|
@ -46,8 +49,10 @@ func (c *Client) NEP5Name(tokenHash util.Uint160) (string, error) {
|
|||
result, err := c.InvokeFunction(tokenHash, "name", []smartcontract.Parameter{}, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else if result.State != "HALT" || len(result.Stack) == 0 {
|
||||
return "", errors.New("invalid VM state")
|
||||
}
|
||||
err = getInvocationError(result)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get NEP5 name: %w", err)
|
||||
}
|
||||
|
||||
return topStringFromStack(result.Stack)
|
||||
|
@ -58,8 +63,10 @@ func (c *Client) NEP5Symbol(tokenHash util.Uint160) (string, error) {
|
|||
result, err := c.InvokeFunction(tokenHash, "symbol", []smartcontract.Parameter{}, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else if result.State != "HALT" || len(result.Stack) == 0 {
|
||||
return "", errors.New("invalid VM state")
|
||||
}
|
||||
err = getInvocationError(result)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get NEP5 symbol: %w", err)
|
||||
}
|
||||
|
||||
return topStringFromStack(result.Stack)
|
||||
|
@ -70,8 +77,10 @@ func (c *Client) NEP5TotalSupply(tokenHash util.Uint160) (int64, error) {
|
|||
result, err := c.InvokeFunction(tokenHash, "totalSupply", []smartcontract.Parameter{}, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else if result.State != "HALT" || len(result.Stack) == 0 {
|
||||
return 0, errors.New("invalid VM state")
|
||||
}
|
||||
err = getInvocationError(result)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get NEP5 total supply: %w", err)
|
||||
}
|
||||
|
||||
return topIntFromStack(result.Stack)
|
||||
|
@ -85,8 +94,10 @@ func (c *Client) NEP5BalanceOf(tokenHash, acc util.Uint160) (int64, error) {
|
|||
}}, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else if result.State != "HALT" || len(result.Stack) == 0 {
|
||||
return 0, errors.New("invalid VM state")
|
||||
}
|
||||
err = getInvocationError(result)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get NEP5 balance: %w", err)
|
||||
}
|
||||
|
||||
return topIntFromStack(result.Stack)
|
||||
|
@ -156,7 +167,7 @@ func (c *Client) CreateTxFromScript(script []byte, acc *wallet.Account, sysFee,
|
|||
return nil, fmt.Errorf("can't add system fee to transaction: %w", err)
|
||||
}
|
||||
if result.State != "HALT" {
|
||||
return nil, fmt.Errorf("can't add system fee to transaction: bad vm state: %s", result.State)
|
||||
return nil, fmt.Errorf("can't add system fee to transaction: bad vm state: %s due to an error: %s", result.State, result.FaultException)
|
||||
}
|
||||
sysFee = result.GasConsumed
|
||||
}
|
||||
|
@ -225,3 +236,14 @@ func topStringFromStack(st []stackitem.Item) (string, error) {
|
|||
}
|
||||
return string(bs), nil
|
||||
}
|
||||
|
||||
// getInvocationError returns an error in case of bad VM state or empty stack.
|
||||
func getInvocationError(result *result.Invoke) error {
|
||||
if result.State != "HALT" {
|
||||
return fmt.Errorf("invocation failed: %s", result.FaultException)
|
||||
}
|
||||
if len(result.Stack) == 0 {
|
||||
return errors.New("result stack is empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||
|
@ -33,8 +32,10 @@ func (c *Client) invokeNativePolicyMethod(operation string) (int64, error) {
|
|||
result, err := c.InvokeFunction(PolicyContractHash, operation, []smartcontract.Parameter{}, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else if result.State != "HALT" || len(result.Stack) == 0 {
|
||||
return 0, errors.New("invalid VM state")
|
||||
}
|
||||
err = getInvocationError(result)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to invoke %s Policy method: %w", operation, err)
|
||||
}
|
||||
|
||||
return topIntFromStack(result.Stack)
|
||||
|
@ -45,10 +46,11 @@ func (c *Client) GetBlockedAccounts() (native.BlockedAccounts, error) {
|
|||
result, err := c.InvokeFunction(PolicyContractHash, "getBlockedAccounts", []smartcontract.Parameter{}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if result.State != "HALT" || len(result.Stack) == 0 {
|
||||
return nil, errors.New("invalid VM state")
|
||||
}
|
||||
|
||||
err = getInvocationError(result)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get blocked accounts: %w", err)
|
||||
}
|
||||
return topBlockedAccountsFromStack(result.Stack)
|
||||
}
|
||||
|
||||
|
|
|
@ -540,12 +540,17 @@ func (c *Client) AddNetworkFee(tx *transaction.Transaction, extraFee int64, accs
|
|||
for i, cosigner := range tx.Signers {
|
||||
if accs[i].Contract.Deployed {
|
||||
res, err := c.InvokeFunction(cosigner.Account, manifest.MethodVerify, []smartcontract.Parameter{}, tx.Signers)
|
||||
if err == nil && res.State == "HALT" && len(res.Stack) == 1 {
|
||||
r, err := topIntFromStack(res.Stack)
|
||||
if err != nil || r == 0 {
|
||||
return core.ErrVerificationFailed
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to invoke verify: %w", err)
|
||||
}
|
||||
if res.State != "HALT" {
|
||||
return fmt.Errorf("invalid VM state %s due to an error: %s", res.State, res.FaultException)
|
||||
}
|
||||
if l := len(res.Stack); l != 1 {
|
||||
return fmt.Errorf("result stack length should be equal to 1, got %d", l)
|
||||
}
|
||||
r, err := topIntFromStack(res.Stack)
|
||||
if err != nil || r == 0 {
|
||||
return core.ErrVerificationFailed
|
||||
}
|
||||
tx.NetworkFee += res.GasConsumed
|
||||
|
|
|
@ -677,6 +677,41 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
|
|||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive, FAULT state",
|
||||
invoke: func(c *Client) (interface{}, error) {
|
||||
hash, err := util.Uint160DecodeStringLE("91b83e96f2a7c4fdf0c1688441ec61986c7cae26")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
contr, err := util.Uint160DecodeStringLE("af7c7328eee5a275a3bcaee2bf0cf662b5e739be")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return c.InvokeFunction(contr, "balanceOf", []smartcontract.Parameter{
|
||||
{
|
||||
Type: smartcontract.Hash160Type,
|
||||
Value: hash,
|
||||
},
|
||||
}, []transaction.Signer{{
|
||||
Account: util.Uint160{1, 2, 3},
|
||||
}})
|
||||
},
|
||||
serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"script":"1426ae7c6c9861ec418468c1f0fdc4a7f2963eb89151c10962616c616e63654f6667be39e7b562f60cbfe2aebca375a2e5ee28737caf","state":"FAULT","gasconsumed":"31100000","stack":[{"type":"ByteString","value":"JivsCEQy"}],"tx":"d101361426ae7c6c9861ec418468c1f0fdc4a7f2963eb89151c10962616c616e63654f6667be39e7b562f60cbfe2aebca375a2e5ee28737caf000000000000000000000000","exception":"gas limit exceeded"}}`,
|
||||
result: func(c *Client) interface{} {
|
||||
bytes, err := hex.DecodeString("262bec084432")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &result.Invoke{
|
||||
State: "FAULT",
|
||||
GasConsumed: 31100000,
|
||||
Script: "1426ae7c6c9861ec418468c1f0fdc4a7f2963eb89151c10962616c616e63654f6667be39e7b562f60cbfe2aebca375a2e5ee28737caf",
|
||||
Stack: []stackitem.Item{stackitem.NewByteArray(bytes)},
|
||||
FaultException: "gas limit exceeded",
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"invokescript": {
|
||||
{
|
||||
|
|
|
@ -9,17 +9,19 @@ import (
|
|||
// Invoke represents code invocation result and is used by several RPC calls
|
||||
// that invoke functions, scripts and generic bytecode.
|
||||
type Invoke struct {
|
||||
State string
|
||||
GasConsumed int64
|
||||
Script string
|
||||
Stack []stackitem.Item
|
||||
State string
|
||||
GasConsumed int64
|
||||
Script string
|
||||
Stack []stackitem.Item
|
||||
FaultException string
|
||||
}
|
||||
|
||||
type invokeAux struct {
|
||||
State string `json:"state"`
|
||||
GasConsumed int64 `json:"gasconsumed,string"`
|
||||
Script string `json:"script"`
|
||||
Stack json.RawMessage `json:"stack"`
|
||||
State string `json:"state"`
|
||||
GasConsumed int64 `json:"gasconsumed,string"`
|
||||
Script string `json:"script"`
|
||||
Stack json.RawMessage `json:"stack"`
|
||||
FaultException string `json:"exception,omitempty"`
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
|
@ -43,10 +45,11 @@ func (r Invoke) MarshalJSON() ([]byte, error) {
|
|||
}
|
||||
}
|
||||
return json.Marshal(&invokeAux{
|
||||
GasConsumed: r.GasConsumed,
|
||||
Script: r.Script,
|
||||
State: r.State,
|
||||
Stack: st,
|
||||
GasConsumed: r.GasConsumed,
|
||||
Script: r.Script,
|
||||
State: r.State,
|
||||
Stack: st,
|
||||
FaultException: r.FaultException,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -72,5 +75,6 @@ func (r *Invoke) UnmarshalJSON(data []byte) error {
|
|||
r.GasConsumed = aux.GasConsumed
|
||||
r.Script = aux.Script
|
||||
r.State = aux.State
|
||||
r.FaultException = aux.FaultException
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -712,8 +712,14 @@ func (s *Server) getDecimals(contractID int32, cache map[int32]decimals) (decima
|
|||
return decimals{}, fmt.Errorf("can't create script: %w", err)
|
||||
}
|
||||
res := s.runScriptInVM(script, nil)
|
||||
if res == nil || res.State != "HALT" || len(res.Stack) == 0 {
|
||||
return decimals{}, errors.New("execution error : no result")
|
||||
if res == nil {
|
||||
return decimals{}, fmt.Errorf("execution error: no result")
|
||||
}
|
||||
if res.State != "HALT" {
|
||||
return decimals{}, fmt.Errorf("execution error: bad VM state %s due to an error %s", res.State, res.FaultException)
|
||||
}
|
||||
if len(res.Stack) == 0 {
|
||||
return decimals{}, fmt.Errorf("execution error: empty stack")
|
||||
}
|
||||
|
||||
d := decimals{Hash: h}
|
||||
|
@ -997,12 +1003,17 @@ func (s *Server) runScriptInVM(script []byte, tx *transaction.Transaction) *resu
|
|||
vm := s.chain.GetTestVM(tx)
|
||||
vm.GasLimit = int64(s.config.MaxGasInvoke)
|
||||
vm.LoadScriptWithFlags(script, smartcontract.All)
|
||||
_ = vm.Run()
|
||||
err := vm.Run()
|
||||
var faultException string
|
||||
if err != nil {
|
||||
faultException = err.Error()
|
||||
}
|
||||
result := &result.Invoke{
|
||||
State: vm.State().String(),
|
||||
GasConsumed: vm.GasConsumed(),
|
||||
Script: hex.EncodeToString(script),
|
||||
Stack: vm.Estack().ToArray(),
|
||||
State: vm.State().String(),
|
||||
GasConsumed: vm.GasConsumed(),
|
||||
Script: hex.EncodeToString(script),
|
||||
Stack: vm.Estack().ToArray(),
|
||||
FaultException: faultException,
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue