Merge pull request #1825 from nspcc-dev/fix-invokecontractverify

rpc: refactor invokecontractverify
This commit is contained in:
Roman Khimov 2021-03-17 18:08:23 +03:00 committed by GitHub
commit 42465dd002
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 217 additions and 76 deletions

View file

@ -80,6 +80,11 @@ func (chain *FakeChain) IsTxStillRelevant(t *transaction.Transaction, txpool *me
panic("TODO") 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. // IsExtensibleAllowed implements Blockchainer interface.
func (*FakeChain) IsExtensibleAllowed(uint160 util.Uint160) bool { func (*FakeChain) IsExtensibleAllowed(uint160 util.Uint160) bool {
return true return true

View file

@ -1698,9 +1698,8 @@ var (
ErrInvalidVerificationContract = errors.New("verification contract is missing `verify` method") ErrInvalidVerificationContract = errors.New("verification contract is missing `verify` method")
) )
// initVerificationVM initializes VM for witness check. // InitVerificationVM initializes VM for witness check.
func (bc *Blockchain) initVerificationVM(ic *interop.Context, hash util.Uint160, witness *transaction.Witness) error { func (bc *Blockchain) InitVerificationVM(v *vm.VM, getContract func(util.Uint160) (*state.Contract, error), hash util.Uint160, witness *transaction.Witness) error {
v := ic.VM
if len(witness.VerificationScript) != 0 { if len(witness.VerificationScript) != 0 {
if witness.ScriptHash() != hash { if witness.ScriptHash() != hash {
return ErrWitnessHashMismatch return ErrWitnessHashMismatch
@ -1714,7 +1713,7 @@ func (bc *Blockchain) initVerificationVM(ic *interop.Context, hash util.Uint160,
} }
v.LoadScriptWithFlags(witness.VerificationScript, callflag.ReadOnly) v.LoadScriptWithFlags(witness.VerificationScript, callflag.ReadOnly)
} else { } else {
cs, err := ic.GetContract(hash) cs, err := getContract(hash)
if err != nil { if err != nil {
return ErrUnknownVerificationContract return ErrUnknownVerificationContract
} }
@ -1760,7 +1759,7 @@ func (bc *Blockchain) verifyHashAgainstScript(hash util.Uint160, witness *transa
vm.SetPriceGetter(interopCtx.GetPrice) vm.SetPriceGetter(interopCtx.GetPrice)
vm.LoadToken = contract.LoadToken(interopCtx) vm.LoadToken = contract.LoadToken(interopCtx)
vm.GasLimit = gas 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 return 0, err
} }
err := vm.Run() err := vm.Run()

View file

@ -25,6 +25,7 @@ type Blockchainer interface {
AddBlock(*block.Block) error AddBlock(*block.Block) error
CalculateClaimable(h util.Uint160, endHeight uint32) (*big.Int, error) CalculateClaimable(h util.Uint160, endHeight uint32) (*big.Int, error)
Close() 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 IsTxStillRelevant(t *transaction.Transaction, txpool *mempool.Pool, isPartialTx bool) bool
HeaderHeight() uint32 HeaderHeight() uint32
GetBlock(hash util.Uint256) (*block.Block, error) GetBlock(hash util.Uint256) (*block.Block, error)

View file

@ -451,6 +451,15 @@ func initBasicChain(t *testing.T, bc *Blockchain) {
bc.setNodesByRole(t, true, native.RoleP2PNotary, keys.PublicKeys{ntr.Accounts[0].PrivateKey().PublicKey()}) 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())) 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 // Compile contract to test `invokescript` RPC call
_, _ = newDeployTx(t, bc, priv0ScriptHash, prefix+"invokescript_contract.go", "ContractForInvokescriptTest") _, _ = newDeployTx(t, bc, priv0ScriptHash, prefix+"invokescript_contract.go", "ContractForInvokescriptTest")
} }

View file

@ -14,9 +14,9 @@ import (
"github.com/nspcc-dev/neo-go/pkg/vm/opcode" "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. // 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-- { for j := len(slice) - 1; j >= 0; j-- {
fp, err := slice[j].GetFuncParam() fp, err := slice[j].GetFuncParam()
if err != nil { if err != nil {
@ -87,7 +87,7 @@ func expandArrayIntoScript(script *io.BinWriter, slice []Param) error {
if err != nil { if err != nil {
return err return err
} }
err = expandArrayIntoScript(script, val) err = ExpandArrayIntoScript(script, val)
if err != nil { if err != nil {
return err return err
} }
@ -119,7 +119,7 @@ func CreateFunctionInvocationScript(contract util.Uint160, method string, params
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = expandArrayIntoScript(script.BinWriter, slice) err = ExpandArrayIntoScript(script.BinWriter, slice)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -105,7 +105,7 @@ func TestExpandArrayIntoScript(t *testing.T) {
} }
for _, c := range testCases { for _, c := range testCases {
script := io.NewBufBinWriter() script := io.NewBufBinWriter()
err := expandArrayIntoScript(script.BinWriter, c.Input) err := ExpandArrayIntoScript(script.BinWriter, c.Input)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, c.Expected, script.Bytes()) require.Equal(t, c.Expected, script.Bytes())
} }
@ -119,7 +119,7 @@ func TestExpandArrayIntoScript(t *testing.T) {
} }
for _, c := range errorCases { for _, c := range errorCases {
script := io.NewBufBinWriter() script := io.NewBufBinWriter()
err := expandArrayIntoScript(script.BinWriter, c) err := ExpandArrayIntoScript(script.BinWriter, c)
require.Error(t, err) require.Error(t, err)
} }
} }

View file

@ -71,6 +71,7 @@ func TestAddNetworkFee(t *testing.T) {
chain, rpcSrv, httpSrv := initServerWithInMemoryChain(t) chain, rpcSrv, httpSrv := initServerWithInMemoryChain(t)
defer chain.Close() defer chain.Close()
defer rpcSrv.Shutdown() defer rpcSrv.Shutdown()
const extraFee = 10
c, err := client.New(context.Background(), httpSrv.URL, client.Options{}) c, err := client.New(context.Background(), httpSrv.URL, client.Options{})
require.NoError(t, err) require.NoError(t, err)
@ -95,7 +96,7 @@ func TestAddNetworkFee(t *testing.T) {
Account: accs[0].PrivateKey().GetScriptHash(), Account: accs[0].PrivateKey().GetScriptHash(),
Scopes: transaction.CalledByEntry, 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) { t.Run("Simple", func(t *testing.T) {
tx := transaction.New(testchain.Network(), []byte{byte(opcode.PUSH1)}, 0) 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, c.AddNetworkFee(tx, 10, accs[0]))
require.NoError(t, accs[0].SignTx(tx)) require.NoError(t, accs[0].SignTx(tx))
cFee, _ := fee.Calculate(chain.GetBaseExecFee(), accs[0].Contract.Script) 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) { t.Run("Multi", func(t *testing.T) {
@ -126,43 +127,69 @@ func TestAddNetworkFee(t *testing.T) {
Scopes: transaction.Global, 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[0].SignTx(tx))
require.NoError(t, accs[1].SignTx(tx)) require.NoError(t, accs[1].SignTx(tx))
require.NoError(t, accs[2].SignTx(tx)) require.NoError(t, accs[2].SignTx(tx))
cFee, _ := fee.Calculate(chain.GetBaseExecFee(), accs[0].Contract.Script) cFee, _ := fee.Calculate(chain.GetBaseExecFee(), accs[0].Contract.Script)
cFeeM, _ := fee.Calculate(chain.GetBaseExecFee(), accs[1].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) { 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) h, err := util.Uint160DecodeStringLE(verifyContractHash)
require.NoError(t, err) 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) { t.Run("Valid", func(t *testing.T) {
acc0 := wallet.NewAccountFromPrivateKey(priv) completeTx := func(t *testing.T) *transaction.Transaction {
tx.Signers = []transaction.Signer{ tx := newTx(t)
{ tx.Signers = []transaction.Signer{
Account: acc0.PrivateKey().GetScriptHash(), {
Scopes: transaction.CalledByEntry, Account: acc0.PrivateKey().GetScriptHash(),
}, Scopes: transaction.CalledByEntry,
{ },
Account: h, {
Scopes: transaction.Global, 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)) // check that network fee with extra value is enough
tx.Scripts = append(tx.Scripts, transaction.Witness{}) tx1 := completeTx(t)
require.NoError(t, chain.VerifyTx(tx)) 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) { t.Run("Invalid", func(t *testing.T) {
tx := newTx(t)
acc0, err := wallet.NewAccount() acc0, err := wallet.NewAccount()
require.NoError(t, err) require.NoError(t, err)
tx.Signers = []transaction.Signer{ tx.Signers = []transaction.Signer{
@ -178,6 +205,7 @@ func TestAddNetworkFee(t *testing.T) {
require.Error(t, c.AddNetworkFee(tx, 10, acc0, acc1)) require.Error(t, c.AddNetworkFee(tx, 10, acc0, acc1))
}) })
t.Run("InvalidContract", func(t *testing.T) { t.Run("InvalidContract", func(t *testing.T) {
tx := newTx(t)
acc0 := wallet.NewAccountFromPrivateKey(priv) acc0 := wallet.NewAccountFromPrivateKey(priv)
tx.Signers = []transaction.Signer{ tx.Signers = []transaction.Signer{
{ {

View file

@ -36,9 +36,9 @@ import (
"github.com/nspcc-dev/neo-go/pkg/services/oracle" "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/services/oracle/broadcaster"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "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/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"go.uber.org/zap" "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) return nil, response.NewInternalServerError("can't create invocation script", err)
} }
tx.Script = script 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. // 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.Signers = []transaction.Signer{{Account: util.Uint160{}, Scopes: transaction.None}}
} }
tx.Script = script 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. // invokeContractVerify implements the `invokecontractverify` RPC call.
@ -1154,36 +1154,43 @@ func (s *Server) invokeContractVerify(reqParams request.Params) (interface{}, *r
return nil, responseErr return nil, responseErr
} }
args := reqParams[1:2] bw := io.NewBufBinWriter()
var tx *transaction.Transaction 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 { if len(reqParams) > 2 {
signers, witnesses, err := reqParams[2].GetSignersWithWitnesses() signers, witnesses, err := reqParams[2].GetSignersWithWitnesses()
if err != nil { if err != nil {
return nil, response.ErrInvalidParams return nil, response.ErrInvalidParams
} }
tx = &transaction.Transaction{ tx.Signers = signers
Signers: signers, tx.Scripts = witnesses
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) return s.runScriptInVM(trigger.Verification, invocationScript, scriptHash, tx)
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)
} }
// runScriptInVM runs given script in a new test VM and returns the invocation // runScriptInVM runs given script in a new test VM and returns the invocation
// result. // result. The script is either a simple script in case of `application` trigger
func (s *Server) runScriptInVM(t trigger.Type, script []byte, tx *transaction.Transaction) (*result.Invoke, *response.Error) { // 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, // When transferring funds, script execution does no auto GAS claim,
// because it depends on persisting tx height. // because it depends on persisting tx height.
// This is why we provide block here. // 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 := s.chain.GetTestVM(t, tx, b)
vm.GasLimit = int64(s.config.MaxGasInvoke) 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() err = vm.Run()
var faultException string var faultException string
if err != nil { if err != nil {

View file

@ -35,6 +35,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm" "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/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -60,11 +61,13 @@ type rpcTestCase struct {
} }
const testContractHash = "1e1c3024bd955ff3baf7cb92e3b7608c7bb3712b" const testContractHash = "1e1c3024bd955ff3baf7cb92e3b7608c7bb3712b"
const deploymentTxHash = "b67eb38f1e6805d60b68cbec9cf8db7e4a71313b6f53ff8545c578b51ce874c5" const deploymentTxHash = "298092d1619585b2fcd3045c8e5a749ddbe14a6fe41569a69b50f47b812112d9"
const genesisBlockHash = "5b60644c6c6f58faca72c70689d7ed1f40c2e795772bd0de5a88e983ad55080c" const genesisBlockHash = "5b60644c6c6f58faca72c70689d7ed1f40c2e795772bd0de5a88e983ad55080c"
const verifyContractHash = "5bb4bac40e961e334ba7bd36d2496010f67e246e" const verifyContractHash = "5bb4bac40e961e334ba7bd36d2496010f67e246e"
const verifyContractAVM = "VwMAQS1RCDAhcAwUVVQtU+0PVUb61E1umZEoZwIvzl7bMHFoE87bKGnbKJdA" const verifyContractAVM = "VwMAQS1RCDAhcAwUVVQtU+0PVUb61E1umZEoZwIvzl7bMHFoE87bKGnbKJdA"
const verifyWithArgsContractHash = "59b08e81dcf94f6ddbef5c2d84a4c1a098b9a984"
const verifyWithArgsContractAVM = "VwIDeAwLZ29vZF9zdHJpbmeXJA15FSgJehHbIJciBRHbIHBoQA=="
const invokescriptContractAVM = "VwcADBQBDAMOBQYMDQIODw0DDgcJAAAAANswcGhB+CfsjCGqJgQRQAwUDQ8DAgkAAgEDBwMEBQIBAA4GDAnbMHFpQfgn7IwhqiYEEkATQA==" const invokescriptContractAVM = "VwcADBQBDAMOBQYMDQIODw0DDgcJAAAAANswcGhB+CfsjCGqJgQRQAwUDQ8DAgkAAgEDBwMEBQIBAA4GDAnbMHFpQfgn7IwhqiYEEkATQA=="
var rpcTestCases = map[string][]rpcTestCase{ var rpcTestCases = map[string][]rpcTestCase{
@ -652,7 +655,7 @@ var rpcTestCases = map[string][]rpcTestCase{
require.True(t, ok) require.True(t, ok)
expected := result.UnclaimedGas{ expected := result.UnclaimedGas{
Address: testchain.MultisigScriptHash(), Address: testchain.MultisigScriptHash(),
Unclaimed: *big.NewInt(4500), Unclaimed: *big.NewInt(5000),
} }
assert.Equal(t, expected, *actual) assert.Equal(t, expected, *actual)
}, },
@ -815,7 +818,7 @@ var rpcTestCases = map[string][]rpcTestCase{
check: func(t *testing.T, e *executor, inv interface{}) { check: func(t *testing.T, e *executor, inv interface{}) {
res, ok := inv.(*result.Invoke) res, ok := inv.(*result.Invoke)
require.True(t, ok) 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.Equal(t, "HALT", res.State)
assert.NotEqual(t, 0, res.GasConsumed) 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())) 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{}) { check: func(t *testing.T, e *executor, inv interface{}) {
res, ok := inv.(*result.Invoke) res, ok := inv.(*result.Invoke)
require.True(t, ok) require.True(t, ok)
assert.NotNil(t, res.Script) assert.Nil(t, res.Script)
assert.Equal(t, "HALT", res.State, res.FaultException) assert.Equal(t, "HALT", res.State, res.FaultException)
assert.NotEqual(t, 0, res.GasConsumed) assert.NotEqual(t, 0, res.GasConsumed)
assert.Equal(t, false, res.Stack[0].Value().(bool)) 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()), params: fmt.Sprintf(`["%s", [], [{"account":"%s", "invocation":"MQo=", "verification": ""}]]`, verifyContractHash, testchain.PrivateKeyByID(0).PublicKey().GetScriptHash().StringLE()),
result: func(e *executor) interface{} { return &result.Invoke{} }, result: func(e *executor) interface{} { return &result.Invoke{} },
check: func(t *testing.T, e *executor, inv interface{}) { check: func(t *testing.T, e *executor, inv interface{}) {
res, ok := inv.(*result.Invoke) res, ok := inv.(*result.Invoke)
require.True(t, ok) require.True(t, ok)
assert.NotNil(t, res.Script) assert.Nil(t, res.Script)
assert.Equal(t, "HALT", res.State) assert.Equal(t, "HALT", res.State)
assert.NotEqual(t, 0, res.GasConsumed) assert.NotEqual(t, 0, res.GasConsumed)
assert.Equal(t, true, res.Stack[0].Value().(bool)) 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", name: "unknown contract",
params: fmt.Sprintf(`["%s", []]`, util.Uint160{}.String()), 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) require.NoErrorf(t, err, "could not parse response: %s", txOut)
assert.Equal(t, *block.Transactions[0], actual.Transaction) 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()) 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)) require.NoError(t, json.Unmarshal(res, actual))
checkNep17TransfersAux(t, e, actual, sent, rcvd) 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("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", 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{5}, []int{1}) }) 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{4, 5}, []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{6, 7}, []int{2}) }) 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(), Asset: e.chain.UtilityTokenHash(),
Amount: "78994302340", Amount: "68992647820",
LastUpdated: 8, LastUpdated: 10,
}}, }},
Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(), 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{}) { 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) { 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) rublesHash, err := util.Uint160DecodeStringLE(testContractHash)
require.NoError(t, err) 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)) blockDepositGAS, err := e.chain.GetBlock(e.chain.GetHeaderHash(8))
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 1, len(blockDepositGAS.Transactions)) require.Equal(t, 1, len(blockDepositGAS.Transactions))
txDepositGAS := blockDepositGAS.Transactions[0] 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.NoError(t, err)
require.Equal(t, 1, len(blockDeploy2.Transactions)) require.Equal(t, 1, len(blockDeploy2.Transactions))
txDeploy2 := blockDeploy2.Transactions[0] txDeploy2 := blockDeploy2.Transactions[0]
@ -1670,6 +1727,14 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rc
// duplicate the Server method. // duplicate the Server method.
expected := result.NEP17Transfers{ expected := result.NEP17Transfers{
Sent: []result.NEP17Transfer{ 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, Timestamp: blockDepositGAS.Timestamp,
Asset: e.chain.UtilityTokenHash(), Asset: e.chain.UtilityTokenHash(),

Binary file not shown.

View file

@ -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
}