Merge pull request #2548 from nspcc-dev/shuffle-tests
Shuffle tests and some other code
This commit is contained in:
commit
9919fd2cc9
46 changed files with 2157 additions and 2037 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -46,6 +46,7 @@ examples/*/*.json
|
|||
# Fuzzing testdata.
|
||||
testdata/
|
||||
!cli/testdata
|
||||
!internal/basicchain/testdata
|
||||
!pkg/compiler/testdata
|
||||
!pkg/config/testdata
|
||||
!pkg/consensus/testdata
|
||||
|
|
244
internal/basicchain/basic.go
Normal file
244
internal/basicchain/basic.go
Normal file
|
@ -0,0 +1,244 @@
|
|||
package basicchain
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpc/client/nns"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const neoAmount = 99999000
|
||||
|
||||
// Init pushes some predefined set of transactions into the given chain, it needs a path to
|
||||
// the root project directory.
|
||||
func Init(t *testing.T, rootpath string, e *neotest.Executor) {
|
||||
if !e.Chain.GetConfig().P2PSigExtensions {
|
||||
t.Fatal("P2PSigExtensions should be enabled to init basic chain")
|
||||
}
|
||||
|
||||
var (
|
||||
// examplesPrefix is a prefix of the example smart-contracts.
|
||||
examplesPrefix = filepath.Join(rootpath, "examples")
|
||||
// testDataPrefix is used to retrieve smart contracts that should be deployed to
|
||||
// Basic chain.
|
||||
testDataPrefix = filepath.Join(rootpath, "internal", "basicchain", "testdata")
|
||||
notaryModulePath = filepath.Join(rootpath, "pkg", "services", "notary")
|
||||
)
|
||||
|
||||
gasHash := e.NativeHash(t, nativenames.Gas)
|
||||
neoHash := e.NativeHash(t, nativenames.Neo)
|
||||
policyHash := e.NativeHash(t, nativenames.Policy)
|
||||
notaryHash := e.NativeHash(t, nativenames.Notary)
|
||||
designationHash := e.NativeHash(t, nativenames.Designation)
|
||||
t.Logf("native GAS hash: %v", gasHash)
|
||||
t.Logf("native NEO hash: %v", neoHash)
|
||||
t.Logf("native Policy hash: %v", policyHash)
|
||||
t.Logf("native Notary hash: %v", notaryHash)
|
||||
t.Logf("Block0 hash: %s", e.Chain.GetHeaderHash(0).StringLE())
|
||||
|
||||
acc0 := e.Validator.(neotest.MultiSigner).Single(2) // priv0 index->order and order->index conversion
|
||||
priv0ScriptHash := acc0.ScriptHash()
|
||||
acc1 := e.Validator.(neotest.MultiSigner).Single(0) // priv1 index->order and order->index conversion
|
||||
priv1ScriptHash := acc1.ScriptHash()
|
||||
neoValidatorInvoker := e.ValidatorInvoker(neoHash)
|
||||
gasValidatorInvoker := e.ValidatorInvoker(gasHash)
|
||||
neoPriv0Invoker := e.NewInvoker(neoHash, acc0)
|
||||
gasPriv0Invoker := e.NewInvoker(gasHash, acc0)
|
||||
designateSuperInvoker := e.NewInvoker(designationHash, e.Validator, e.Committee)
|
||||
|
||||
deployContractFromPriv0 := func(t *testing.T, path, contractName string, configPath string, expectedID int32) (util.Uint256, util.Uint256, util.Uint160) {
|
||||
txDeployHash, cH := newDeployTx(t, e, acc0, path, configPath, true)
|
||||
b := e.TopBlock(t)
|
||||
return b.Hash(), txDeployHash, cH
|
||||
}
|
||||
|
||||
e.CheckGASBalance(t, priv0ScriptHash, big.NewInt(5000_0000)) // gas bounty
|
||||
|
||||
// Block #1: move 1000 GAS and neoAmount NEO to priv0.
|
||||
txMoveNeo := neoValidatorInvoker.PrepareInvoke(t, "transfer", e.Validator.ScriptHash(), priv0ScriptHash, neoAmount, nil)
|
||||
txMoveGas := gasValidatorInvoker.PrepareInvoke(t, "transfer", e.Validator.ScriptHash(), priv0ScriptHash, int64(fixedn.Fixed8FromInt64(1000)), nil)
|
||||
b := e.AddNewBlock(t, txMoveNeo, txMoveGas)
|
||||
e.CheckHalt(t, txMoveNeo.Hash(), stackitem.Make(true))
|
||||
e.CheckHalt(t, txMoveGas.Hash(), stackitem.Make(true))
|
||||
t.Logf("Block1 hash: %s", b.Hash().StringLE())
|
||||
bw := io.NewBufBinWriter()
|
||||
b.EncodeBinary(bw.BinWriter)
|
||||
require.NoError(t, bw.Err)
|
||||
jsonB, err := b.MarshalJSON()
|
||||
require.NoError(t, err)
|
||||
t.Logf("Block1 base64: %s", base64.StdEncoding.EncodeToString(bw.Bytes()))
|
||||
t.Logf("Block1 JSON: %s", string(jsonB))
|
||||
bw.Reset()
|
||||
b.Header.EncodeBinary(bw.BinWriter)
|
||||
require.NoError(t, bw.Err)
|
||||
jsonH, err := b.Header.MarshalJSON()
|
||||
require.NoError(t, err)
|
||||
t.Logf("Header1 base64: %s", base64.StdEncoding.EncodeToString(bw.Bytes()))
|
||||
t.Logf("Header1 JSON: %s", string(jsonH))
|
||||
jsonTxMoveNeo, err := txMoveNeo.MarshalJSON()
|
||||
require.NoError(t, err)
|
||||
t.Logf("txMoveNeo hash: %s", txMoveNeo.Hash().StringLE())
|
||||
t.Logf("txMoveNeo JSON: %s", string(jsonTxMoveNeo))
|
||||
t.Logf("txMoveNeo base64: %s", base64.StdEncoding.EncodeToString(txMoveNeo.Bytes()))
|
||||
t.Logf("txMoveGas hash: %s", txMoveGas.Hash().StringLE())
|
||||
|
||||
e.EnsureGASBalance(t, priv0ScriptHash, func(balance *big.Int) bool { return balance.Cmp(big.NewInt(1000*native.GASFactor)) >= 0 })
|
||||
// info for getblockheader rpc tests
|
||||
t.Logf("header hash: %s", b.Hash().StringLE())
|
||||
buf := io.NewBufBinWriter()
|
||||
b.Header.EncodeBinary(buf.BinWriter)
|
||||
t.Logf("header: %s", hex.EncodeToString(buf.Bytes()))
|
||||
|
||||
// Block #2: deploy test_contract (Rubles contract).
|
||||
cfgPath := filepath.Join(testDataPrefix, "test_contract.yml")
|
||||
block2H, txDeployH, cHash := deployContractFromPriv0(t, filepath.Join(testDataPrefix, "test_contract.go"), "Rubl", cfgPath, 1)
|
||||
t.Logf("txDeploy: %s", txDeployH.StringLE())
|
||||
t.Logf("Block2 hash: %s", block2H.StringLE())
|
||||
|
||||
// Block #3: invoke `putValue` method on the test_contract.
|
||||
rublPriv0Invoker := e.NewInvoker(cHash, acc0)
|
||||
txInvH := rublPriv0Invoker.Invoke(t, true, "putValue", "testkey", "testvalue")
|
||||
t.Logf("txInv: %s", txInvH.StringLE())
|
||||
|
||||
// Block #4: transfer 1000 NEO from priv0 to priv1.
|
||||
neoPriv0Invoker.Invoke(t, true, "transfer", priv0ScriptHash, priv1ScriptHash, 1000, nil)
|
||||
|
||||
// Block #5: initialize rubles contract and transfer 1000 rubles from the contract to priv0.
|
||||
initTx := rublPriv0Invoker.PrepareInvoke(t, "init")
|
||||
transferTx := e.NewUnsignedTx(t, rublPriv0Invoker.Hash, "transfer", cHash, priv0ScriptHash, 1000, nil)
|
||||
e.SignTx(t, transferTx, 1500_0000, acc0) // Set system fee manually to avoid verification failure.
|
||||
e.AddNewBlock(t, initTx, transferTx)
|
||||
e.CheckHalt(t, initTx.Hash(), stackitem.NewBool(true))
|
||||
e.CheckHalt(t, transferTx.Hash(), stackitem.Make(true))
|
||||
t.Logf("receiveRublesTx: %v", transferTx.Hash().StringLE())
|
||||
|
||||
// Block #6: transfer 123 rubles from priv0 to priv1
|
||||
transferTxH := rublPriv0Invoker.Invoke(t, true, "transfer", priv0ScriptHash, priv1ScriptHash, 123, nil)
|
||||
t.Logf("sendRublesTx: %v", transferTxH.StringLE())
|
||||
|
||||
// Block #7: push verification contract into the chain.
|
||||
verifyPath := filepath.Join(testDataPrefix, "verify", "verification_contract.go")
|
||||
verifyCfg := filepath.Join(testDataPrefix, "verify", "verification_contract.yml")
|
||||
_, _, _ = deployContractFromPriv0(t, verifyPath, "Verify", verifyCfg, 2)
|
||||
|
||||
// Block #8: deposit some GAS to notary contract for priv0.
|
||||
transferTxH = gasPriv0Invoker.Invoke(t, true, "transfer", priv0ScriptHash, notaryHash, 10_0000_0000, []interface{}{priv0ScriptHash, int64(e.Chain.BlockHeight() + 1000)})
|
||||
t.Logf("notaryDepositTxPriv0: %v", transferTxH.StringLE())
|
||||
|
||||
// Block #9: designate new Notary node.
|
||||
ntr, err := wallet.NewWalletFromFile(path.Join(notaryModulePath, "./testdata/notary1.json"))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, ntr.Accounts[0].Decrypt("one", ntr.Scrypt))
|
||||
designateSuperInvoker.Invoke(t, stackitem.Null{}, "designateAsRole",
|
||||
int64(noderoles.P2PNotary), []interface{}{ntr.Accounts[0].PrivateKey().PublicKey().Bytes()})
|
||||
t.Logf("Designated Notary node: %s", hex.EncodeToString(ntr.Accounts[0].PrivateKey().PublicKey().Bytes()))
|
||||
|
||||
// Block #10: push verification contract with arguments into the chain.
|
||||
verifyPath = filepath.Join(testDataPrefix, "verify_args", "verification_with_args_contract.go")
|
||||
verifyCfg = filepath.Join(testDataPrefix, "verify_args", "verification_with_args_contract.yml")
|
||||
_, _, _ = deployContractFromPriv0(t, verifyPath, "VerifyWithArgs", verifyCfg, 3) // block #10
|
||||
|
||||
// Block #11: push NameService contract into the chain.
|
||||
nsPath := filepath.Join(examplesPrefix, "nft-nd-nns")
|
||||
nsConfigPath := filepath.Join(nsPath, "nns.yml")
|
||||
_, _, nsHash := deployContractFromPriv0(t, nsPath, nsPath, nsConfigPath, 4) // block #11
|
||||
nsCommitteeInvoker := e.CommitteeInvoker(nsHash)
|
||||
nsPriv0Invoker := e.NewInvoker(nsHash, acc0)
|
||||
|
||||
// Block #12: transfer funds to committee for further NS record registration.
|
||||
gasValidatorInvoker.Invoke(t, true, "transfer",
|
||||
e.Validator.ScriptHash(), e.Committee.ScriptHash(), 1000_00000000, nil) // block #12
|
||||
|
||||
// Block #13: add `.com` root to NNS.
|
||||
nsCommitteeInvoker.Invoke(t, stackitem.Null{}, "addRoot", "com") // block #13
|
||||
|
||||
// Block #14: register `neo.com` via NNS.
|
||||
registerTxH := nsPriv0Invoker.Invoke(t, true, "register",
|
||||
"neo.com", priv0ScriptHash) // block #14
|
||||
res := e.GetTxExecResult(t, registerTxH)
|
||||
require.Equal(t, 1, len(res.Events)) // transfer
|
||||
tokenID, err := res.Events[0].Item.Value().([]stackitem.Item)[3].TryBytes()
|
||||
require.NoError(t, err)
|
||||
t.Logf("NNS token #1 ID (hex): %s", hex.EncodeToString(tokenID))
|
||||
|
||||
// Block #15: set A record type with priv0 owner via NNS.
|
||||
nsPriv0Invoker.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4") // block #15
|
||||
|
||||
// Block #16: invoke `test_contract.go`: put new value with the same key to check `getstate` RPC call
|
||||
txPutNewValue := rublPriv0Invoker.PrepareInvoke(t, "putValue", "testkey", "newtestvalue") // tx1
|
||||
// Invoke `test_contract.go`: put values to check `findstates` RPC call.
|
||||
txPut1 := rublPriv0Invoker.PrepareInvoke(t, "putValue", "aa", "v1") // tx2
|
||||
txPut2 := rublPriv0Invoker.PrepareInvoke(t, "putValue", "aa10", "v2") // tx3
|
||||
txPut3 := rublPriv0Invoker.PrepareInvoke(t, "putValue", "aa50", "v3") // tx4
|
||||
e.AddNewBlock(t, txPutNewValue, txPut1, txPut2, txPut3) // block #16
|
||||
e.CheckHalt(t, txPutNewValue.Hash(), stackitem.NewBool(true))
|
||||
e.CheckHalt(t, txPut1.Hash(), stackitem.NewBool(true))
|
||||
e.CheckHalt(t, txPut2.Hash(), stackitem.NewBool(true))
|
||||
e.CheckHalt(t, txPut3.Hash(), stackitem.NewBool(true))
|
||||
|
||||
// Block #17: deploy NeoFS Object contract (NEP11-Divisible).
|
||||
nfsPath := filepath.Join(examplesPrefix, "nft-d")
|
||||
nfsConfigPath := filepath.Join(nfsPath, "nft.yml")
|
||||
_, _, nfsHash := deployContractFromPriv0(t, nfsPath, nfsPath, nfsConfigPath, 5) // block #17
|
||||
nfsPriv0Invoker := e.NewInvoker(nfsHash, acc0)
|
||||
nfsPriv1Invoker := e.NewInvoker(nfsHash, acc1)
|
||||
|
||||
// Block #18: mint 1.00 NFSO token by transferring 10 GAS to NFSO contract.
|
||||
containerID := util.Uint256{1, 2, 3}
|
||||
objectID := util.Uint256{4, 5, 6}
|
||||
txGas0toNFSH := gasPriv0Invoker.Invoke(t, true, "transfer",
|
||||
priv0ScriptHash, nfsHash, 10_0000_0000, []interface{}{containerID.BytesBE(), objectID.BytesBE()}) // block #18
|
||||
res = e.GetTxExecResult(t, txGas0toNFSH)
|
||||
require.Equal(t, 2, len(res.Events)) // GAS transfer + NFSO transfer
|
||||
tokenID, err = res.Events[1].Item.Value().([]stackitem.Item)[3].TryBytes()
|
||||
require.NoError(t, err)
|
||||
t.Logf("NFSO token #1 ID (hex): %s", hex.EncodeToString(tokenID))
|
||||
|
||||
// Block #19: transfer 0.25 NFSO from priv0 to priv1.
|
||||
nfsPriv0Invoker.Invoke(t, true, "transfer", priv0ScriptHash, priv1ScriptHash, 25, tokenID, nil) // block #19
|
||||
|
||||
// Block #20: transfer 1000 GAS to priv1.
|
||||
gasValidatorInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(),
|
||||
priv1ScriptHash, int64(fixedn.Fixed8FromInt64(1000)), nil) // block #20
|
||||
|
||||
// Block #21: transfer 0.05 NFSO from priv1 back to priv0.
|
||||
nfsPriv1Invoker.Invoke(t, true, "transfer", priv1ScriptHash, priv0ScriptHash, 5, tokenID, nil) // block #21
|
||||
|
||||
// Compile contract to test `invokescript` RPC call
|
||||
invokePath := filepath.Join(testDataPrefix, "invoke", "invokescript_contract.go")
|
||||
invokeCfg := filepath.Join(testDataPrefix, "invoke", "invoke.yml")
|
||||
_, _ = newDeployTx(t, e, acc0, invokePath, invokeCfg, false)
|
||||
|
||||
// Prepare some transaction for future submission.
|
||||
txSendRaw := neoPriv0Invoker.PrepareInvoke(t, "transfer", priv0ScriptHash, priv1ScriptHash, int64(fixedn.Fixed8FromInt64(1000)), nil)
|
||||
bw.Reset()
|
||||
txSendRaw.EncodeBinary(bw.BinWriter)
|
||||
t.Logf("sendrawtransaction: \n\tbase64: %s\n\tHash LE: %s", base64.StdEncoding.EncodeToString(bw.Bytes()), txSendRaw.Hash().StringLE())
|
||||
|
||||
sr20, err := e.Chain.GetStateModule().GetStateRoot(20)
|
||||
require.NoError(t, err)
|
||||
t.Logf("Block #20 stateroot LE: %s", sr20.Root.StringLE())
|
||||
}
|
||||
|
||||
func newDeployTx(t *testing.T, e *neotest.Executor, sender neotest.Signer, sourcePath, configPath string, deploy bool) (util.Uint256, util.Uint160) {
|
||||
c := neotest.CompileFile(t, sender.ScriptHash(), sourcePath, configPath)
|
||||
t.Logf("contract (%s): \n\tHash: %s\n\tAVM: %s", sourcePath, c.Hash.StringLE(), base64.StdEncoding.EncodeToString(c.NEF.Script))
|
||||
if deploy {
|
||||
return e.DeployContractBy(t, sender, c, nil), c.Hash
|
||||
}
|
||||
return util.Uint256{}, c.Hash
|
||||
}
|
|
@ -1,34 +1,21 @@
|
|||
package core_test
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/internal/basicchain"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/chaindump"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpc/client/nns"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
// examplesPrefix is a prefix of the example smart-contracts.
|
||||
examplesPrefix = "../../examples/"
|
||||
// basicChainPrefix is a prefix used to store Basic chain .acc file for tests.
|
||||
// It is also used to retrieve smart contracts that should be deployed to
|
||||
// Basic chain.
|
||||
|
@ -39,7 +26,6 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
notaryModulePath = filepath.Join("..", "services", "notary")
|
||||
pathToInternalContracts = filepath.Join("..", "..", "internal", "contracts")
|
||||
)
|
||||
|
||||
|
@ -56,7 +42,7 @@ func TestCreateBasicChain(t *testing.T) {
|
|||
})
|
||||
e := neotest.NewExecutor(t, bc, validators, committee)
|
||||
|
||||
initBasicChain(t, e)
|
||||
basicchain.Init(t, "../../", e)
|
||||
|
||||
if saveChain {
|
||||
outStream, err := os.Create(basicChainPrefix + "testblocks.acc")
|
||||
|
@ -73,214 +59,3 @@ func TestCreateBasicChain(t *testing.T) {
|
|||
|
||||
require.False(t, saveChain)
|
||||
}
|
||||
|
||||
func initBasicChain(t *testing.T, e *neotest.Executor) {
|
||||
if !e.Chain.GetConfig().P2PSigExtensions {
|
||||
t.Fatal("P2PSigExtensions should be enabled to init basic chain")
|
||||
}
|
||||
|
||||
const neoAmount = 99999000
|
||||
|
||||
gasHash := e.NativeHash(t, nativenames.Gas)
|
||||
neoHash := e.NativeHash(t, nativenames.Neo)
|
||||
policyHash := e.NativeHash(t, nativenames.Policy)
|
||||
notaryHash := e.NativeHash(t, nativenames.Notary)
|
||||
designationHash := e.NativeHash(t, nativenames.Designation)
|
||||
t.Logf("native GAS hash: %v", gasHash)
|
||||
t.Logf("native NEO hash: %v", neoHash)
|
||||
t.Logf("native Policy hash: %v", policyHash)
|
||||
t.Logf("native Notary hash: %v", notaryHash)
|
||||
t.Logf("Block0 hash: %s", e.Chain.GetHeaderHash(0).StringLE())
|
||||
|
||||
acc0 := e.Validator.(neotest.MultiSigner).Single(2) // priv0 index->order and order->index conversion
|
||||
priv0ScriptHash := acc0.ScriptHash()
|
||||
acc1 := e.Validator.(neotest.MultiSigner).Single(0) // priv1 index->order and order->index conversion
|
||||
priv1ScriptHash := acc1.ScriptHash()
|
||||
neoValidatorInvoker := e.ValidatorInvoker(neoHash)
|
||||
gasValidatorInvoker := e.ValidatorInvoker(gasHash)
|
||||
neoPriv0Invoker := e.NewInvoker(neoHash, acc0)
|
||||
gasPriv0Invoker := e.NewInvoker(gasHash, acc0)
|
||||
designateSuperInvoker := e.NewInvoker(designationHash, e.Validator, e.Committee)
|
||||
|
||||
deployContractFromPriv0 := func(t *testing.T, path, contractName string, configPath string, expectedID int32) (util.Uint256, util.Uint256, util.Uint160) {
|
||||
txDeployHash, cH := newDeployTx(t, e, acc0, path, configPath, true)
|
||||
b := e.TopBlock(t)
|
||||
return b.Hash(), txDeployHash, cH
|
||||
}
|
||||
|
||||
e.CheckGASBalance(t, priv0ScriptHash, big.NewInt(5000_0000)) // gas bounty
|
||||
|
||||
// Block #1: move 1000 GAS and neoAmount NEO to priv0.
|
||||
txMoveNeo := neoValidatorInvoker.PrepareInvoke(t, "transfer", e.Validator.ScriptHash(), priv0ScriptHash, neoAmount, nil)
|
||||
txMoveGas := gasValidatorInvoker.PrepareInvoke(t, "transfer", e.Validator.ScriptHash(), priv0ScriptHash, int64(fixedn.Fixed8FromInt64(1000)), nil)
|
||||
b := e.AddNewBlock(t, txMoveNeo, txMoveGas)
|
||||
e.CheckHalt(t, txMoveNeo.Hash(), stackitem.Make(true))
|
||||
e.CheckHalt(t, txMoveGas.Hash(), stackitem.Make(true))
|
||||
t.Logf("Block1 hash: %s", b.Hash().StringLE())
|
||||
bw := io.NewBufBinWriter()
|
||||
b.EncodeBinary(bw.BinWriter)
|
||||
require.NoError(t, bw.Err)
|
||||
jsonB, err := b.MarshalJSON()
|
||||
require.NoError(t, err)
|
||||
t.Logf("Block1 base64: %s", base64.StdEncoding.EncodeToString(bw.Bytes()))
|
||||
t.Logf("Block1 JSON: %s", string(jsonB))
|
||||
bw.Reset()
|
||||
b.Header.EncodeBinary(bw.BinWriter)
|
||||
require.NoError(t, bw.Err)
|
||||
jsonH, err := b.Header.MarshalJSON()
|
||||
require.NoError(t, err)
|
||||
t.Logf("Header1 base64: %s", base64.StdEncoding.EncodeToString(bw.Bytes()))
|
||||
t.Logf("Header1 JSON: %s", string(jsonH))
|
||||
jsonTxMoveNeo, err := txMoveNeo.MarshalJSON()
|
||||
require.NoError(t, err)
|
||||
t.Logf("txMoveNeo hash: %s", txMoveNeo.Hash().StringLE())
|
||||
t.Logf("txMoveNeo JSON: %s", string(jsonTxMoveNeo))
|
||||
t.Logf("txMoveNeo base64: %s", base64.StdEncoding.EncodeToString(txMoveNeo.Bytes()))
|
||||
t.Logf("txMoveGas hash: %s", txMoveGas.Hash().StringLE())
|
||||
|
||||
e.EnsureGASBalance(t, priv0ScriptHash, func(balance *big.Int) bool { return balance.Cmp(big.NewInt(1000*native.GASFactor)) >= 0 })
|
||||
// info for getblockheader rpc tests
|
||||
t.Logf("header hash: %s", b.Hash().StringLE())
|
||||
buf := io.NewBufBinWriter()
|
||||
b.Header.EncodeBinary(buf.BinWriter)
|
||||
t.Logf("header: %s", hex.EncodeToString(buf.Bytes()))
|
||||
|
||||
// Block #2: deploy test_contract (Rubles contract).
|
||||
cfgPath := basicChainPrefix + "test_contract.yml"
|
||||
block2H, txDeployH, cHash := deployContractFromPriv0(t, basicChainPrefix+"test_contract.go", "Rubl", cfgPath, 1)
|
||||
t.Logf("txDeploy: %s", txDeployH.StringLE())
|
||||
t.Logf("Block2 hash: %s", block2H.StringLE())
|
||||
|
||||
// Block #3: invoke `putValue` method on the test_contract.
|
||||
rublPriv0Invoker := e.NewInvoker(cHash, acc0)
|
||||
txInvH := rublPriv0Invoker.Invoke(t, true, "putValue", "testkey", "testvalue")
|
||||
t.Logf("txInv: %s", txInvH.StringLE())
|
||||
|
||||
// Block #4: transfer 1000 NEO from priv0 to priv1.
|
||||
neoPriv0Invoker.Invoke(t, true, "transfer", priv0ScriptHash, priv1ScriptHash, 1000, nil)
|
||||
|
||||
// Block #5: initialize rubles contract and transfer 1000 rubles from the contract to priv0.
|
||||
initTx := rublPriv0Invoker.PrepareInvoke(t, "init")
|
||||
transferTx := e.NewUnsignedTx(t, rublPriv0Invoker.Hash, "transfer", cHash, priv0ScriptHash, 1000, nil)
|
||||
e.SignTx(t, transferTx, 1500_0000, acc0) // Set system fee manually to avoid verification failure.
|
||||
e.AddNewBlock(t, initTx, transferTx)
|
||||
e.CheckHalt(t, initTx.Hash(), stackitem.NewBool(true))
|
||||
e.CheckHalt(t, transferTx.Hash(), stackitem.Make(true))
|
||||
t.Logf("receiveRublesTx: %v", transferTx.Hash().StringLE())
|
||||
|
||||
// Block #6: transfer 123 rubles from priv0 to priv1
|
||||
transferTxH := rublPriv0Invoker.Invoke(t, true, "transfer", priv0ScriptHash, priv1ScriptHash, 123, nil)
|
||||
t.Logf("sendRublesTx: %v", transferTxH.StringLE())
|
||||
|
||||
// Block #7: push verification contract into the chain.
|
||||
verifyPath := filepath.Join(basicChainPrefix, "verify", "verification_contract.go")
|
||||
verifyCfg := filepath.Join(basicChainPrefix, "verify", "verification_contract.yml")
|
||||
_, _, _ = deployContractFromPriv0(t, verifyPath, "Verify", verifyCfg, 2)
|
||||
|
||||
// Block #8: deposit some GAS to notary contract for priv0.
|
||||
transferTxH = gasPriv0Invoker.Invoke(t, true, "transfer", priv0ScriptHash, notaryHash, 10_0000_0000, []interface{}{priv0ScriptHash, int64(e.Chain.BlockHeight() + 1000)})
|
||||
t.Logf("notaryDepositTxPriv0: %v", transferTxH.StringLE())
|
||||
|
||||
// Block #9: designate new Notary node.
|
||||
ntr, err := wallet.NewWalletFromFile(path.Join(notaryModulePath, "./testdata/notary1.json"))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, ntr.Accounts[0].Decrypt("one", ntr.Scrypt))
|
||||
designateSuperInvoker.Invoke(t, stackitem.Null{}, "designateAsRole",
|
||||
int64(noderoles.P2PNotary), []interface{}{ntr.Accounts[0].PrivateKey().PublicKey().Bytes()})
|
||||
t.Logf("Designated Notary node: %s", hex.EncodeToString(ntr.Accounts[0].PrivateKey().PublicKey().Bytes()))
|
||||
|
||||
// Block #10: push verification contract with arguments into the chain.
|
||||
verifyPath = filepath.Join(basicChainPrefix, "verify_args", "verification_with_args_contract.go")
|
||||
verifyCfg = filepath.Join(basicChainPrefix, "verify_args", "verification_with_args_contract.yml")
|
||||
_, _, _ = deployContractFromPriv0(t, verifyPath, "VerifyWithArgs", verifyCfg, 3) // block #10
|
||||
|
||||
// Block #11: push NameService contract into the chain.
|
||||
nsPath := examplesPrefix + "nft-nd-nns/"
|
||||
nsConfigPath := nsPath + "nns.yml"
|
||||
_, _, nsHash := deployContractFromPriv0(t, nsPath, nsPath, nsConfigPath, 4) // block #11
|
||||
nsCommitteeInvoker := e.CommitteeInvoker(nsHash)
|
||||
nsPriv0Invoker := e.NewInvoker(nsHash, acc0)
|
||||
|
||||
// Block #12: transfer funds to committee for further NS record registration.
|
||||
gasValidatorInvoker.Invoke(t, true, "transfer",
|
||||
e.Validator.ScriptHash(), e.Committee.ScriptHash(), 1000_00000000, nil) // block #12
|
||||
|
||||
// Block #13: add `.com` root to NNS.
|
||||
nsCommitteeInvoker.Invoke(t, stackitem.Null{}, "addRoot", "com") // block #13
|
||||
|
||||
// Block #14: register `neo.com` via NNS.
|
||||
registerTxH := nsPriv0Invoker.Invoke(t, true, "register",
|
||||
"neo.com", priv0ScriptHash) // block #14
|
||||
res := e.GetTxExecResult(t, registerTxH)
|
||||
require.Equal(t, 1, len(res.Events)) // transfer
|
||||
tokenID, err := res.Events[0].Item.Value().([]stackitem.Item)[3].TryBytes()
|
||||
require.NoError(t, err)
|
||||
t.Logf("NNS token #1 ID (hex): %s", hex.EncodeToString(tokenID))
|
||||
|
||||
// Block #15: set A record type with priv0 owner via NNS.
|
||||
nsPriv0Invoker.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4") // block #15
|
||||
|
||||
// Block #16: invoke `test_contract.go`: put new value with the same key to check `getstate` RPC call
|
||||
txPutNewValue := rublPriv0Invoker.PrepareInvoke(t, "putValue", "testkey", "newtestvalue") // tx1
|
||||
// Invoke `test_contract.go`: put values to check `findstates` RPC call.
|
||||
txPut1 := rublPriv0Invoker.PrepareInvoke(t, "putValue", "aa", "v1") // tx2
|
||||
txPut2 := rublPriv0Invoker.PrepareInvoke(t, "putValue", "aa10", "v2") // tx3
|
||||
txPut3 := rublPriv0Invoker.PrepareInvoke(t, "putValue", "aa50", "v3") // tx4
|
||||
e.AddNewBlock(t, txPutNewValue, txPut1, txPut2, txPut3) // block #16
|
||||
e.CheckHalt(t, txPutNewValue.Hash(), stackitem.NewBool(true))
|
||||
e.CheckHalt(t, txPut1.Hash(), stackitem.NewBool(true))
|
||||
e.CheckHalt(t, txPut2.Hash(), stackitem.NewBool(true))
|
||||
e.CheckHalt(t, txPut3.Hash(), stackitem.NewBool(true))
|
||||
|
||||
// Block #17: deploy NeoFS Object contract (NEP11-Divisible).
|
||||
nfsPath := examplesPrefix + "nft-d/"
|
||||
nfsConfigPath := nfsPath + "nft.yml"
|
||||
_, _, nfsHash := deployContractFromPriv0(t, nfsPath, nfsPath, nfsConfigPath, 5) // block #17
|
||||
nfsPriv0Invoker := e.NewInvoker(nfsHash, acc0)
|
||||
nfsPriv1Invoker := e.NewInvoker(nfsHash, acc1)
|
||||
|
||||
// Block #18: mint 1.00 NFSO token by transferring 10 GAS to NFSO contract.
|
||||
containerID := util.Uint256{1, 2, 3}
|
||||
objectID := util.Uint256{4, 5, 6}
|
||||
txGas0toNFSH := gasPriv0Invoker.Invoke(t, true, "transfer",
|
||||
priv0ScriptHash, nfsHash, 10_0000_0000, []interface{}{containerID.BytesBE(), objectID.BytesBE()}) // block #18
|
||||
res = e.GetTxExecResult(t, txGas0toNFSH)
|
||||
require.Equal(t, 2, len(res.Events)) // GAS transfer + NFSO transfer
|
||||
tokenID, err = res.Events[1].Item.Value().([]stackitem.Item)[3].TryBytes()
|
||||
require.NoError(t, err)
|
||||
t.Logf("NFSO token #1 ID (hex): %s", hex.EncodeToString(tokenID))
|
||||
|
||||
// Block #19: transfer 0.25 NFSO from priv0 to priv1.
|
||||
nfsPriv0Invoker.Invoke(t, true, "transfer", priv0ScriptHash, priv1ScriptHash, 25, tokenID, nil) // block #19
|
||||
|
||||
// Block #20: transfer 1000 GAS to priv1.
|
||||
gasValidatorInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(),
|
||||
priv1ScriptHash, int64(fixedn.Fixed8FromInt64(1000)), nil) // block #20
|
||||
|
||||
// Block #21: transfer 0.05 NFSO from priv1 back to priv0.
|
||||
nfsPriv1Invoker.Invoke(t, true, "transfer", priv1ScriptHash, priv0ScriptHash, 5, tokenID, nil) // block #21
|
||||
|
||||
// Compile contract to test `invokescript` RPC call
|
||||
invokePath := filepath.Join(basicChainPrefix, "invoke", "invokescript_contract.go")
|
||||
invokeCfg := filepath.Join(basicChainPrefix, "invoke", "invoke.yml")
|
||||
_, _ = newDeployTx(t, e, acc0, invokePath, invokeCfg, false)
|
||||
|
||||
// Prepare some transaction for future submission.
|
||||
txSendRaw := neoPriv0Invoker.PrepareInvoke(t, "transfer", priv0ScriptHash, priv1ScriptHash, int64(fixedn.Fixed8FromInt64(1000)), nil)
|
||||
bw.Reset()
|
||||
txSendRaw.EncodeBinary(bw.BinWriter)
|
||||
t.Logf("sendrawtransaction: \n\tbase64: %s\n\tHash LE: %s", base64.StdEncoding.EncodeToString(bw.Bytes()), txSendRaw.Hash().StringLE())
|
||||
|
||||
sr20, err := e.Chain.GetStateModule().GetStateRoot(20)
|
||||
require.NoError(t, err)
|
||||
t.Logf("Block #20 stateroot LE: %s", sr20.Root.StringLE())
|
||||
}
|
||||
|
||||
func newDeployTx(t *testing.T, e *neotest.Executor, sender neotest.Signer, sourcePath, configPath string, deploy bool) (util.Uint256, util.Uint160) {
|
||||
c := neotest.CompileFile(t, sender.ScriptHash(), sourcePath, configPath)
|
||||
t.Logf("contract (%s): \n\tHash: %s\n\tAVM: %s", sourcePath, c.Hash.StringLE(), base64.StdEncoding.EncodeToString(c.NEF.Script))
|
||||
if deploy {
|
||||
return e.DeployContractBy(t, sender, c, nil), c.Hash
|
||||
}
|
||||
return util.Uint256{}, c.Hash
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@ package core_test
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/internal/random"
|
||||
|
@ -10,12 +12,14 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"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"
|
||||
)
|
||||
|
||||
|
@ -54,6 +58,27 @@ func BenchmarkBlockchain_ForEachNEP17Transfer(t *testing.B) {
|
|||
}
|
||||
}
|
||||
|
||||
func BenchmarkNEO_GetGASPerVote(t *testing.B) {
|
||||
var stores = map[string]func(testing.TB) storage.Store{
|
||||
"MemPS": func(t testing.TB) storage.Store {
|
||||
return storage.NewMemoryStore()
|
||||
},
|
||||
"BoltPS": newBoltStoreForTesting,
|
||||
"LevelPS": newLevelDBForTesting,
|
||||
}
|
||||
for psName, newPS := range stores {
|
||||
for nRewardRecords := 10; nRewardRecords <= 1000; nRewardRecords *= 10 {
|
||||
for rewardDistance := 1; rewardDistance <= 1000; rewardDistance *= 10 {
|
||||
t.Run(fmt.Sprintf("%s_%dRewardRecords_%dRewardDistance", psName, nRewardRecords, rewardDistance), func(t *testing.B) {
|
||||
ps := newPS(t)
|
||||
t.Cleanup(func() { ps.Close() })
|
||||
benchmarkGasPerVote(t, ps, nRewardRecords, rewardDistance)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkForEachNEP17Transfer(t *testing.B, ps storage.Store, startFromBlock, nBlocksToTake int) {
|
||||
var (
|
||||
chainHeight = 2_100 // constant chain height to be able to compare paging results
|
||||
|
@ -108,3 +133,90 @@ func benchmarkForEachNEP17Transfer(t *testing.B, ps storage.Store, startFromBloc
|
|||
}
|
||||
t.StopTimer()
|
||||
}
|
||||
|
||||
func newLevelDBForTesting(t testing.TB) storage.Store {
|
||||
dbPath := t.TempDir()
|
||||
dbOptions := storage.LevelDBOptions{
|
||||
DataDirectoryPath: dbPath,
|
||||
}
|
||||
newLevelStore, err := storage.NewLevelDBStore(dbOptions)
|
||||
require.Nil(t, err, "NewLevelDBStore error")
|
||||
return newLevelStore
|
||||
}
|
||||
|
||||
func newBoltStoreForTesting(t testing.TB) storage.Store {
|
||||
d := t.TempDir()
|
||||
dbPath := filepath.Join(d, "test_bolt_db")
|
||||
boltDBStore, err := storage.NewBoltDBStore(storage.BoltDBOptions{FilePath: dbPath})
|
||||
require.NoError(t, err)
|
||||
return boltDBStore
|
||||
}
|
||||
|
||||
func benchmarkGasPerVote(t *testing.B, ps storage.Store, nRewardRecords int, rewardDistance int) {
|
||||
bc, validators, committee := chain.NewMultiWithCustomConfigAndStore(t, nil, ps, true)
|
||||
cfg := bc.GetConfig()
|
||||
|
||||
e := neotest.NewExecutor(t, bc, validators, committee)
|
||||
neoHash := e.NativeHash(t, nativenames.Neo)
|
||||
gasHash := e.NativeHash(t, nativenames.Gas)
|
||||
neoSuperInvoker := e.NewInvoker(neoHash, validators, committee)
|
||||
neoValidatorsInvoker := e.ValidatorInvoker(neoHash)
|
||||
gasValidatorsInvoker := e.ValidatorInvoker(gasHash)
|
||||
|
||||
// Vote for new committee.
|
||||
sz := len(cfg.StandbyCommittee)
|
||||
voters := make([]*wallet.Account, sz)
|
||||
candidates := make(keys.PublicKeys, sz)
|
||||
txs := make([]*transaction.Transaction, 0, len(voters)*3)
|
||||
for i := 0; i < sz; i++ {
|
||||
priv, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
candidates[i] = priv.PublicKey()
|
||||
voters[i], err = wallet.NewAccount()
|
||||
require.NoError(t, err)
|
||||
registerTx := neoSuperInvoker.PrepareInvoke(t, "registerCandidate", candidates[i].Bytes())
|
||||
txs = append(txs, registerTx)
|
||||
|
||||
to := voters[i].Contract.ScriptHash()
|
||||
transferNeoTx := neoValidatorsInvoker.PrepareInvoke(t, "transfer", e.Validator.ScriptHash(), to, big.NewInt(int64(sz-i)*1000000).Int64(), nil)
|
||||
txs = append(txs, transferNeoTx)
|
||||
|
||||
transferGasTx := gasValidatorsInvoker.PrepareInvoke(t, "transfer", e.Validator.ScriptHash(), to, int64(1_000_000_000), nil)
|
||||
txs = append(txs, transferGasTx)
|
||||
}
|
||||
e.AddNewBlock(t, txs...)
|
||||
for _, tx := range txs {
|
||||
e.CheckHalt(t, tx.Hash())
|
||||
}
|
||||
voteTxs := make([]*transaction.Transaction, 0, sz)
|
||||
for i := 0; i < sz; i++ {
|
||||
priv := voters[i].PrivateKey()
|
||||
h := priv.GetScriptHash()
|
||||
voteTx := e.NewTx(t, []neotest.Signer{neotest.NewSingleSigner(voters[i])}, neoHash, "vote", h, candidates[i].Bytes())
|
||||
voteTxs = append(voteTxs, voteTx)
|
||||
}
|
||||
e.AddNewBlock(t, voteTxs...)
|
||||
for _, tx := range voteTxs {
|
||||
e.CheckHalt(t, tx.Hash())
|
||||
}
|
||||
|
||||
// Collect set of nRewardRecords reward records for each voter.
|
||||
e.GenerateNewBlocks(t, len(cfg.StandbyCommittee))
|
||||
|
||||
// Transfer some more NEO to first voter to update his balance height.
|
||||
to := voters[0].Contract.ScriptHash()
|
||||
neoValidatorsInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(), to, int64(1), nil)
|
||||
|
||||
// Advance chain one more time to avoid same start/end rewarding bounds.
|
||||
e.GenerateNewBlocks(t, rewardDistance)
|
||||
end := bc.BlockHeight()
|
||||
|
||||
t.ResetTimer()
|
||||
t.ReportAllocs()
|
||||
t.StartTimer()
|
||||
for i := 0; i < t.N; i++ {
|
||||
_, err := bc.CalculateClaimable(to, end)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
t.StopTimer()
|
||||
}
|
||||
|
|
|
@ -4,11 +4,13 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"math"
|
||||
"math/big"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -211,3 +213,18 @@ func (b *Block) GetExpectedBlockSizeWithoutTransactions(txCount int) int {
|
|||
}
|
||||
return size
|
||||
}
|
||||
|
||||
// ToStackItem converts Block to stackitem.Item.
|
||||
func (b *Block) ToStackItem() stackitem.Item {
|
||||
return stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.NewByteArray(b.Hash().BytesBE()),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(b.Version))),
|
||||
stackitem.NewByteArray(b.PrevHash.BytesBE()),
|
||||
stackitem.NewByteArray(b.MerkleRoot.BytesBE()),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(b.Timestamp))),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(b.Nonce)),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(b.Index))),
|
||||
stackitem.NewByteArray(b.NextConsensus.BytesBE()),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(len(b.Transactions)))),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -324,7 +324,7 @@ func (bc *Blockchain) init() error {
|
|||
bc.dao.PutVersion(ver)
|
||||
bc.dao.Version = ver
|
||||
bc.persistent.Version = ver
|
||||
genesisBlock, err := createGenesisBlock(bc.config)
|
||||
genesisBlock, err := CreateGenesisBlock(bc.config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -386,7 +386,7 @@ func (bc *Blockchain) init() error {
|
|||
if len(bc.headerHashes) > 0 {
|
||||
targetHash = bc.headerHashes[len(bc.headerHashes)-1]
|
||||
} else {
|
||||
genesisBlock, err := createGenesisBlock(bc.config)
|
||||
genesisBlock, err := CreateGenesisBlock(bc.config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/internal/basicchain"
|
||||
"github.com/nspcc-dev/neo-go/internal/contracts"
|
||||
"github.com/nspcc-dev/neo-go/internal/random"
|
||||
"github.com/nspcc-dev/neo-go/pkg/compiler"
|
||||
|
@ -17,7 +18,6 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/chaindump"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/fee"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
|
||||
|
@ -48,83 +48,6 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBlockchain_DumpAndRestore(t *testing.T) {
|
||||
t.Run("no state root", func(t *testing.T) {
|
||||
testDumpAndRestore(t, func(c *config.ProtocolConfiguration) {
|
||||
c.StateRootInHeader = false
|
||||
c.P2PSigExtensions = true
|
||||
}, nil)
|
||||
})
|
||||
t.Run("with state root", func(t *testing.T) {
|
||||
testDumpAndRestore(t, func(c *config.ProtocolConfiguration) {
|
||||
c.StateRootInHeader = true
|
||||
c.P2PSigExtensions = true
|
||||
}, nil)
|
||||
})
|
||||
t.Run("remove untraceable", func(t *testing.T) {
|
||||
// Dump can only be created if all blocks and transactions are present.
|
||||
testDumpAndRestore(t, func(c *config.ProtocolConfiguration) {
|
||||
c.P2PSigExtensions = true
|
||||
}, func(c *config.ProtocolConfiguration) {
|
||||
c.MaxTraceableBlocks = 2
|
||||
c.RemoveUntraceableBlocks = true
|
||||
c.P2PSigExtensions = true
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func testDumpAndRestore(t *testing.T, dumpF, restoreF func(c *config.ProtocolConfiguration)) {
|
||||
if restoreF == nil {
|
||||
restoreF = dumpF
|
||||
}
|
||||
|
||||
bc, validators, committee := chain.NewMultiWithCustomConfig(t, dumpF)
|
||||
e := neotest.NewExecutor(t, bc, validators, committee)
|
||||
|
||||
initBasicChain(t, e)
|
||||
require.True(t, bc.BlockHeight() > 5) // ensure that test is valid
|
||||
|
||||
w := io.NewBufBinWriter()
|
||||
require.NoError(t, chaindump.Dump(bc, w.BinWriter, 0, bc.BlockHeight()+1))
|
||||
require.NoError(t, w.Err)
|
||||
|
||||
buf := w.Bytes()
|
||||
t.Run("invalid start", func(t *testing.T) {
|
||||
bc2, _, _ := chain.NewMultiWithCustomConfig(t, restoreF)
|
||||
|
||||
r := io.NewBinReaderFromBuf(buf)
|
||||
require.Error(t, chaindump.Restore(bc2, r, 2, 1, nil))
|
||||
})
|
||||
t.Run("good", func(t *testing.T) {
|
||||
bc2, _, _ := chain.NewMultiWithCustomConfig(t, dumpF)
|
||||
|
||||
r := io.NewBinReaderFromBuf(buf)
|
||||
require.NoError(t, chaindump.Restore(bc2, r, 0, 2, nil))
|
||||
require.Equal(t, uint32(1), bc2.BlockHeight())
|
||||
|
||||
r = io.NewBinReaderFromBuf(buf) // new reader because start is relative to dump
|
||||
require.NoError(t, chaindump.Restore(bc2, r, 2, 1, nil))
|
||||
t.Run("check handler", func(t *testing.T) {
|
||||
lastIndex := uint32(0)
|
||||
errStopped := errors.New("stopped")
|
||||
f := func(b *block.Block) error {
|
||||
lastIndex = b.Index
|
||||
if b.Index >= bc.BlockHeight()-1 {
|
||||
return errStopped
|
||||
}
|
||||
return nil
|
||||
}
|
||||
require.NoError(t, chaindump.Restore(bc2, r, 0, 1, f))
|
||||
require.Equal(t, bc2.BlockHeight(), lastIndex)
|
||||
|
||||
r = io.NewBinReaderFromBuf(buf)
|
||||
err := chaindump.Restore(bc2, r, 4, bc.BlockHeight()-bc2.BlockHeight(), f)
|
||||
require.True(t, errors.Is(err, errStopped))
|
||||
require.Equal(t, bc.BlockHeight()-1, lastIndex)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func newLevelDBForTestingWithPath(t testing.TB, dbPath string) (storage.Store, string) {
|
||||
if dbPath == "" {
|
||||
dbPath = t.TempDir()
|
||||
|
@ -147,7 +70,7 @@ func TestBlockchain_StartFromExistingDB(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
go bc.Run()
|
||||
e := neotest.NewExecutor(t, bc, validators, committee)
|
||||
initBasicChain(t, e)
|
||||
basicchain.Init(t, "../../", e)
|
||||
require.True(t, bc.BlockHeight() > 5, "ensure that basic chain is correctly initialised")
|
||||
|
||||
// Information for further tests.
|
||||
|
@ -1407,7 +1330,7 @@ func TestBlockchain_VerifyTx(t *testing.T) {
|
|||
cInvoker := e.ValidatorInvoker(cs.Hash)
|
||||
|
||||
const gasForResponse int64 = 10_000_000
|
||||
putOracleRequest(t, cInvoker, "https://get.1234", new(string), "handle", []byte{}, gasForResponse)
|
||||
cInvoker.Invoke(t, stackitem.Null{}, "requestURL", "https://get.1234", "", "handle", []byte{}, gasForResponse)
|
||||
|
||||
oracleScript, err := smartcontract.CreateMajorityMultiSigRedeemScript(oraclePubs)
|
||||
require.NoError(t, err)
|
||||
|
|
92
pkg/core/chaindump/dump_test.go
Normal file
92
pkg/core/chaindump/dump_test.go
Normal file
|
@ -0,0 +1,92 @@
|
|||
package chaindump_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/internal/basicchain"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/chaindump"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBlockchain_DumpAndRestore(t *testing.T) {
|
||||
t.Run("no state root", func(t *testing.T) {
|
||||
testDumpAndRestore(t, func(c *config.ProtocolConfiguration) {
|
||||
c.StateRootInHeader = false
|
||||
c.P2PSigExtensions = true
|
||||
}, nil)
|
||||
})
|
||||
t.Run("with state root", func(t *testing.T) {
|
||||
testDumpAndRestore(t, func(c *config.ProtocolConfiguration) {
|
||||
c.StateRootInHeader = true
|
||||
c.P2PSigExtensions = true
|
||||
}, nil)
|
||||
})
|
||||
t.Run("remove untraceable", func(t *testing.T) {
|
||||
// Dump can only be created if all blocks and transactions are present.
|
||||
testDumpAndRestore(t, func(c *config.ProtocolConfiguration) {
|
||||
c.P2PSigExtensions = true
|
||||
}, func(c *config.ProtocolConfiguration) {
|
||||
c.MaxTraceableBlocks = 2
|
||||
c.RemoveUntraceableBlocks = true
|
||||
c.P2PSigExtensions = true
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func testDumpAndRestore(t *testing.T, dumpF, restoreF func(c *config.ProtocolConfiguration)) {
|
||||
if restoreF == nil {
|
||||
restoreF = dumpF
|
||||
}
|
||||
|
||||
bc, validators, committee := chain.NewMultiWithCustomConfig(t, dumpF)
|
||||
e := neotest.NewExecutor(t, bc, validators, committee)
|
||||
|
||||
basicchain.Init(t, "../../../", e)
|
||||
require.True(t, bc.BlockHeight() > 5) // ensure that test is valid
|
||||
|
||||
w := io.NewBufBinWriter()
|
||||
require.NoError(t, chaindump.Dump(bc, w.BinWriter, 0, bc.BlockHeight()+1))
|
||||
require.NoError(t, w.Err)
|
||||
|
||||
buf := w.Bytes()
|
||||
t.Run("invalid start", func(t *testing.T) {
|
||||
bc2, _, _ := chain.NewMultiWithCustomConfig(t, restoreF)
|
||||
|
||||
r := io.NewBinReaderFromBuf(buf)
|
||||
require.Error(t, chaindump.Restore(bc2, r, 2, 1, nil))
|
||||
})
|
||||
t.Run("good", func(t *testing.T) {
|
||||
bc2, _, _ := chain.NewMultiWithCustomConfig(t, dumpF)
|
||||
|
||||
r := io.NewBinReaderFromBuf(buf)
|
||||
require.NoError(t, chaindump.Restore(bc2, r, 0, 2, nil))
|
||||
require.Equal(t, uint32(1), bc2.BlockHeight())
|
||||
|
||||
r = io.NewBinReaderFromBuf(buf) // new reader because start is relative to dump
|
||||
require.NoError(t, chaindump.Restore(bc2, r, 2, 1, nil))
|
||||
t.Run("check handler", func(t *testing.T) {
|
||||
lastIndex := uint32(0)
|
||||
errStopped := errors.New("stopped")
|
||||
f := func(b *block.Block) error {
|
||||
lastIndex = b.Index
|
||||
if b.Index >= bc.BlockHeight()-1 {
|
||||
return errStopped
|
||||
}
|
||||
return nil
|
||||
}
|
||||
require.NoError(t, chaindump.Restore(bc2, r, 0, 1, f))
|
||||
require.Equal(t, bc2.BlockHeight(), lastIndex)
|
||||
|
||||
r = io.NewBinReaderFromBuf(buf)
|
||||
err := chaindump.Restore(bc2, r, 4, bc.BlockHeight()-bc2.BlockHeight(), f)
|
||||
require.True(t, errors.Is(err, errStopped))
|
||||
require.Equal(t, bc.BlockHeight()-1, lastIndex)
|
||||
})
|
||||
})
|
||||
}
|
71
pkg/core/interop/contract/account.go
Normal file
71
pkg/core/interop/contract/account.go
Normal file
|
@ -0,0 +1,71 @@
|
|||
package contract
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"errors"
|
||||
"math"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/fee"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
)
|
||||
|
||||
// CreateMultisigAccount calculates multisig contract scripthash for a
|
||||
// given m and a set of public keys.
|
||||
func CreateMultisigAccount(ic *interop.Context) error {
|
||||
m := ic.VM.Estack().Pop().BigInt()
|
||||
mu64 := m.Uint64()
|
||||
if !m.IsUint64() || mu64 > math.MaxInt32 {
|
||||
return errors.New("m must be positive and fit int32")
|
||||
}
|
||||
arr := ic.VM.Estack().Pop().Array()
|
||||
pubs := make(keys.PublicKeys, len(arr))
|
||||
for i, pk := range arr {
|
||||
p, err := keys.NewPublicKeyFromBytes(pk.Value().([]byte), elliptic.P256())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pubs[i] = p
|
||||
}
|
||||
var invokeFee int64
|
||||
if ic.IsHardforkEnabled(config.HFAspidochelone) {
|
||||
invokeFee = fee.ECDSAVerifyPrice * int64(len(pubs))
|
||||
} else {
|
||||
invokeFee = 1 << 8
|
||||
}
|
||||
invokeFee *= ic.BaseExecFee()
|
||||
if !ic.VM.AddGas(invokeFee) {
|
||||
return errors.New("gas limit exceeded")
|
||||
}
|
||||
script, err := smartcontract.CreateMultiSigRedeemScript(int(mu64), pubs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ic.VM.Estack().PushItem(stackitem.NewByteArray(hash.Hash160(script).BytesBE()))
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateStandardAccount calculates contract scripthash for a given public key.
|
||||
func CreateStandardAccount(ic *interop.Context) error {
|
||||
h := ic.VM.Estack().Pop().Bytes()
|
||||
p, err := keys.NewPublicKeyFromBytes(h, elliptic.P256())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var invokeFee int64
|
||||
if ic.IsHardforkEnabled(config.HFAspidochelone) {
|
||||
invokeFee = fee.ECDSAVerifyPrice
|
||||
} else {
|
||||
invokeFee = 1 << 8
|
||||
}
|
||||
invokeFee *= ic.BaseExecFee()
|
||||
if !ic.VM.AddGas(invokeFee) {
|
||||
return errors.New("gas limit exceeded")
|
||||
}
|
||||
ic.VM.Estack().PushItem(stackitem.NewByteArray(p.GetScriptHash().BytesBE()))
|
||||
return nil
|
||||
}
|
173
pkg/core/interop/contract/account_test.go
Normal file
173
pkg/core/interop/contract/account_test.go
Normal file
|
@ -0,0 +1,173 @@
|
|||
package contract_test
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util/slice"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCreateStandardAccount(t *testing.T) {
|
||||
bc, acc := chain.NewSingle(t)
|
||||
e := neotest.NewExecutor(t, bc, acc, acc)
|
||||
w := io.NewBufBinWriter()
|
||||
|
||||
t.Run("Good", func(t *testing.T) {
|
||||
priv, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
pub := priv.PublicKey()
|
||||
|
||||
emit.Bytes(w.BinWriter, pub.Bytes())
|
||||
emit.Syscall(w.BinWriter, interopnames.SystemContractCreateStandardAccount)
|
||||
require.NoError(t, w.Err)
|
||||
script := w.Bytes()
|
||||
|
||||
tx := e.PrepareInvocation(t, script, []neotest.Signer{e.Validator}, bc.BlockHeight()+1)
|
||||
e.AddNewBlock(t, tx)
|
||||
e.CheckHalt(t, tx.Hash())
|
||||
|
||||
res := e.GetTxExecResult(t, tx.Hash())
|
||||
value := res.Stack[0].Value().([]byte)
|
||||
u, err := util.Uint160DecodeBytesBE(value)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, pub.GetScriptHash(), u)
|
||||
})
|
||||
t.Run("InvalidKey", func(t *testing.T) {
|
||||
w.Reset()
|
||||
emit.Bytes(w.BinWriter, []byte{1, 2, 3})
|
||||
emit.Syscall(w.BinWriter, interopnames.SystemContractCreateStandardAccount)
|
||||
require.NoError(t, w.Err)
|
||||
script := w.Bytes()
|
||||
|
||||
tx := e.PrepareInvocation(t, script, []neotest.Signer{e.Validator}, bc.BlockHeight()+1)
|
||||
e.AddNewBlock(t, tx)
|
||||
e.CheckFault(t, tx.Hash(), "invalid prefix 1")
|
||||
})
|
||||
}
|
||||
|
||||
func TestCreateMultisigAccount(t *testing.T) {
|
||||
bc, acc := chain.NewSingle(t)
|
||||
e := neotest.NewExecutor(t, bc, acc, acc)
|
||||
w := io.NewBufBinWriter()
|
||||
|
||||
createScript := func(t *testing.T, pubs []interface{}, m int) []byte {
|
||||
w.Reset()
|
||||
emit.Array(w.BinWriter, pubs...)
|
||||
emit.Int(w.BinWriter, int64(m))
|
||||
emit.Syscall(w.BinWriter, interopnames.SystemContractCreateMultisigAccount)
|
||||
require.NoError(t, w.Err)
|
||||
return w.Bytes()
|
||||
}
|
||||
t.Run("Good", func(t *testing.T) {
|
||||
m, n := 3, 5
|
||||
pubs := make(keys.PublicKeys, n)
|
||||
arr := make([]interface{}, n)
|
||||
for i := range pubs {
|
||||
pk, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
pubs[i] = pk.PublicKey()
|
||||
arr[i] = pubs[i].Bytes()
|
||||
}
|
||||
script := createScript(t, arr, m)
|
||||
|
||||
txH := e.InvokeScript(t, script, []neotest.Signer{acc})
|
||||
e.CheckHalt(t, txH)
|
||||
res := e.GetTxExecResult(t, txH)
|
||||
value := res.Stack[0].Value().([]byte)
|
||||
u, err := util.Uint160DecodeBytesBE(value)
|
||||
require.NoError(t, err)
|
||||
expected, err := smartcontract.CreateMultiSigRedeemScript(m, pubs)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, hash.Hash160(expected), u)
|
||||
})
|
||||
t.Run("InvalidKey", func(t *testing.T) {
|
||||
script := createScript(t, []interface{}{[]byte{1, 2, 3}}, 1)
|
||||
e.InvokeScriptCheckFAULT(t, script, []neotest.Signer{acc}, "invalid prefix 1")
|
||||
})
|
||||
t.Run("Invalid m", func(t *testing.T) {
|
||||
pk, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
script := createScript(t, []interface{}{pk.PublicKey().Bytes()}, 2)
|
||||
e.InvokeScriptCheckFAULT(t, script, []neotest.Signer{acc}, "length of the signatures (2) is higher then the number of public keys")
|
||||
})
|
||||
t.Run("m overflows int32", func(t *testing.T) {
|
||||
pk, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
m := big.NewInt(math.MaxInt32)
|
||||
m.Add(m, big.NewInt(1))
|
||||
w.Reset()
|
||||
emit.Array(w.BinWriter, pk.Bytes())
|
||||
emit.BigInt(w.BinWriter, m)
|
||||
emit.Syscall(w.BinWriter, interopnames.SystemContractCreateMultisigAccount)
|
||||
require.NoError(t, w.Err)
|
||||
e.InvokeScriptCheckFAULT(t, w.Bytes(), []neotest.Signer{acc}, "m must be positive and fit int32")
|
||||
})
|
||||
}
|
||||
|
||||
func TestCreateAccount_Hardfork(t *testing.T) {
|
||||
bc, acc := chain.NewSingleWithCustomConfig(t, func(c *config.ProtocolConfiguration) {
|
||||
c.P2PSigExtensions = true // `basicchain.Init` requires Notary enabled
|
||||
c.Hardforks = map[string]uint32{
|
||||
config.HFAspidochelone.String(): 2,
|
||||
}
|
||||
})
|
||||
e := neotest.NewExecutor(t, bc, acc, acc)
|
||||
|
||||
priv, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
pub := priv.PublicKey()
|
||||
|
||||
w := io.NewBufBinWriter()
|
||||
emit.Array(w.BinWriter, []interface{}{pub.Bytes(), pub.Bytes(), pub.Bytes()}...)
|
||||
emit.Int(w.BinWriter, int64(2))
|
||||
emit.Syscall(w.BinWriter, interopnames.SystemContractCreateMultisigAccount)
|
||||
require.NoError(t, w.Err)
|
||||
multisigScript := slice.Copy(w.Bytes())
|
||||
|
||||
w.Reset()
|
||||
emit.Bytes(w.BinWriter, pub.Bytes())
|
||||
emit.Syscall(w.BinWriter, interopnames.SystemContractCreateStandardAccount)
|
||||
require.NoError(t, w.Err)
|
||||
standardScript := slice.Copy(w.Bytes())
|
||||
|
||||
createAccTx := func(t *testing.T, script []byte) *transaction.Transaction {
|
||||
tx := e.PrepareInvocation(t, script, []neotest.Signer{e.Committee}, bc.BlockHeight()+1)
|
||||
return tx
|
||||
}
|
||||
|
||||
// blocks #1, #2: old prices
|
||||
tx1Standard := createAccTx(t, standardScript)
|
||||
tx1Multisig := createAccTx(t, multisigScript)
|
||||
e.AddNewBlock(t, tx1Standard, tx1Multisig)
|
||||
e.CheckHalt(t, tx1Standard.Hash())
|
||||
e.CheckHalt(t, tx1Multisig.Hash())
|
||||
tx2Standard := createAccTx(t, standardScript)
|
||||
tx2Multisig := createAccTx(t, multisigScript)
|
||||
e.AddNewBlock(t, tx2Standard, tx2Multisig)
|
||||
e.CheckHalt(t, tx2Standard.Hash())
|
||||
e.CheckHalt(t, tx2Multisig.Hash())
|
||||
|
||||
// block #3: updated prices (larger than the previous ones)
|
||||
tx3Standard := createAccTx(t, standardScript)
|
||||
tx3Multisig := createAccTx(t, multisigScript)
|
||||
e.AddNewBlock(t, tx3Standard, tx3Multisig)
|
||||
e.CheckHalt(t, tx3Standard.Hash())
|
||||
e.CheckHalt(t, tx3Multisig.Hash())
|
||||
require.True(t, tx1Standard.SystemFee == tx2Standard.SystemFee)
|
||||
require.True(t, tx1Multisig.SystemFee == tx2Multisig.SystemFee)
|
||||
require.True(t, tx2Standard.SystemFee < tx3Standard.SystemFee)
|
||||
require.True(t, tx2Multisig.SystemFee < tx3Multisig.SystemFee)
|
||||
}
|
|
@ -3,6 +3,7 @@ package contract
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
||||
|
@ -172,3 +173,9 @@ func CallFromNative(ic *interop.Context, caller util.Uint160, cs *state.Contract
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCallFlags returns current context calling flags.
|
||||
func GetCallFlags(ic *interop.Context) error {
|
||||
ic.VM.Estack().PushItem(stackitem.NewBigInteger(big.NewInt(int64(ic.VM.Context().GetCallFlags()))))
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,182 +1,175 @@
|
|||
package core_test
|
||||
package contract_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/internal/contracts"
|
||||
"github.com/nspcc-dev/neo-go/internal/random"
|
||||
"github.com/nspcc-dev/neo-go/pkg/compiler"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"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/util/slice"
|
||||
"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/stackitem"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSystemRuntimeGetRandom_DifferentTransactions(t *testing.T) {
|
||||
bc, acc := chain.NewSingle(t)
|
||||
e := neotest.NewExecutor(t, bc, acc, acc)
|
||||
var pathToInternalContracts = filepath.Join("..", "..", "..", "..", "internal", "contracts")
|
||||
|
||||
w := io.NewBufBinWriter()
|
||||
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetRandom)
|
||||
require.NoError(t, w.Err)
|
||||
script := w.Bytes()
|
||||
func TestGetCallFlags(t *testing.T) {
|
||||
bc, _ := chain.NewSingle(t)
|
||||
ic := bc.GetTestVM(trigger.Application, &transaction.Transaction{}, &block.Block{})
|
||||
|
||||
tx1 := e.PrepareInvocation(t, script, []neotest.Signer{e.Validator}, bc.BlockHeight()+1)
|
||||
tx2 := e.PrepareInvocation(t, script, []neotest.Signer{e.Validator}, bc.BlockHeight()+1)
|
||||
e.AddNewBlock(t, tx1, tx2)
|
||||
e.CheckHalt(t, tx1.Hash())
|
||||
e.CheckHalt(t, tx2.Hash())
|
||||
|
||||
res1 := e.GetTxExecResult(t, tx1.Hash())
|
||||
res2 := e.GetTxExecResult(t, tx2.Hash())
|
||||
|
||||
r1, err := res1.Stack[0].TryInteger()
|
||||
require.NoError(t, err)
|
||||
r2, err := res2.Stack[0].TryInteger()
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, r1, r2)
|
||||
ic.VM.LoadScriptWithHash([]byte{byte(opcode.RET)}, util.Uint160{1, 2, 3}, callflag.All)
|
||||
require.NoError(t, contract.GetCallFlags(ic))
|
||||
require.Equal(t, int64(callflag.All), ic.VM.Estack().Pop().Value().(*big.Int).Int64())
|
||||
}
|
||||
|
||||
func TestSystemContractCreateStandardAccount(t *testing.T) {
|
||||
bc, acc := chain.NewSingle(t)
|
||||
e := neotest.NewExecutor(t, bc, acc, acc)
|
||||
w := io.NewBufBinWriter()
|
||||
func TestCall(t *testing.T) {
|
||||
bc, _ := chain.NewSingle(t)
|
||||
ic := bc.GetTestVM(trigger.Application, &transaction.Transaction{}, &block.Block{})
|
||||
|
||||
cs, currCs := contracts.GetTestContractState(t, pathToInternalContracts, 4, 5, random.Uint160()) // sender and IDs are not important for the test
|
||||
require.NoError(t, native.PutContractState(ic.DAO, cs))
|
||||
require.NoError(t, native.PutContractState(ic.DAO, currCs))
|
||||
|
||||
currScript := currCs.NEF.Script
|
||||
h := cs.Hash
|
||||
|
||||
addArgs := stackitem.NewArray([]stackitem.Item{stackitem.Make(1), stackitem.Make(2)})
|
||||
t.Run("Good", func(t *testing.T) {
|
||||
priv, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
pub := priv.PublicKey()
|
||||
|
||||
emit.Bytes(w.BinWriter, pub.Bytes())
|
||||
emit.Syscall(w.BinWriter, interopnames.SystemContractCreateStandardAccount)
|
||||
require.NoError(t, w.Err)
|
||||
script := w.Bytes()
|
||||
|
||||
tx := e.PrepareInvocation(t, script, []neotest.Signer{e.Validator}, bc.BlockHeight()+1)
|
||||
e.AddNewBlock(t, tx)
|
||||
e.CheckHalt(t, tx.Hash())
|
||||
|
||||
res := e.GetTxExecResult(t, tx.Hash())
|
||||
value := res.Stack[0].Value().([]byte)
|
||||
u, err := util.Uint160DecodeBytesBE(value)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, pub.GetScriptHash(), u)
|
||||
t.Run("2 arguments", func(t *testing.T) {
|
||||
loadScript(ic, currScript, 42)
|
||||
ic.VM.Estack().PushVal(addArgs)
|
||||
ic.VM.Estack().PushVal(callflag.All)
|
||||
ic.VM.Estack().PushVal("add")
|
||||
ic.VM.Estack().PushVal(h.BytesBE())
|
||||
require.NoError(t, contract.Call(ic))
|
||||
require.NoError(t, ic.VM.Run())
|
||||
require.Equal(t, 2, ic.VM.Estack().Len())
|
||||
require.Equal(t, big.NewInt(3), ic.VM.Estack().Pop().Value())
|
||||
require.Equal(t, big.NewInt(42), ic.VM.Estack().Pop().Value())
|
||||
})
|
||||
t.Run("3 arguments", func(t *testing.T) {
|
||||
loadScript(ic, currScript, 42)
|
||||
ic.VM.Estack().PushVal(stackitem.NewArray(
|
||||
append(addArgs.Value().([]stackitem.Item), stackitem.Make(3))))
|
||||
ic.VM.Estack().PushVal(callflag.All)
|
||||
ic.VM.Estack().PushVal("add")
|
||||
ic.VM.Estack().PushVal(h.BytesBE())
|
||||
require.NoError(t, contract.Call(ic))
|
||||
require.NoError(t, ic.VM.Run())
|
||||
require.Equal(t, 2, ic.VM.Estack().Len())
|
||||
require.Equal(t, big.NewInt(6), ic.VM.Estack().Pop().Value())
|
||||
require.Equal(t, big.NewInt(42), ic.VM.Estack().Pop().Value())
|
||||
})
|
||||
})
|
||||
t.Run("InvalidKey", func(t *testing.T) {
|
||||
w.Reset()
|
||||
emit.Bytes(w.BinWriter, []byte{1, 2, 3})
|
||||
emit.Syscall(w.BinWriter, interopnames.SystemContractCreateStandardAccount)
|
||||
require.NoError(t, w.Err)
|
||||
script := w.Bytes()
|
||||
|
||||
tx := e.PrepareInvocation(t, script, []neotest.Signer{e.Validator}, bc.BlockHeight()+1)
|
||||
e.AddNewBlock(t, tx)
|
||||
e.CheckFault(t, tx.Hash(), "invalid prefix 1")
|
||||
t.Run("CallExInvalidFlag", func(t *testing.T) {
|
||||
loadScript(ic, currScript, 42)
|
||||
ic.VM.Estack().PushVal(addArgs)
|
||||
ic.VM.Estack().PushVal(byte(0xFF))
|
||||
ic.VM.Estack().PushVal("add")
|
||||
ic.VM.Estack().PushVal(h.BytesBE())
|
||||
require.Error(t, contract.Call(ic))
|
||||
})
|
||||
}
|
||||
|
||||
func TestSystemContractCreateMultisigAccount(t *testing.T) {
|
||||
bc, acc := chain.NewSingle(t)
|
||||
e := neotest.NewExecutor(t, bc, acc, acc)
|
||||
w := io.NewBufBinWriter()
|
||||
|
||||
createScript := func(t *testing.T, pubs []interface{}, m int) []byte {
|
||||
w.Reset()
|
||||
emit.Array(w.BinWriter, pubs...)
|
||||
emit.Int(w.BinWriter, int64(m))
|
||||
emit.Syscall(w.BinWriter, interopnames.SystemContractCreateMultisigAccount)
|
||||
require.NoError(t, w.Err)
|
||||
return w.Bytes()
|
||||
}
|
||||
t.Run("Good", func(t *testing.T) {
|
||||
m, n := 3, 5
|
||||
pubs := make(keys.PublicKeys, n)
|
||||
arr := make([]interface{}, n)
|
||||
for i := range pubs {
|
||||
pk, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
pubs[i] = pk.PublicKey()
|
||||
arr[i] = pubs[i].Bytes()
|
||||
runInvalid := func(args ...interface{}) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
loadScriptWithHashAndFlags(ic, currScript, h, callflag.All, 42)
|
||||
for i := range args {
|
||||
ic.VM.Estack().PushVal(args[i])
|
||||
}
|
||||
// interops can both return error and panic,
|
||||
// we don't care which kind of error has occurred
|
||||
require.Panics(t, func() {
|
||||
err := contract.Call(ic)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
script := createScript(t, arr, m)
|
||||
}
|
||||
|
||||
txH := e.InvokeScript(t, script, []neotest.Signer{acc})
|
||||
e.CheckHalt(t, txH)
|
||||
res := e.GetTxExecResult(t, txH)
|
||||
value := res.Stack[0].Value().([]byte)
|
||||
u, err := util.Uint160DecodeBytesBE(value)
|
||||
require.NoError(t, err)
|
||||
expected, err := smartcontract.CreateMultiSigRedeemScript(m, pubs)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, hash.Hash160(expected), u)
|
||||
t.Run("Invalid", func(t *testing.T) {
|
||||
t.Run("Hash", runInvalid(addArgs, "add", h.BytesBE()[1:]))
|
||||
t.Run("MissingHash", runInvalid(addArgs, "add", util.Uint160{}.BytesBE()))
|
||||
t.Run("Method", runInvalid(addArgs, stackitem.NewInterop("add"), h.BytesBE()))
|
||||
t.Run("MissingMethod", runInvalid(addArgs, "sub", h.BytesBE()))
|
||||
t.Run("DisallowedMethod", runInvalid(stackitem.NewArray(nil), "ret7", h.BytesBE()))
|
||||
t.Run("Arguments", runInvalid(1, "add", h.BytesBE()))
|
||||
t.Run("NotEnoughArguments", runInvalid(
|
||||
stackitem.NewArray([]stackitem.Item{stackitem.Make(1)}), "add", h.BytesBE()))
|
||||
t.Run("TooMuchArguments", runInvalid(
|
||||
stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.Make(1), stackitem.Make(2), stackitem.Make(3), stackitem.Make(4)}),
|
||||
"add", h.BytesBE()))
|
||||
})
|
||||
t.Run("InvalidKey", func(t *testing.T) {
|
||||
script := createScript(t, []interface{}{[]byte{1, 2, 3}}, 1)
|
||||
e.InvokeScriptCheckFAULT(t, script, []neotest.Signer{acc}, "invalid prefix 1")
|
||||
|
||||
t.Run("ReturnValues", func(t *testing.T) {
|
||||
t.Run("Many", func(t *testing.T) {
|
||||
loadScript(ic, currScript, 42)
|
||||
ic.VM.Estack().PushVal(stackitem.NewArray(nil))
|
||||
ic.VM.Estack().PushVal(callflag.All)
|
||||
ic.VM.Estack().PushVal("invalidReturn")
|
||||
ic.VM.Estack().PushVal(h.BytesBE())
|
||||
require.NoError(t, contract.Call(ic))
|
||||
require.Error(t, ic.VM.Run())
|
||||
})
|
||||
t.Run("Void", func(t *testing.T) {
|
||||
loadScript(ic, currScript, 42)
|
||||
ic.VM.Estack().PushVal(stackitem.NewArray(nil))
|
||||
ic.VM.Estack().PushVal(callflag.All)
|
||||
ic.VM.Estack().PushVal("justReturn")
|
||||
ic.VM.Estack().PushVal(h.BytesBE())
|
||||
require.NoError(t, contract.Call(ic))
|
||||
require.NoError(t, ic.VM.Run())
|
||||
require.Equal(t, 2, ic.VM.Estack().Len())
|
||||
require.Equal(t, stackitem.Null{}, ic.VM.Estack().Pop().Item())
|
||||
require.Equal(t, big.NewInt(42), ic.VM.Estack().Pop().Value())
|
||||
})
|
||||
})
|
||||
t.Run("Invalid m", func(t *testing.T) {
|
||||
pk, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
script := createScript(t, []interface{}{pk.PublicKey().Bytes()}, 2)
|
||||
e.InvokeScriptCheckFAULT(t, script, []neotest.Signer{acc}, "length of the signatures (2) is higher then the number of public keys")
|
||||
|
||||
t.Run("IsolatedStack", func(t *testing.T) {
|
||||
loadScript(ic, currScript, 42)
|
||||
ic.VM.Estack().PushVal(stackitem.NewArray(nil))
|
||||
ic.VM.Estack().PushVal(callflag.All)
|
||||
ic.VM.Estack().PushVal("drop")
|
||||
ic.VM.Estack().PushVal(h.BytesBE())
|
||||
require.NoError(t, contract.Call(ic))
|
||||
require.Error(t, ic.VM.Run())
|
||||
})
|
||||
t.Run("m overflows int32", func(t *testing.T) {
|
||||
pk, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
m := big.NewInt(math.MaxInt32)
|
||||
m.Add(m, big.NewInt(1))
|
||||
w.Reset()
|
||||
emit.Array(w.BinWriter, pk.Bytes())
|
||||
emit.BigInt(w.BinWriter, m)
|
||||
emit.Syscall(w.BinWriter, interopnames.SystemContractCreateMultisigAccount)
|
||||
require.NoError(t, w.Err)
|
||||
e.InvokeScriptCheckFAULT(t, w.Bytes(), []neotest.Signer{acc}, "m must be positive and fit int32")
|
||||
|
||||
t.Run("CallInitialize", func(t *testing.T) {
|
||||
t.Run("Directly", runInvalid(stackitem.NewArray([]stackitem.Item{}), "_initialize", h.BytesBE()))
|
||||
|
||||
loadScript(ic, currScript, 42)
|
||||
ic.VM.Estack().PushVal(stackitem.NewArray([]stackitem.Item{stackitem.Make(5)}))
|
||||
ic.VM.Estack().PushVal(callflag.All)
|
||||
ic.VM.Estack().PushVal("add3")
|
||||
ic.VM.Estack().PushVal(h.BytesBE())
|
||||
require.NoError(t, contract.Call(ic))
|
||||
require.NoError(t, ic.VM.Run())
|
||||
require.Equal(t, 2, ic.VM.Estack().Len())
|
||||
require.Equal(t, big.NewInt(8), ic.VM.Estack().Pop().Value())
|
||||
require.Equal(t, big.NewInt(42), ic.VM.Estack().Pop().Value())
|
||||
})
|
||||
}
|
||||
|
||||
func TestSystemRuntimeGasLeft(t *testing.T) {
|
||||
const runtimeGasLeftPrice = 1 << 4
|
||||
|
||||
bc, acc := chain.NewSingle(t)
|
||||
e := neotest.NewExecutor(t, bc, acc, acc)
|
||||
w := io.NewBufBinWriter()
|
||||
|
||||
gasLimit := 1100
|
||||
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGasLeft)
|
||||
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGasLeft)
|
||||
require.NoError(t, w.Err)
|
||||
tx := transaction.New(w.Bytes(), int64(gasLimit))
|
||||
tx.Nonce = neotest.Nonce()
|
||||
tx.ValidUntilBlock = e.Chain.BlockHeight() + 1
|
||||
e.SignTx(t, tx, int64(gasLimit), acc)
|
||||
e.AddNewBlock(t, tx)
|
||||
e.CheckHalt(t, tx.Hash())
|
||||
res := e.GetTxExecResult(t, tx.Hash())
|
||||
l1 := res.Stack[0].Value().(*big.Int)
|
||||
l2 := res.Stack[1].Value().(*big.Int)
|
||||
|
||||
require.Equal(t, int64(gasLimit-runtimeGasLeftPrice*interop.DefaultBaseExecFee), l1.Int64())
|
||||
require.Equal(t, int64(gasLimit-2*runtimeGasLeftPrice*interop.DefaultBaseExecFee), l2.Int64())
|
||||
}
|
||||
|
||||
func TestLoadToken(t *testing.T) {
|
||||
bc, acc := chain.NewSingle(t)
|
||||
e := neotest.NewExecutor(t, bc, acc, acc)
|
||||
|
@ -204,118 +197,6 @@ func TestLoadToken(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestSystemRuntimeGetNetwork(t *testing.T) {
|
||||
bc, acc := chain.NewSingle(t)
|
||||
e := neotest.NewExecutor(t, bc, acc, acc)
|
||||
w := io.NewBufBinWriter()
|
||||
|
||||
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetNetwork)
|
||||
require.NoError(t, w.Err)
|
||||
e.InvokeScriptCheckHALT(t, w.Bytes(), []neotest.Signer{acc}, stackitem.NewBigInteger(big.NewInt(int64(bc.GetConfig().Magic))))
|
||||
}
|
||||
|
||||
func TestSystemRuntimeGetAddressVersion(t *testing.T) {
|
||||
bc, acc := chain.NewSingle(t)
|
||||
e := neotest.NewExecutor(t, bc, acc, acc)
|
||||
w := io.NewBufBinWriter()
|
||||
|
||||
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetAddressVersion)
|
||||
require.NoError(t, w.Err)
|
||||
e.InvokeScriptCheckHALT(t, w.Bytes(), []neotest.Signer{acc}, stackitem.NewBigInteger(big.NewInt(int64(address.NEO3Prefix))))
|
||||
}
|
||||
|
||||
func TestSystemRuntimeBurnGas(t *testing.T) {
|
||||
bc, acc := chain.NewSingle(t)
|
||||
e := neotest.NewExecutor(t, bc, acc, acc)
|
||||
managementInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Management))
|
||||
|
||||
cs, _ := contracts.GetTestContractState(t, pathToInternalContracts, 0, 1, acc.ScriptHash())
|
||||
rawManifest, err := json.Marshal(cs.Manifest)
|
||||
require.NoError(t, err)
|
||||
rawNef, err := cs.NEF.Bytes()
|
||||
require.NoError(t, err)
|
||||
tx := managementInvoker.PrepareInvoke(t, "deploy", rawNef, rawManifest)
|
||||
e.AddNewBlock(t, tx)
|
||||
e.CheckHalt(t, tx.Hash())
|
||||
cInvoker := e.ValidatorInvoker(cs.Hash)
|
||||
|
||||
t.Run("good", func(t *testing.T) {
|
||||
h := cInvoker.Invoke(t, stackitem.Null{}, "burnGas", int64(1))
|
||||
res := e.GetTxExecResult(t, h)
|
||||
|
||||
t.Run("gas limit exceeded", func(t *testing.T) {
|
||||
tx := e.NewUnsignedTx(t, cs.Hash, "burnGas", int64(2))
|
||||
e.SignTx(t, tx, res.GasConsumed, acc)
|
||||
e.AddNewBlock(t, tx)
|
||||
e.CheckFault(t, tx.Hash(), "GAS limit exceeded")
|
||||
})
|
||||
})
|
||||
t.Run("too big integer", func(t *testing.T) {
|
||||
gas := big.NewInt(math.MaxInt64)
|
||||
gas.Add(gas, big.NewInt(1))
|
||||
|
||||
cInvoker.InvokeFail(t, "invalid GAS value", "burnGas", gas)
|
||||
})
|
||||
t.Run("zero GAS", func(t *testing.T) {
|
||||
cInvoker.InvokeFail(t, "GAS must be positive", "burnGas", int64(0))
|
||||
})
|
||||
}
|
||||
|
||||
func TestSystemContractCreateAccount_Hardfork(t *testing.T) {
|
||||
bc, acc := chain.NewSingleWithCustomConfig(t, func(c *config.ProtocolConfiguration) {
|
||||
c.P2PSigExtensions = true // `initBasicChain` requires Notary enabled
|
||||
c.Hardforks = map[string]uint32{
|
||||
config.HFAspidochelone.String(): 2,
|
||||
}
|
||||
})
|
||||
e := neotest.NewExecutor(t, bc, acc, acc)
|
||||
|
||||
priv, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
pub := priv.PublicKey()
|
||||
|
||||
w := io.NewBufBinWriter()
|
||||
emit.Array(w.BinWriter, []interface{}{pub.Bytes(), pub.Bytes(), pub.Bytes()}...)
|
||||
emit.Int(w.BinWriter, int64(2))
|
||||
emit.Syscall(w.BinWriter, interopnames.SystemContractCreateMultisigAccount)
|
||||
require.NoError(t, w.Err)
|
||||
multisigScript := slice.Copy(w.Bytes())
|
||||
|
||||
w.Reset()
|
||||
emit.Bytes(w.BinWriter, pub.Bytes())
|
||||
emit.Syscall(w.BinWriter, interopnames.SystemContractCreateStandardAccount)
|
||||
require.NoError(t, w.Err)
|
||||
standardScript := slice.Copy(w.Bytes())
|
||||
|
||||
createAccTx := func(t *testing.T, script []byte) *transaction.Transaction {
|
||||
tx := e.PrepareInvocation(t, script, []neotest.Signer{e.Committee}, bc.BlockHeight()+1)
|
||||
return tx
|
||||
}
|
||||
|
||||
// blocks #1, #2: old prices
|
||||
tx1Standard := createAccTx(t, standardScript)
|
||||
tx1Multisig := createAccTx(t, multisigScript)
|
||||
e.AddNewBlock(t, tx1Standard, tx1Multisig)
|
||||
e.CheckHalt(t, tx1Standard.Hash())
|
||||
e.CheckHalt(t, tx1Multisig.Hash())
|
||||
tx2Standard := createAccTx(t, standardScript)
|
||||
tx2Multisig := createAccTx(t, multisigScript)
|
||||
e.AddNewBlock(t, tx2Standard, tx2Multisig)
|
||||
e.CheckHalt(t, tx2Standard.Hash())
|
||||
e.CheckHalt(t, tx2Multisig.Hash())
|
||||
|
||||
// block #3: updated prices (larger than the previous ones)
|
||||
tx3Standard := createAccTx(t, standardScript)
|
||||
tx3Multisig := createAccTx(t, multisigScript)
|
||||
e.AddNewBlock(t, tx3Standard, tx3Multisig)
|
||||
e.CheckHalt(t, tx3Standard.Hash())
|
||||
e.CheckHalt(t, tx3Multisig.Hash())
|
||||
require.True(t, tx1Standard.SystemFee == tx2Standard.SystemFee)
|
||||
require.True(t, tx1Multisig.SystemFee == tx2Multisig.SystemFee)
|
||||
require.True(t, tx2Standard.SystemFee < tx3Standard.SystemFee)
|
||||
require.True(t, tx2Multisig.SystemFee < tx3Multisig.SystemFee)
|
||||
}
|
||||
|
||||
func TestSnapshotIsolation_Exceptions(t *testing.T) {
|
||||
bc, acc := chain.NewSingle(t)
|
||||
e := neotest.NewExecutor(t, bc, acc, acc)
|
||||
|
@ -707,3 +588,21 @@ func TestCALLL_from_VoidContext(t *testing.T) {
|
|||
ctrInvoker := e.NewInvoker(ctrA.Hash, e.Committee)
|
||||
ctrInvoker.Invoke(t, stackitem.Null{}, "callHasRet")
|
||||
}
|
||||
|
||||
func loadScript(ic *interop.Context, script []byte, args ...interface{}) {
|
||||
ic.SpawnVM()
|
||||
ic.VM.LoadScriptWithFlags(script, callflag.AllowCall)
|
||||
for i := range args {
|
||||
ic.VM.Estack().PushVal(args[i])
|
||||
}
|
||||
ic.VM.GasLimit = -1
|
||||
}
|
||||
|
||||
func loadScriptWithHashAndFlags(ic *interop.Context, script []byte, hash util.Uint160, f callflag.CallFlag, args ...interface{}) {
|
||||
ic.SpawnVM()
|
||||
ic.VM.LoadScriptWithHash(script, hash, f)
|
||||
for i := range args {
|
||||
ic.VM.Estack().PushVal(args[i])
|
||||
}
|
||||
ic.VM.GasLimit = -1
|
||||
}
|
|
@ -10,6 +10,10 @@ import (
|
|||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type itemable interface {
|
||||
ToStackItem() stackitem.Item
|
||||
}
|
||||
|
||||
const (
|
||||
// MaxEventNameLen is the maximum length of a name for event.
|
||||
MaxEventNameLen = 32
|
||||
|
@ -37,6 +41,17 @@ func GetEntryScriptHash(ic *interop.Context) error {
|
|||
return ic.VM.PushContextScriptHash(ic.VM.Istack().Len() - 1)
|
||||
}
|
||||
|
||||
// GetScriptContainer returns transaction or block that contains the script
|
||||
// being run.
|
||||
func GetScriptContainer(ic *interop.Context) error {
|
||||
c, ok := ic.Container.(itemable)
|
||||
if !ok {
|
||||
return errors.New("unknown script container")
|
||||
}
|
||||
ic.VM.Estack().PushItem(c.ToStackItem())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Platform returns the name of the platform.
|
||||
func Platform(ic *interop.Context) error {
|
||||
ic.VM.Estack().PushItem(stackitem.NewByteArray([]byte("NEO")))
|
||||
|
|
540
pkg/core/interop/runtime/ext_test.go
Normal file
540
pkg/core/interop/runtime/ext_test.go
Normal file
|
@ -0,0 +1,540 @@
|
|||
package runtime_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"math/big"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/internal/contracts"
|
||||
"github.com/nspcc-dev/neo-go/internal/random"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/runtime"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
||||
"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/nef"
|
||||
"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/vm/stackitem"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var pathToInternalContracts = filepath.Join("..", "..", "..", "..", "internal", "contracts")
|
||||
|
||||
func getSharpTestTx(sender util.Uint160) *transaction.Transaction {
|
||||
tx := transaction.New([]byte{byte(opcode.PUSH2)}, 0)
|
||||
tx.Nonce = 0
|
||||
tx.Signers = append(tx.Signers, transaction.Signer{
|
||||
Account: sender,
|
||||
Scopes: transaction.CalledByEntry,
|
||||
})
|
||||
tx.Attributes = []transaction.Attribute{}
|
||||
tx.Scripts = append(tx.Scripts, transaction.Witness{InvocationScript: []byte{}, VerificationScript: []byte{}})
|
||||
return tx
|
||||
}
|
||||
|
||||
func getSharpTestGenesis(t *testing.T) *block.Block {
|
||||
const configPath = "../../../../config"
|
||||
|
||||
cfg, err := config.Load(configPath, netmode.MainNet)
|
||||
require.NoError(t, err)
|
||||
b, err := core.CreateGenesisBlock(cfg.ProtocolConfiguration)
|
||||
require.NoError(t, err)
|
||||
return b
|
||||
}
|
||||
|
||||
func createVM(t testing.TB) (*vm.VM, *interop.Context, *core.Blockchain) {
|
||||
chain, _ := chain.NewSingle(t)
|
||||
ic := chain.GetTestVM(trigger.Application, &transaction.Transaction{}, &block.Block{})
|
||||
v := ic.SpawnVM()
|
||||
return v, ic, chain
|
||||
}
|
||||
|
||||
func loadScriptWithHashAndFlags(ic *interop.Context, script []byte, hash util.Uint160, f callflag.CallFlag, args ...interface{}) {
|
||||
ic.SpawnVM()
|
||||
ic.VM.LoadScriptWithHash(script, hash, f)
|
||||
for i := range args {
|
||||
ic.VM.Estack().PushVal(args[i])
|
||||
}
|
||||
ic.VM.GasLimit = -1
|
||||
}
|
||||
|
||||
func TestBurnGas(t *testing.T) {
|
||||
bc, acc := chain.NewSingle(t)
|
||||
e := neotest.NewExecutor(t, bc, acc, acc)
|
||||
managementInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Management))
|
||||
|
||||
cs, _ := contracts.GetTestContractState(t, pathToInternalContracts, 0, 1, acc.ScriptHash())
|
||||
rawManifest, err := json.Marshal(cs.Manifest)
|
||||
require.NoError(t, err)
|
||||
rawNef, err := cs.NEF.Bytes()
|
||||
require.NoError(t, err)
|
||||
tx := managementInvoker.PrepareInvoke(t, "deploy", rawNef, rawManifest)
|
||||
e.AddNewBlock(t, tx)
|
||||
e.CheckHalt(t, tx.Hash())
|
||||
cInvoker := e.ValidatorInvoker(cs.Hash)
|
||||
|
||||
t.Run("good", func(t *testing.T) {
|
||||
h := cInvoker.Invoke(t, stackitem.Null{}, "burnGas", int64(1))
|
||||
res := e.GetTxExecResult(t, h)
|
||||
|
||||
t.Run("gas limit exceeded", func(t *testing.T) {
|
||||
tx := e.NewUnsignedTx(t, cs.Hash, "burnGas", int64(2))
|
||||
e.SignTx(t, tx, res.GasConsumed, acc)
|
||||
e.AddNewBlock(t, tx)
|
||||
e.CheckFault(t, tx.Hash(), "GAS limit exceeded")
|
||||
})
|
||||
})
|
||||
t.Run("too big integer", func(t *testing.T) {
|
||||
gas := big.NewInt(math.MaxInt64)
|
||||
gas.Add(gas, big.NewInt(1))
|
||||
|
||||
cInvoker.InvokeFail(t, "invalid GAS value", "burnGas", gas)
|
||||
})
|
||||
t.Run("zero GAS", func(t *testing.T) {
|
||||
cInvoker.InvokeFail(t, "GAS must be positive", "burnGas", int64(0))
|
||||
})
|
||||
}
|
||||
|
||||
func TestCheckWitness(t *testing.T) {
|
||||
_, ic, _ := createVM(t)
|
||||
|
||||
script := []byte{byte(opcode.RET)}
|
||||
scriptHash := hash.Hash160(script)
|
||||
check := func(t *testing.T, ic *interop.Context, arg interface{}, shouldFail bool, expected ...bool) {
|
||||
ic.VM.Estack().PushVal(arg)
|
||||
err := runtime.CheckWitness(ic)
|
||||
if shouldFail {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, expected)
|
||||
actual, ok := ic.VM.Estack().Pop().Value().(bool)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, expected[0], actual)
|
||||
}
|
||||
}
|
||||
t.Run("error", func(t *testing.T) {
|
||||
t.Run("not a hash or key", func(t *testing.T) {
|
||||
check(t, ic, []byte{1, 2, 3}, true)
|
||||
})
|
||||
t.Run("script container is not a transaction", func(t *testing.T) {
|
||||
loadScriptWithHashAndFlags(ic, script, scriptHash, callflag.ReadStates)
|
||||
check(t, ic, random.Uint160().BytesBE(), true)
|
||||
})
|
||||
t.Run("check scope", func(t *testing.T) {
|
||||
t.Run("CustomGroups, missing ReadStates flag", func(t *testing.T) {
|
||||
hash := random.Uint160()
|
||||
tx := &transaction.Transaction{
|
||||
Signers: []transaction.Signer{
|
||||
{
|
||||
Account: hash,
|
||||
Scopes: transaction.CustomGroups,
|
||||
AllowedGroups: []*keys.PublicKey{},
|
||||
},
|
||||
},
|
||||
}
|
||||
ic.Tx = tx
|
||||
callingScriptHash := scriptHash
|
||||
loadScriptWithHashAndFlags(ic, script, callingScriptHash, callflag.All)
|
||||
ic.VM.LoadScriptWithHash([]byte{0x1}, random.Uint160(), callflag.AllowCall)
|
||||
check(t, ic, hash.BytesBE(), true)
|
||||
})
|
||||
t.Run("Rules, missing ReadStates flag", func(t *testing.T) {
|
||||
hash := random.Uint160()
|
||||
pk, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
tx := &transaction.Transaction{
|
||||
Signers: []transaction.Signer{
|
||||
{
|
||||
Account: hash,
|
||||
Scopes: transaction.Rules,
|
||||
Rules: []transaction.WitnessRule{{
|
||||
Action: transaction.WitnessAllow,
|
||||
Condition: (*transaction.ConditionGroup)(pk.PublicKey()),
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
ic.Tx = tx
|
||||
callingScriptHash := scriptHash
|
||||
loadScriptWithHashAndFlags(ic, script, callingScriptHash, callflag.All)
|
||||
ic.VM.LoadScriptWithHash([]byte{0x1}, random.Uint160(), callflag.AllowCall)
|
||||
check(t, ic, hash.BytesBE(), true)
|
||||
})
|
||||
})
|
||||
})
|
||||
t.Run("positive", func(t *testing.T) {
|
||||
t.Run("calling scripthash", func(t *testing.T) {
|
||||
t.Run("hashed witness", func(t *testing.T) {
|
||||
callingScriptHash := scriptHash
|
||||
loadScriptWithHashAndFlags(ic, script, callingScriptHash, callflag.All)
|
||||
ic.VM.LoadScriptWithHash([]byte{0x1}, random.Uint160(), callflag.All)
|
||||
check(t, ic, callingScriptHash.BytesBE(), false, true)
|
||||
})
|
||||
t.Run("keyed witness", func(t *testing.T) {
|
||||
pk, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
callingScriptHash := pk.PublicKey().GetScriptHash()
|
||||
loadScriptWithHashAndFlags(ic, script, callingScriptHash, callflag.All)
|
||||
ic.VM.LoadScriptWithHash([]byte{0x1}, random.Uint160(), callflag.All)
|
||||
check(t, ic, pk.PublicKey().Bytes(), false, true)
|
||||
})
|
||||
})
|
||||
t.Run("check scope", func(t *testing.T) {
|
||||
t.Run("Global", func(t *testing.T) {
|
||||
hash := random.Uint160()
|
||||
tx := &transaction.Transaction{
|
||||
Signers: []transaction.Signer{
|
||||
{
|
||||
Account: hash,
|
||||
Scopes: transaction.Global,
|
||||
},
|
||||
},
|
||||
}
|
||||
loadScriptWithHashAndFlags(ic, script, scriptHash, callflag.ReadStates)
|
||||
ic.Tx = tx
|
||||
check(t, ic, hash.BytesBE(), false, true)
|
||||
})
|
||||
t.Run("CalledByEntry", func(t *testing.T) {
|
||||
hash := random.Uint160()
|
||||
tx := &transaction.Transaction{
|
||||
Signers: []transaction.Signer{
|
||||
{
|
||||
Account: hash,
|
||||
Scopes: transaction.CalledByEntry,
|
||||
},
|
||||
},
|
||||
}
|
||||
loadScriptWithHashAndFlags(ic, script, scriptHash, callflag.ReadStates)
|
||||
ic.Tx = tx
|
||||
check(t, ic, hash.BytesBE(), false, true)
|
||||
})
|
||||
t.Run("CustomContracts", func(t *testing.T) {
|
||||
hash := random.Uint160()
|
||||
tx := &transaction.Transaction{
|
||||
Signers: []transaction.Signer{
|
||||
{
|
||||
Account: hash,
|
||||
Scopes: transaction.CustomContracts,
|
||||
AllowedContracts: []util.Uint160{scriptHash},
|
||||
},
|
||||
},
|
||||
}
|
||||
loadScriptWithHashAndFlags(ic, script, scriptHash, callflag.ReadStates)
|
||||
ic.Tx = tx
|
||||
check(t, ic, hash.BytesBE(), false, true)
|
||||
})
|
||||
t.Run("CustomGroups", func(t *testing.T) {
|
||||
t.Run("unknown scripthash", func(t *testing.T) {
|
||||
hash := random.Uint160()
|
||||
tx := &transaction.Transaction{
|
||||
Signers: []transaction.Signer{
|
||||
{
|
||||
Account: hash,
|
||||
Scopes: transaction.CustomGroups,
|
||||
AllowedGroups: []*keys.PublicKey{},
|
||||
},
|
||||
},
|
||||
}
|
||||
loadScriptWithHashAndFlags(ic, script, scriptHash, callflag.ReadStates)
|
||||
ic.Tx = tx
|
||||
check(t, ic, hash.BytesBE(), false, false)
|
||||
})
|
||||
t.Run("positive", func(t *testing.T) {
|
||||
targetHash := random.Uint160()
|
||||
pk, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
tx := &transaction.Transaction{
|
||||
Signers: []transaction.Signer{
|
||||
{
|
||||
Account: targetHash,
|
||||
Scopes: transaction.CustomGroups,
|
||||
AllowedGroups: []*keys.PublicKey{pk.PublicKey()},
|
||||
},
|
||||
},
|
||||
}
|
||||
contractScript := []byte{byte(opcode.PUSH1), byte(opcode.RET)}
|
||||
contractScriptHash := hash.Hash160(contractScript)
|
||||
ne, err := nef.NewFile(contractScript)
|
||||
require.NoError(t, err)
|
||||
contractState := &state.Contract{
|
||||
ContractBase: state.ContractBase{
|
||||
ID: 15,
|
||||
Hash: contractScriptHash,
|
||||
NEF: *ne,
|
||||
Manifest: manifest.Manifest{
|
||||
Groups: []manifest.Group{{PublicKey: pk.PublicKey(), Signature: make([]byte, keys.SignatureLen)}},
|
||||
},
|
||||
},
|
||||
}
|
||||
require.NoError(t, native.PutContractState(ic.DAO, contractState))
|
||||
loadScriptWithHashAndFlags(ic, contractScript, contractScriptHash, callflag.All)
|
||||
ic.Tx = tx
|
||||
check(t, ic, targetHash.BytesBE(), false, true)
|
||||
})
|
||||
})
|
||||
t.Run("Rules", func(t *testing.T) {
|
||||
t.Run("no match", func(t *testing.T) {
|
||||
hash := random.Uint160()
|
||||
tx := &transaction.Transaction{
|
||||
Signers: []transaction.Signer{
|
||||
{
|
||||
Account: hash,
|
||||
Scopes: transaction.Rules,
|
||||
Rules: []transaction.WitnessRule{{
|
||||
Action: transaction.WitnessAllow,
|
||||
Condition: (*transaction.ConditionScriptHash)(&hash),
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
loadScriptWithHashAndFlags(ic, script, scriptHash, callflag.ReadStates)
|
||||
ic.Tx = tx
|
||||
check(t, ic, hash.BytesBE(), false, false)
|
||||
})
|
||||
t.Run("allow", func(t *testing.T) {
|
||||
hash := random.Uint160()
|
||||
var cond = true
|
||||
tx := &transaction.Transaction{
|
||||
Signers: []transaction.Signer{
|
||||
{
|
||||
Account: hash,
|
||||
Scopes: transaction.Rules,
|
||||
Rules: []transaction.WitnessRule{{
|
||||
Action: transaction.WitnessAllow,
|
||||
Condition: (*transaction.ConditionBoolean)(&cond),
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
loadScriptWithHashAndFlags(ic, script, scriptHash, callflag.ReadStates)
|
||||
ic.Tx = tx
|
||||
check(t, ic, hash.BytesBE(), false, true)
|
||||
})
|
||||
t.Run("deny", func(t *testing.T) {
|
||||
hash := random.Uint160()
|
||||
var cond = true
|
||||
tx := &transaction.Transaction{
|
||||
Signers: []transaction.Signer{
|
||||
{
|
||||
Account: hash,
|
||||
Scopes: transaction.Rules,
|
||||
Rules: []transaction.WitnessRule{{
|
||||
Action: transaction.WitnessDeny,
|
||||
Condition: (*transaction.ConditionBoolean)(&cond),
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
loadScriptWithHashAndFlags(ic, script, scriptHash, callflag.ReadStates)
|
||||
ic.Tx = tx
|
||||
check(t, ic, hash.BytesBE(), false, false)
|
||||
})
|
||||
})
|
||||
t.Run("bad scope", func(t *testing.T) {
|
||||
hash := random.Uint160()
|
||||
tx := &transaction.Transaction{
|
||||
Signers: []transaction.Signer{
|
||||
{
|
||||
Account: hash,
|
||||
Scopes: transaction.None,
|
||||
},
|
||||
},
|
||||
}
|
||||
loadScriptWithHashAndFlags(ic, script, scriptHash, callflag.ReadStates)
|
||||
ic.Tx = tx
|
||||
check(t, ic, hash.BytesBE(), false, false)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestGasLeft(t *testing.T) {
|
||||
const runtimeGasLeftPrice = 1 << 4
|
||||
|
||||
bc, acc := chain.NewSingle(t)
|
||||
e := neotest.NewExecutor(t, bc, acc, acc)
|
||||
w := io.NewBufBinWriter()
|
||||
|
||||
gasLimit := 1100
|
||||
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGasLeft)
|
||||
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGasLeft)
|
||||
require.NoError(t, w.Err)
|
||||
tx := transaction.New(w.Bytes(), int64(gasLimit))
|
||||
tx.Nonce = neotest.Nonce()
|
||||
tx.ValidUntilBlock = e.Chain.BlockHeight() + 1
|
||||
e.SignTx(t, tx, int64(gasLimit), acc)
|
||||
e.AddNewBlock(t, tx)
|
||||
e.CheckHalt(t, tx.Hash())
|
||||
res := e.GetTxExecResult(t, tx.Hash())
|
||||
l1 := res.Stack[0].Value().(*big.Int)
|
||||
l2 := res.Stack[1].Value().(*big.Int)
|
||||
|
||||
require.Equal(t, int64(gasLimit-runtimeGasLeftPrice*interop.DefaultBaseExecFee), l1.Int64())
|
||||
require.Equal(t, int64(gasLimit-2*runtimeGasLeftPrice*interop.DefaultBaseExecFee), l2.Int64())
|
||||
}
|
||||
|
||||
func TestGetAddressVersion(t *testing.T) {
|
||||
bc, acc := chain.NewSingle(t)
|
||||
e := neotest.NewExecutor(t, bc, acc, acc)
|
||||
w := io.NewBufBinWriter()
|
||||
|
||||
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetAddressVersion)
|
||||
require.NoError(t, w.Err)
|
||||
e.InvokeScriptCheckHALT(t, w.Bytes(), []neotest.Signer{acc}, stackitem.NewBigInteger(big.NewInt(int64(address.NEO3Prefix))))
|
||||
}
|
||||
|
||||
func TestGetInvocationCounter(t *testing.T) {
|
||||
v, ic, _ := createVM(t)
|
||||
|
||||
cs, _ := contracts.GetTestContractState(t, pathToInternalContracts, 4, 5, random.Uint160()) // sender and IDs are not important for the test
|
||||
require.NoError(t, native.PutContractState(ic.DAO, cs))
|
||||
|
||||
ic.Invocations[hash.Hash160([]byte{2})] = 42
|
||||
|
||||
t.Run("No invocations", func(t *testing.T) {
|
||||
v.Load([]byte{1})
|
||||
// do not return an error in this case.
|
||||
require.NoError(t, runtime.GetInvocationCounter(ic))
|
||||
require.EqualValues(t, 1, v.Estack().Pop().BigInt().Int64())
|
||||
})
|
||||
t.Run("NonZero", func(t *testing.T) {
|
||||
v.Load([]byte{2})
|
||||
require.NoError(t, runtime.GetInvocationCounter(ic))
|
||||
require.EqualValues(t, 42, v.Estack().Pop().BigInt().Int64())
|
||||
})
|
||||
t.Run("Contract", func(t *testing.T) {
|
||||
w := io.NewBufBinWriter()
|
||||
emit.AppCall(w.BinWriter, cs.Hash, "invocCounter", callflag.All)
|
||||
v.LoadWithFlags(w.Bytes(), callflag.All)
|
||||
require.NoError(t, v.Run())
|
||||
require.EqualValues(t, 1, v.Estack().Pop().BigInt().Int64())
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetNetwork(t *testing.T) {
|
||||
bc, acc := chain.NewSingle(t)
|
||||
e := neotest.NewExecutor(t, bc, acc, acc)
|
||||
w := io.NewBufBinWriter()
|
||||
|
||||
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetNetwork)
|
||||
require.NoError(t, w.Err)
|
||||
e.InvokeScriptCheckHALT(t, w.Bytes(), []neotest.Signer{acc}, stackitem.NewBigInteger(big.NewInt(int64(bc.GetConfig().Magic))))
|
||||
}
|
||||
|
||||
func TestGetNotifications(t *testing.T) {
|
||||
v, ic, _ := createVM(t)
|
||||
|
||||
ic.Notifications = []state.NotificationEvent{
|
||||
{ScriptHash: util.Uint160{1}, Name: "Event1", Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte{11})})},
|
||||
{ScriptHash: util.Uint160{2}, Name: "Event2", Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte{22})})},
|
||||
{ScriptHash: util.Uint160{1}, Name: "Event1", Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte{33})})},
|
||||
}
|
||||
|
||||
t.Run("NoFilter", func(t *testing.T) {
|
||||
v.Estack().PushVal(stackitem.Null{})
|
||||
require.NoError(t, runtime.GetNotifications(ic))
|
||||
|
||||
arr := v.Estack().Pop().Array()
|
||||
require.Equal(t, len(ic.Notifications), len(arr))
|
||||
for i := range arr {
|
||||
elem := arr[i].Value().([]stackitem.Item)
|
||||
require.Equal(t, ic.Notifications[i].ScriptHash.BytesBE(), elem[0].Value())
|
||||
name, err := stackitem.ToString(elem[1])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ic.Notifications[i].Name, name)
|
||||
ic.Notifications[i].Item.MarkAsReadOnly() // tiny hack for test to be able to compare object references.
|
||||
require.Equal(t, ic.Notifications[i].Item, elem[2])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("WithFilter", func(t *testing.T) {
|
||||
h := util.Uint160{2}.BytesBE()
|
||||
v.Estack().PushVal(h)
|
||||
require.NoError(t, runtime.GetNotifications(ic))
|
||||
|
||||
arr := v.Estack().Pop().Array()
|
||||
require.Equal(t, 1, len(arr))
|
||||
elem := arr[0].Value().([]stackitem.Item)
|
||||
require.Equal(t, h, elem[0].Value())
|
||||
name, err := stackitem.ToString(elem[1])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ic.Notifications[1].Name, name)
|
||||
require.Equal(t, ic.Notifications[1].Item, elem[2])
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetRandom_DifferentTransactions(t *testing.T) {
|
||||
bc, acc := chain.NewSingle(t)
|
||||
e := neotest.NewExecutor(t, bc, acc, acc)
|
||||
|
||||
w := io.NewBufBinWriter()
|
||||
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetRandom)
|
||||
require.NoError(t, w.Err)
|
||||
script := w.Bytes()
|
||||
|
||||
tx1 := e.PrepareInvocation(t, script, []neotest.Signer{e.Validator}, bc.BlockHeight()+1)
|
||||
tx2 := e.PrepareInvocation(t, script, []neotest.Signer{e.Validator}, bc.BlockHeight()+1)
|
||||
e.AddNewBlock(t, tx1, tx2)
|
||||
e.CheckHalt(t, tx1.Hash())
|
||||
e.CheckHalt(t, tx2.Hash())
|
||||
|
||||
res1 := e.GetTxExecResult(t, tx1.Hash())
|
||||
res2 := e.GetTxExecResult(t, tx2.Hash())
|
||||
|
||||
r1, err := res1.Stack[0].TryInteger()
|
||||
require.NoError(t, err)
|
||||
r2, err := res2.Stack[0].TryInteger()
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, r1, r2)
|
||||
}
|
||||
|
||||
// Tests are taken from
|
||||
// https://github.com/neo-project/neo/blob/master/tests/neo.UnitTests/SmartContract/UT_ApplicationEngine.Runtime.cs
|
||||
func TestGetRandomCompatibility(t *testing.T) {
|
||||
bc, _ := chain.NewSingle(t)
|
||||
|
||||
b := getSharpTestGenesis(t)
|
||||
tx := getSharpTestTx(util.Uint160{})
|
||||
ic := bc.GetTestVM(trigger.Application, tx, b)
|
||||
ic.Network = 860833102 // Old mainnet magic used by C# tests.
|
||||
|
||||
ic.VM = vm.New()
|
||||
ic.VM.LoadScript([]byte{0x01})
|
||||
ic.VM.GasLimit = 1100_00000000
|
||||
|
||||
require.NoError(t, runtime.GetRandom(ic))
|
||||
require.Equal(t, "271339657438512451304577787170704246350", ic.VM.Estack().Pop().BigInt().String())
|
||||
|
||||
require.NoError(t, runtime.GetRandom(ic))
|
||||
require.Equal(t, "98548189559099075644778613728143131367", ic.VM.Estack().Pop().BigInt().String())
|
||||
|
||||
require.NoError(t, runtime.GetRandom(ic))
|
||||
require.Equal(t, "247654688993873392544380234598471205121", ic.VM.Estack().Pop().BigInt().String())
|
||||
|
||||
require.NoError(t, runtime.GetRandom(ic))
|
||||
require.Equal(t, "291082758879475329976578097236212073607", ic.VM.Estack().Pop().BigInt().String())
|
||||
|
||||
require.NoError(t, runtime.GetRandom(ic))
|
||||
require.Equal(t, "247152297361212656635216876565962360375", ic.VM.Estack().Pop().BigInt().String())
|
||||
}
|
139
pkg/core/interop/storage/basic.go
Normal file
139
pkg/core/interop/storage/basic.go
Normal file
|
@ -0,0 +1,139 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrGasLimitExceeded is returned from interops when there is not enough
|
||||
// GAS left in the execution context to complete the action.
|
||||
ErrGasLimitExceeded = errors.New("gas limit exceeded")
|
||||
errFindInvalidOptions = errors.New("invalid Find options")
|
||||
)
|
||||
|
||||
// Context contains contract ID and read/write flag, it's used as
|
||||
// a context for storage manipulation functions.
|
||||
type Context struct {
|
||||
ID int32
|
||||
ReadOnly bool
|
||||
}
|
||||
|
||||
// storageDelete deletes stored key-value pair.
|
||||
func Delete(ic *interop.Context) error {
|
||||
stcInterface := ic.VM.Estack().Pop().Value()
|
||||
stc, ok := stcInterface.(*Context)
|
||||
if !ok {
|
||||
return fmt.Errorf("%T is not a storage.Context", stcInterface)
|
||||
}
|
||||
if stc.ReadOnly {
|
||||
return errors.New("storage.Context is read only")
|
||||
}
|
||||
key := ic.VM.Estack().Pop().Bytes()
|
||||
ic.DAO.DeleteStorageItem(stc.ID, key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns stored key-value pair.
|
||||
func Get(ic *interop.Context) error {
|
||||
stcInterface := ic.VM.Estack().Pop().Value()
|
||||
stc, ok := stcInterface.(*Context)
|
||||
if !ok {
|
||||
return fmt.Errorf("%T is not a storage.Context", stcInterface)
|
||||
}
|
||||
key := ic.VM.Estack().Pop().Bytes()
|
||||
si := ic.DAO.GetStorageItem(stc.ID, key)
|
||||
if si != nil {
|
||||
ic.VM.Estack().PushItem(stackitem.NewByteArray([]byte(si)))
|
||||
} else {
|
||||
ic.VM.Estack().PushItem(stackitem.Null{})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetContext returns storage context for the currently executing contract.
|
||||
func GetContext(ic *interop.Context) error {
|
||||
return getContextInternal(ic, false)
|
||||
}
|
||||
|
||||
// GetReadOnlyContext returns read-only storage context for the currently executing contract.
|
||||
func GetReadOnlyContext(ic *interop.Context) error {
|
||||
return getContextInternal(ic, true)
|
||||
}
|
||||
|
||||
// getContextInternal is internal version of storageGetContext and
|
||||
// storageGetReadOnlyContext which allows to specify ReadOnly context flag.
|
||||
func getContextInternal(ic *interop.Context, isReadOnly bool) error {
|
||||
contract, err := ic.GetContract(ic.VM.GetCurrentScriptHash())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sc := &Context{
|
||||
ID: contract.ID,
|
||||
ReadOnly: isReadOnly,
|
||||
}
|
||||
ic.VM.Estack().PushItem(stackitem.NewInterop(sc))
|
||||
return nil
|
||||
}
|
||||
|
||||
func putWithContext(ic *interop.Context, stc *Context, key []byte, value []byte) error {
|
||||
if len(key) > storage.MaxStorageKeyLen {
|
||||
return errors.New("key is too big")
|
||||
}
|
||||
if len(value) > storage.MaxStorageValueLen {
|
||||
return errors.New("value is too big")
|
||||
}
|
||||
if stc.ReadOnly {
|
||||
return errors.New("storage.Context is read only")
|
||||
}
|
||||
si := ic.DAO.GetStorageItem(stc.ID, key)
|
||||
sizeInc := len(value)
|
||||
if si == nil {
|
||||
sizeInc = len(key) + len(value)
|
||||
} else if len(value) != 0 {
|
||||
if len(value) <= len(si) {
|
||||
sizeInc = (len(value)-1)/4 + 1
|
||||
} else if len(si) != 0 {
|
||||
sizeInc = (len(si)-1)/4 + 1 + len(value) - len(si)
|
||||
}
|
||||
}
|
||||
if !ic.VM.AddGas(int64(sizeInc) * ic.BaseStorageFee()) {
|
||||
return ErrGasLimitExceeded
|
||||
}
|
||||
ic.DAO.PutStorageItem(stc.ID, key, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Put puts key-value pair into the storage.
|
||||
func Put(ic *interop.Context) error {
|
||||
stcInterface := ic.VM.Estack().Pop().Value()
|
||||
stc, ok := stcInterface.(*Context)
|
||||
if !ok {
|
||||
return fmt.Errorf("%T is not a storage.Context", stcInterface)
|
||||
}
|
||||
key := ic.VM.Estack().Pop().Bytes()
|
||||
value := ic.VM.Estack().Pop().Bytes()
|
||||
return putWithContext(ic, stc, key, value)
|
||||
}
|
||||
|
||||
// ContextAsReadOnly sets given context to read-only mode.
|
||||
func ContextAsReadOnly(ic *interop.Context) error {
|
||||
stcInterface := ic.VM.Estack().Pop().Value()
|
||||
stc, ok := stcInterface.(*Context)
|
||||
if !ok {
|
||||
return fmt.Errorf("%T is not a storage.Context", stcInterface)
|
||||
}
|
||||
if !stc.ReadOnly {
|
||||
stx := &Context{
|
||||
ID: stc.ID,
|
||||
ReadOnly: true,
|
||||
}
|
||||
stc = stx
|
||||
}
|
||||
ic.VM.Estack().PushItem(stackitem.NewInterop(stc))
|
||||
return nil
|
||||
}
|
113
pkg/core/interop/storage/bench_test.go
Normal file
113
pkg/core/interop/storage/bench_test.go
Normal file
|
@ -0,0 +1,113 @@
|
|||
package storage_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/internal/random"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/iterator"
|
||||
istorage "github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func BenchmarkStorageFind(b *testing.B) {
|
||||
for count := 10; count <= 10000; count *= 10 {
|
||||
b.Run(fmt.Sprintf("%dElements", count), func(b *testing.B) {
|
||||
v, contractState, context, _ := createVMAndContractState(b)
|
||||
require.NoError(b, native.PutContractState(context.DAO, contractState))
|
||||
|
||||
items := make(map[string]state.StorageItem)
|
||||
for i := 0; i < count; i++ {
|
||||
items["abc"+random.String(10)] = random.Bytes(10)
|
||||
}
|
||||
for k, v := range items {
|
||||
context.DAO.PutStorageItem(contractState.ID, []byte(k), v)
|
||||
context.DAO.PutStorageItem(contractState.ID+1, []byte(k), v)
|
||||
}
|
||||
changes, err := context.DAO.Persist()
|
||||
require.NoError(b, err)
|
||||
require.NotEqual(b, 0, changes)
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
b.StopTimer()
|
||||
v.Estack().PushVal(istorage.FindDefault)
|
||||
v.Estack().PushVal("abc")
|
||||
v.Estack().PushVal(stackitem.NewInterop(&istorage.Context{ID: contractState.ID}))
|
||||
b.StartTimer()
|
||||
err := istorage.Find(context)
|
||||
if err != nil {
|
||||
b.FailNow()
|
||||
}
|
||||
b.StopTimer()
|
||||
context.Finalize()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStorageFindIteratorNext(b *testing.B) {
|
||||
for count := 10; count <= 10000; count *= 10 {
|
||||
cases := map[string]int{
|
||||
"Pick1": 1,
|
||||
"PickHalf": count / 2,
|
||||
"PickAll": count,
|
||||
}
|
||||
b.Run(fmt.Sprintf("%dElements", count), func(b *testing.B) {
|
||||
for name, last := range cases {
|
||||
b.Run(name, func(b *testing.B) {
|
||||
v, contractState, context, _ := createVMAndContractState(b)
|
||||
require.NoError(b, native.PutContractState(context.DAO, contractState))
|
||||
|
||||
items := make(map[string]state.StorageItem)
|
||||
for i := 0; i < count; i++ {
|
||||
items["abc"+random.String(10)] = random.Bytes(10)
|
||||
}
|
||||
for k, v := range items {
|
||||
context.DAO.PutStorageItem(contractState.ID, []byte(k), v)
|
||||
context.DAO.PutStorageItem(contractState.ID+1, []byte(k), v)
|
||||
}
|
||||
changes, err := context.DAO.Persist()
|
||||
require.NoError(b, err)
|
||||
require.NotEqual(b, 0, changes)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
b.StopTimer()
|
||||
v.Estack().PushVal(istorage.FindDefault)
|
||||
v.Estack().PushVal("abc")
|
||||
v.Estack().PushVal(stackitem.NewInterop(&istorage.Context{ID: contractState.ID}))
|
||||
b.StartTimer()
|
||||
err := istorage.Find(context)
|
||||
b.StopTimer()
|
||||
if err != nil {
|
||||
b.FailNow()
|
||||
}
|
||||
res := context.VM.Estack().Pop().Item()
|
||||
for i := 0; i < last; i++ {
|
||||
context.VM.Estack().PushVal(res)
|
||||
b.StartTimer()
|
||||
require.NoError(b, iterator.Next(context))
|
||||
b.StopTimer()
|
||||
require.True(b, context.VM.Estack().Pop().Bool())
|
||||
}
|
||||
|
||||
context.VM.Estack().PushVal(res)
|
||||
require.NoError(b, iterator.Next(context))
|
||||
actual := context.VM.Estack().Pop().Bool()
|
||||
if last == count {
|
||||
require.False(b, actual)
|
||||
} else {
|
||||
require.True(b, actual)
|
||||
}
|
||||
context.Finalize()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,6 +1,10 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util/slice"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
|
@ -80,3 +84,45 @@ func (s *Iterator) Value() stackitem.Item {
|
|||
value,
|
||||
})
|
||||
}
|
||||
|
||||
// Find finds stored key-value pair.
|
||||
func Find(ic *interop.Context) error {
|
||||
stcInterface := ic.VM.Estack().Pop().Value()
|
||||
stc, ok := stcInterface.(*Context)
|
||||
if !ok {
|
||||
return fmt.Errorf("%T is not a storage,Context", stcInterface)
|
||||
}
|
||||
prefix := ic.VM.Estack().Pop().Bytes()
|
||||
opts := ic.VM.Estack().Pop().BigInt().Int64()
|
||||
if opts&^FindAll != 0 {
|
||||
return fmt.Errorf("%w: unknown flag", errFindInvalidOptions)
|
||||
}
|
||||
if opts&FindKeysOnly != 0 &&
|
||||
opts&(FindDeserialize|FindPick0|FindPick1) != 0 {
|
||||
return fmt.Errorf("%w KeysOnly conflicts with other options", errFindInvalidOptions)
|
||||
}
|
||||
if opts&FindValuesOnly != 0 &&
|
||||
opts&(FindKeysOnly|FindRemovePrefix) != 0 {
|
||||
return fmt.Errorf("%w: KeysOnly conflicts with ValuesOnly", errFindInvalidOptions)
|
||||
}
|
||||
if opts&FindPick0 != 0 && opts&FindPick1 != 0 {
|
||||
return fmt.Errorf("%w: Pick0 conflicts with Pick1", errFindInvalidOptions)
|
||||
}
|
||||
if opts&FindDeserialize == 0 && (opts&FindPick0 != 0 || opts&FindPick1 != 0) {
|
||||
return fmt.Errorf("%w: PickN is specified without Deserialize", errFindInvalidOptions)
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
seekres := ic.DAO.SeekAsync(ctx, stc.ID, storage.SeekRange{Prefix: prefix})
|
||||
item := NewIterator(seekres, prefix, opts)
|
||||
ic.VM.Estack().PushItem(stackitem.NewInterop(item))
|
||||
ic.RegisterCancelFunc(func() {
|
||||
cancel()
|
||||
// Underlying persistent store is likely to be a private MemCachedStore. Thus,
|
||||
// to avoid concurrent map iteration and map write we need to wait until internal
|
||||
// seek goroutine is finished, because it can access underlying persistent store.
|
||||
for range seekres {
|
||||
}
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package core
|
||||
package storage_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
@ -6,20 +6,10 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func testNonInterop(t *testing.T, value interface{}, f func(*interop.Context) error) {
|
||||
v := vm.New()
|
||||
v.Estack().PushVal(value)
|
||||
chain := newTestChain(t)
|
||||
context := chain.newInteropContext(trigger.Application, chain.dao, nil, nil)
|
||||
context.VM = v
|
||||
require.Error(t, f(context))
|
||||
}
|
||||
|
||||
func TestUnexpectedNonInterops(t *testing.T) {
|
||||
vals := map[string]interface{}{
|
||||
"int": 1,
|
||||
|
@ -30,17 +20,19 @@ func TestUnexpectedNonInterops(t *testing.T) {
|
|||
|
||||
// All of these functions expect an interop item on the stack.
|
||||
funcs := []func(*interop.Context) error{
|
||||
storageContextAsReadOnly,
|
||||
storageDelete,
|
||||
storageFind,
|
||||
storageGet,
|
||||
storagePut,
|
||||
storage.ContextAsReadOnly,
|
||||
storage.Delete,
|
||||
storage.Find,
|
||||
storage.Get,
|
||||
storage.Put,
|
||||
}
|
||||
for _, f := range funcs {
|
||||
for k, v := range vals {
|
||||
fname := runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
|
||||
t.Run(k+"/"+fname, func(t *testing.T) {
|
||||
testNonInterop(t, v, f)
|
||||
vm, ic, _ := createVM(t)
|
||||
vm.Estack().PushVal(v)
|
||||
require.Error(t, f(ic))
|
||||
})
|
||||
}
|
||||
}
|
324
pkg/core/interop/storage/storage_test.go
Normal file
324
pkg/core/interop/storage/storage_test.go
Normal file
|
@ -0,0 +1,324 @@
|
|||
package storage_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/iterator"
|
||||
istorage "github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
||||
"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/nef"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPut(t *testing.T) {
|
||||
_, cs, ic, _ := createVMAndContractState(t)
|
||||
|
||||
require.NoError(t, native.PutContractState(ic.DAO, cs))
|
||||
|
||||
initVM := func(t *testing.T, key, value []byte, gas int64) {
|
||||
v := ic.SpawnVM()
|
||||
v.LoadScript(cs.NEF.Script)
|
||||
v.GasLimit = gas
|
||||
v.Estack().PushVal(value)
|
||||
v.Estack().PushVal(key)
|
||||
require.NoError(t, istorage.GetContext(ic))
|
||||
}
|
||||
|
||||
t.Run("create, not enough gas", func(t *testing.T) {
|
||||
initVM(t, []byte{1}, []byte{2, 3}, 2*native.DefaultStoragePrice)
|
||||
err := istorage.Put(ic)
|
||||
require.True(t, errors.Is(err, istorage.ErrGasLimitExceeded), "got: %v", err)
|
||||
})
|
||||
|
||||
initVM(t, []byte{4}, []byte{5, 6}, 3*native.DefaultStoragePrice)
|
||||
require.NoError(t, istorage.Put(ic))
|
||||
|
||||
t.Run("update", func(t *testing.T) {
|
||||
t.Run("not enough gas", func(t *testing.T) {
|
||||
initVM(t, []byte{4}, []byte{5, 6, 7, 8}, native.DefaultStoragePrice)
|
||||
err := istorage.Put(ic)
|
||||
require.True(t, errors.Is(err, istorage.ErrGasLimitExceeded), "got: %v", err)
|
||||
})
|
||||
initVM(t, []byte{4}, []byte{5, 6, 7, 8}, 3*native.DefaultStoragePrice)
|
||||
require.NoError(t, istorage.Put(ic))
|
||||
initVM(t, []byte{4}, []byte{5, 6}, native.DefaultStoragePrice)
|
||||
require.NoError(t, istorage.Put(ic))
|
||||
})
|
||||
|
||||
t.Run("check limits", func(t *testing.T) {
|
||||
initVM(t, make([]byte, storage.MaxStorageKeyLen), make([]byte, storage.MaxStorageValueLen), -1)
|
||||
require.NoError(t, istorage.Put(ic))
|
||||
})
|
||||
|
||||
t.Run("bad", func(t *testing.T) {
|
||||
t.Run("readonly context", func(t *testing.T) {
|
||||
initVM(t, []byte{1}, []byte{1}, -1)
|
||||
require.NoError(t, istorage.ContextAsReadOnly(ic))
|
||||
require.Error(t, istorage.Put(ic))
|
||||
})
|
||||
t.Run("big key", func(t *testing.T) {
|
||||
initVM(t, make([]byte, storage.MaxStorageKeyLen+1), []byte{1}, -1)
|
||||
require.Error(t, istorage.Put(ic))
|
||||
})
|
||||
t.Run("big value", func(t *testing.T) {
|
||||
initVM(t, []byte{1}, make([]byte, storage.MaxStorageValueLen+1), -1)
|
||||
require.Error(t, istorage.Put(ic))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
v, cs, ic, _ := createVMAndContractState(t)
|
||||
|
||||
require.NoError(t, native.PutContractState(ic.DAO, cs))
|
||||
v.LoadScriptWithHash(cs.NEF.Script, cs.Hash, callflag.All)
|
||||
put := func(key, value string, flag int) {
|
||||
v.Estack().PushVal(value)
|
||||
v.Estack().PushVal(key)
|
||||
require.NoError(t, istorage.GetContext(ic))
|
||||
require.NoError(t, istorage.Put(ic))
|
||||
}
|
||||
put("key1", "value1", 0)
|
||||
put("key2", "value2", 0)
|
||||
put("key3", "value3", 0)
|
||||
|
||||
t.Run("good", func(t *testing.T) {
|
||||
v.Estack().PushVal("key1")
|
||||
require.NoError(t, istorage.GetContext(ic))
|
||||
require.NoError(t, istorage.Delete(ic))
|
||||
})
|
||||
t.Run("readonly context", func(t *testing.T) {
|
||||
v.Estack().PushVal("key2")
|
||||
require.NoError(t, istorage.GetReadOnlyContext(ic))
|
||||
require.Error(t, istorage.Delete(ic))
|
||||
})
|
||||
t.Run("readonly context (from normal)", func(t *testing.T) {
|
||||
v.Estack().PushVal("key3")
|
||||
require.NoError(t, istorage.GetContext(ic))
|
||||
require.NoError(t, istorage.ContextAsReadOnly(ic))
|
||||
require.Error(t, istorage.Delete(ic))
|
||||
})
|
||||
}
|
||||
|
||||
func TestFind(t *testing.T) {
|
||||
v, contractState, context, _ := createVMAndContractState(t)
|
||||
|
||||
arr := []stackitem.Item{
|
||||
stackitem.NewBigInteger(big.NewInt(42)),
|
||||
stackitem.NewByteArray([]byte("second")),
|
||||
stackitem.Null{},
|
||||
}
|
||||
rawArr, err := stackitem.Serialize(stackitem.NewArray(arr))
|
||||
require.NoError(t, err)
|
||||
rawArr0, err := stackitem.Serialize(stackitem.NewArray(arr[:0]))
|
||||
require.NoError(t, err)
|
||||
rawArr1, err := stackitem.Serialize(stackitem.NewArray(arr[:1]))
|
||||
require.NoError(t, err)
|
||||
|
||||
skeys := [][]byte{{0x01, 0x02}, {0x02, 0x01}, {0x01, 0x01},
|
||||
{0x04, 0x00}, {0x05, 0x00}, {0x06}, {0x07}, {0x08},
|
||||
{0x09, 0x12, 0x34}, {0x09, 0x12, 0x56},
|
||||
}
|
||||
items := []state.StorageItem{
|
||||
[]byte{0x01, 0x02, 0x03, 0x04},
|
||||
[]byte{0x04, 0x03, 0x02, 0x01},
|
||||
[]byte{0x03, 0x04, 0x05, 0x06},
|
||||
[]byte{byte(stackitem.ByteArrayT), 2, 0xCA, 0xFE},
|
||||
[]byte{0xFF, 0xFF},
|
||||
rawArr,
|
||||
rawArr0,
|
||||
rawArr1,
|
||||
[]byte{111},
|
||||
[]byte{222},
|
||||
}
|
||||
|
||||
require.NoError(t, native.PutContractState(context.DAO, contractState))
|
||||
|
||||
id := contractState.ID
|
||||
|
||||
for i := range skeys {
|
||||
context.DAO.PutStorageItem(id, skeys[i], items[i])
|
||||
}
|
||||
|
||||
testFind := func(t *testing.T, prefix []byte, opts int64, expected []stackitem.Item) {
|
||||
v.Estack().PushVal(opts)
|
||||
v.Estack().PushVal(prefix)
|
||||
v.Estack().PushVal(stackitem.NewInterop(&istorage.Context{ID: id}))
|
||||
|
||||
err := istorage.Find(context)
|
||||
require.NoError(t, err)
|
||||
|
||||
var iter *stackitem.Interop
|
||||
require.NotPanics(t, func() { iter = v.Estack().Pop().Interop() })
|
||||
|
||||
for i := range expected { // sorted indices with mathing prefix
|
||||
v.Estack().PushVal(iter)
|
||||
require.NoError(t, iterator.Next(context))
|
||||
require.True(t, v.Estack().Pop().Bool())
|
||||
|
||||
v.Estack().PushVal(iter)
|
||||
if expected[i] == nil {
|
||||
require.Panics(t, func() { _ = iterator.Value(context) })
|
||||
return
|
||||
}
|
||||
require.NoError(t, iterator.Value(context))
|
||||
require.Equal(t, expected[i], v.Estack().Pop().Item())
|
||||
}
|
||||
|
||||
v.Estack().PushVal(iter)
|
||||
require.NoError(t, iterator.Next(context))
|
||||
require.False(t, v.Estack().Pop().Bool())
|
||||
}
|
||||
|
||||
t.Run("normal invocation", func(t *testing.T) {
|
||||
testFind(t, []byte{0x01}, istorage.FindDefault, []stackitem.Item{
|
||||
stackitem.NewStruct([]stackitem.Item{
|
||||
stackitem.NewByteArray(skeys[2]),
|
||||
stackitem.NewByteArray(items[2]),
|
||||
}),
|
||||
stackitem.NewStruct([]stackitem.Item{
|
||||
stackitem.NewByteArray(skeys[0]),
|
||||
stackitem.NewByteArray(items[0]),
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("keys only", func(t *testing.T) {
|
||||
testFind(t, []byte{0x01}, istorage.FindKeysOnly, []stackitem.Item{
|
||||
stackitem.NewByteArray(skeys[2]),
|
||||
stackitem.NewByteArray(skeys[0]),
|
||||
})
|
||||
})
|
||||
t.Run("remove prefix", func(t *testing.T) {
|
||||
testFind(t, []byte{0x01}, istorage.FindKeysOnly|istorage.FindRemovePrefix, []stackitem.Item{
|
||||
stackitem.NewByteArray(skeys[2][1:]),
|
||||
stackitem.NewByteArray(skeys[0][1:]),
|
||||
})
|
||||
testFind(t, []byte{0x09, 0x12}, istorage.FindKeysOnly|istorage.FindRemovePrefix, []stackitem.Item{
|
||||
stackitem.NewByteArray(skeys[8][2:]),
|
||||
stackitem.NewByteArray(skeys[9][2:]),
|
||||
})
|
||||
})
|
||||
t.Run("values only", func(t *testing.T) {
|
||||
testFind(t, []byte{0x01}, istorage.FindValuesOnly, []stackitem.Item{
|
||||
stackitem.NewByteArray(items[2]),
|
||||
stackitem.NewByteArray(items[0]),
|
||||
})
|
||||
})
|
||||
t.Run("deserialize values", func(t *testing.T) {
|
||||
testFind(t, []byte{0x04}, istorage.FindValuesOnly|istorage.FindDeserialize, []stackitem.Item{
|
||||
stackitem.NewByteArray(items[3][2:]),
|
||||
})
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
v.Estack().PushVal(istorage.FindDeserialize)
|
||||
v.Estack().PushVal([]byte{0x05})
|
||||
v.Estack().PushVal(stackitem.NewInterop(&istorage.Context{ID: id}))
|
||||
err := istorage.Find(context)
|
||||
require.NoError(t, err)
|
||||
|
||||
var iter *stackitem.Interop
|
||||
require.NotPanics(t, func() { iter = v.Estack().Pop().Interop() })
|
||||
|
||||
v.Estack().PushVal(iter)
|
||||
require.NoError(t, iterator.Next(context))
|
||||
|
||||
v.Estack().PushVal(iter)
|
||||
require.Panics(t, func() { _ = iterator.Value(context) })
|
||||
})
|
||||
})
|
||||
t.Run("PickN", func(t *testing.T) {
|
||||
testFind(t, []byte{0x06}, istorage.FindPick0|istorage.FindValuesOnly|istorage.FindDeserialize, arr[:1])
|
||||
testFind(t, []byte{0x06}, istorage.FindPick1|istorage.FindValuesOnly|istorage.FindDeserialize, arr[1:2])
|
||||
// Array with 0 elements.
|
||||
testFind(t, []byte{0x07}, istorage.FindPick0|istorage.FindValuesOnly|istorage.FindDeserialize,
|
||||
[]stackitem.Item{nil})
|
||||
// Array with 1 element.
|
||||
testFind(t, []byte{0x08}, istorage.FindPick1|istorage.FindValuesOnly|istorage.FindDeserialize,
|
||||
[]stackitem.Item{nil})
|
||||
// Not an array, but serialized ByteArray.
|
||||
testFind(t, []byte{0x04}, istorage.FindPick1|istorage.FindValuesOnly|istorage.FindDeserialize,
|
||||
[]stackitem.Item{nil})
|
||||
})
|
||||
|
||||
t.Run("normal invocation, empty result", func(t *testing.T) {
|
||||
testFind(t, []byte{0x03}, istorage.FindDefault, nil)
|
||||
})
|
||||
|
||||
t.Run("invalid options", func(t *testing.T) {
|
||||
invalid := []int64{
|
||||
istorage.FindKeysOnly | istorage.FindValuesOnly,
|
||||
^istorage.FindAll,
|
||||
istorage.FindKeysOnly | istorage.FindDeserialize,
|
||||
istorage.FindPick0,
|
||||
istorage.FindPick0 | istorage.FindPick1 | istorage.FindDeserialize,
|
||||
istorage.FindPick0 | istorage.FindPick1,
|
||||
}
|
||||
for _, opts := range invalid {
|
||||
v.Estack().PushVal(opts)
|
||||
v.Estack().PushVal([]byte{0x01})
|
||||
v.Estack().PushVal(stackitem.NewInterop(&istorage.Context{ID: id}))
|
||||
require.Error(t, istorage.Find(context))
|
||||
}
|
||||
})
|
||||
t.Run("invalid type for storage.Context", func(t *testing.T) {
|
||||
v.Estack().PushVal(istorage.FindDefault)
|
||||
v.Estack().PushVal([]byte{0x01})
|
||||
v.Estack().PushVal(stackitem.NewInterop(nil))
|
||||
|
||||
require.Error(t, istorage.Find(context))
|
||||
})
|
||||
|
||||
t.Run("invalid id", func(t *testing.T) {
|
||||
invalidID := id + 1
|
||||
|
||||
v.Estack().PushVal(istorage.FindDefault)
|
||||
v.Estack().PushVal([]byte{0x01})
|
||||
v.Estack().PushVal(stackitem.NewInterop(&istorage.Context{ID: invalidID}))
|
||||
|
||||
require.NoError(t, istorage.Find(context))
|
||||
require.NoError(t, iterator.Next(context))
|
||||
require.False(t, v.Estack().Pop().Bool())
|
||||
})
|
||||
}
|
||||
|
||||
// Helper functions to create VM, InteropContext, TX, Account, Contract.
|
||||
|
||||
func createVM(t testing.TB) (*vm.VM, *interop.Context, *core.Blockchain) {
|
||||
chain, _ := chain.NewSingle(t)
|
||||
ic := chain.GetTestVM(trigger.Application, &transaction.Transaction{}, &block.Block{})
|
||||
v := ic.SpawnVM()
|
||||
return v, ic, chain
|
||||
}
|
||||
|
||||
func createVMAndContractState(t testing.TB) (*vm.VM, *state.Contract, *interop.Context, *core.Blockchain) {
|
||||
script := []byte("testscript")
|
||||
m := manifest.NewManifest("Test")
|
||||
ne, err := nef.NewFile(script)
|
||||
require.NoError(t, err)
|
||||
contractState := &state.Contract{
|
||||
ContractBase: state.ContractBase{
|
||||
NEF: *ne,
|
||||
Hash: hash.Hash160(script),
|
||||
Manifest: *m,
|
||||
ID: 123,
|
||||
},
|
||||
}
|
||||
|
||||
v, context, chain := createVM(t)
|
||||
return v, contractState, context, chain
|
||||
}
|
|
@ -1,270 +0,0 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/elliptic"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/fee"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
istorage "github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
)
|
||||
|
||||
var (
|
||||
errGasLimitExceeded = errors.New("gas limit exceeded")
|
||||
errFindInvalidOptions = errors.New("invalid Find options")
|
||||
)
|
||||
|
||||
// StorageContext contains storing id and read/write flag, it's used as
|
||||
// a context for storage manipulation functions.
|
||||
type StorageContext struct {
|
||||
ID int32
|
||||
ReadOnly bool
|
||||
}
|
||||
|
||||
// engineGetScriptContainer returns transaction or block that contains the script
|
||||
// being run.
|
||||
func engineGetScriptContainer(ic *interop.Context) error {
|
||||
var item stackitem.Item
|
||||
switch t := ic.Container.(type) {
|
||||
case *transaction.Transaction:
|
||||
item = native.TransactionToStackItem(t)
|
||||
case *block.Block:
|
||||
item = native.BlockToStackItem(t)
|
||||
default:
|
||||
return errors.New("unknown script container")
|
||||
}
|
||||
ic.VM.Estack().PushItem(item)
|
||||
return nil
|
||||
}
|
||||
|
||||
// storageDelete deletes stored key-value pair.
|
||||
func storageDelete(ic *interop.Context) error {
|
||||
stcInterface := ic.VM.Estack().Pop().Value()
|
||||
stc, ok := stcInterface.(*StorageContext)
|
||||
if !ok {
|
||||
return fmt.Errorf("%T is not a StorageContext", stcInterface)
|
||||
}
|
||||
if stc.ReadOnly {
|
||||
return errors.New("StorageContext is read only")
|
||||
}
|
||||
key := ic.VM.Estack().Pop().Bytes()
|
||||
ic.DAO.DeleteStorageItem(stc.ID, key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// storageGet returns stored key-value pair.
|
||||
func storageGet(ic *interop.Context) error {
|
||||
stcInterface := ic.VM.Estack().Pop().Value()
|
||||
stc, ok := stcInterface.(*StorageContext)
|
||||
if !ok {
|
||||
return fmt.Errorf("%T is not a StorageContext", stcInterface)
|
||||
}
|
||||
key := ic.VM.Estack().Pop().Bytes()
|
||||
si := ic.DAO.GetStorageItem(stc.ID, key)
|
||||
if si != nil {
|
||||
ic.VM.Estack().PushItem(stackitem.NewByteArray([]byte(si)))
|
||||
} else {
|
||||
ic.VM.Estack().PushItem(stackitem.Null{})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// storageGetContext returns storage context (scripthash).
|
||||
func storageGetContext(ic *interop.Context) error {
|
||||
return storageGetContextInternal(ic, false)
|
||||
}
|
||||
|
||||
// storageGetReadOnlyContext returns read-only context (scripthash).
|
||||
func storageGetReadOnlyContext(ic *interop.Context) error {
|
||||
return storageGetContextInternal(ic, true)
|
||||
}
|
||||
|
||||
// storageGetContextInternal is internal version of storageGetContext and
|
||||
// storageGetReadOnlyContext which allows to specify ReadOnly context flag.
|
||||
func storageGetContextInternal(ic *interop.Context, isReadOnly bool) error {
|
||||
contract, err := ic.GetContract(ic.VM.GetCurrentScriptHash())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sc := &StorageContext{
|
||||
ID: contract.ID,
|
||||
ReadOnly: isReadOnly,
|
||||
}
|
||||
ic.VM.Estack().PushItem(stackitem.NewInterop(sc))
|
||||
return nil
|
||||
}
|
||||
|
||||
func putWithContext(ic *interop.Context, stc *StorageContext, key []byte, value []byte) error {
|
||||
if len(key) > storage.MaxStorageKeyLen {
|
||||
return errors.New("key is too big")
|
||||
}
|
||||
if len(value) > storage.MaxStorageValueLen {
|
||||
return errors.New("value is too big")
|
||||
}
|
||||
if stc.ReadOnly {
|
||||
return errors.New("StorageContext is read only")
|
||||
}
|
||||
si := ic.DAO.GetStorageItem(stc.ID, key)
|
||||
sizeInc := len(value)
|
||||
if si == nil {
|
||||
sizeInc = len(key) + len(value)
|
||||
} else if len(value) != 0 {
|
||||
if len(value) <= len(si) {
|
||||
sizeInc = (len(value)-1)/4 + 1
|
||||
} else if len(si) != 0 {
|
||||
sizeInc = (len(si)-1)/4 + 1 + len(value) - len(si)
|
||||
}
|
||||
}
|
||||
if !ic.VM.AddGas(int64(sizeInc) * ic.BaseStorageFee()) {
|
||||
return errGasLimitExceeded
|
||||
}
|
||||
ic.DAO.PutStorageItem(stc.ID, key, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// storagePut puts key-value pair into the storage.
|
||||
func storagePut(ic *interop.Context) error {
|
||||
stcInterface := ic.VM.Estack().Pop().Value()
|
||||
stc, ok := stcInterface.(*StorageContext)
|
||||
if !ok {
|
||||
return fmt.Errorf("%T is not a StorageContext", stcInterface)
|
||||
}
|
||||
key := ic.VM.Estack().Pop().Bytes()
|
||||
value := ic.VM.Estack().Pop().Bytes()
|
||||
return putWithContext(ic, stc, key, value)
|
||||
}
|
||||
|
||||
// storageContextAsReadOnly sets given context to read-only mode.
|
||||
func storageContextAsReadOnly(ic *interop.Context) error {
|
||||
stcInterface := ic.VM.Estack().Pop().Value()
|
||||
stc, ok := stcInterface.(*StorageContext)
|
||||
if !ok {
|
||||
return fmt.Errorf("%T is not a StorageContext", stcInterface)
|
||||
}
|
||||
if !stc.ReadOnly {
|
||||
stx := &StorageContext{
|
||||
ID: stc.ID,
|
||||
ReadOnly: true,
|
||||
}
|
||||
stc = stx
|
||||
}
|
||||
ic.VM.Estack().PushItem(stackitem.NewInterop(stc))
|
||||
return nil
|
||||
}
|
||||
|
||||
// storageFind finds stored key-value pair.
|
||||
func storageFind(ic *interop.Context) error {
|
||||
stcInterface := ic.VM.Estack().Pop().Value()
|
||||
stc, ok := stcInterface.(*StorageContext)
|
||||
if !ok {
|
||||
return fmt.Errorf("%T is not a StorageContext", stcInterface)
|
||||
}
|
||||
prefix := ic.VM.Estack().Pop().Bytes()
|
||||
opts := ic.VM.Estack().Pop().BigInt().Int64()
|
||||
if opts&^istorage.FindAll != 0 {
|
||||
return fmt.Errorf("%w: unknown flag", errFindInvalidOptions)
|
||||
}
|
||||
if opts&istorage.FindKeysOnly != 0 &&
|
||||
opts&(istorage.FindDeserialize|istorage.FindPick0|istorage.FindPick1) != 0 {
|
||||
return fmt.Errorf("%w KeysOnly conflicts with other options", errFindInvalidOptions)
|
||||
}
|
||||
if opts&istorage.FindValuesOnly != 0 &&
|
||||
opts&(istorage.FindKeysOnly|istorage.FindRemovePrefix) != 0 {
|
||||
return fmt.Errorf("%w: KeysOnly conflicts with ValuesOnly", errFindInvalidOptions)
|
||||
}
|
||||
if opts&istorage.FindPick0 != 0 && opts&istorage.FindPick1 != 0 {
|
||||
return fmt.Errorf("%w: Pick0 conflicts with Pick1", errFindInvalidOptions)
|
||||
}
|
||||
if opts&istorage.FindDeserialize == 0 && (opts&istorage.FindPick0 != 0 || opts&istorage.FindPick1 != 0) {
|
||||
return fmt.Errorf("%w: PickN is specified without Deserialize", errFindInvalidOptions)
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
seekres := ic.DAO.SeekAsync(ctx, stc.ID, storage.SeekRange{Prefix: prefix})
|
||||
item := istorage.NewIterator(seekres, prefix, opts)
|
||||
ic.VM.Estack().PushItem(stackitem.NewInterop(item))
|
||||
ic.RegisterCancelFunc(func() {
|
||||
cancel()
|
||||
// Underlying persistent store is likely to be a private MemCachedStore. Thus,
|
||||
// to avoid concurrent map iteration and map write we need to wait until internal
|
||||
// seek goroutine is finished, because it can access underlying persistent store.
|
||||
for range seekres {
|
||||
}
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// contractCreateMultisigAccount calculates multisig contract scripthash for a
|
||||
// given m and a set of public keys.
|
||||
func contractCreateMultisigAccount(ic *interop.Context) error {
|
||||
m := ic.VM.Estack().Pop().BigInt()
|
||||
mu64 := m.Uint64()
|
||||
if !m.IsUint64() || mu64 > math.MaxInt32 {
|
||||
return errors.New("m must be positive and fit int32")
|
||||
}
|
||||
arr := ic.VM.Estack().Pop().Array()
|
||||
pubs := make(keys.PublicKeys, len(arr))
|
||||
for i, pk := range arr {
|
||||
p, err := keys.NewPublicKeyFromBytes(pk.Value().([]byte), elliptic.P256())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pubs[i] = p
|
||||
}
|
||||
var invokeFee int64
|
||||
if ic.IsHardforkEnabled(config.HFAspidochelone) {
|
||||
invokeFee = fee.ECDSAVerifyPrice * int64(len(pubs))
|
||||
} else {
|
||||
invokeFee = 1 << 8
|
||||
}
|
||||
invokeFee *= ic.BaseExecFee()
|
||||
if !ic.VM.AddGas(invokeFee) {
|
||||
return errors.New("gas limit exceeded")
|
||||
}
|
||||
script, err := smartcontract.CreateMultiSigRedeemScript(int(mu64), pubs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ic.VM.Estack().PushItem(stackitem.NewByteArray(hash.Hash160(script).BytesBE()))
|
||||
return nil
|
||||
}
|
||||
|
||||
// contractCreateStandardAccount calculates contract scripthash for a given public key.
|
||||
func contractCreateStandardAccount(ic *interop.Context) error {
|
||||
h := ic.VM.Estack().Pop().Bytes()
|
||||
p, err := keys.NewPublicKeyFromBytes(h, elliptic.P256())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var invokeFee int64
|
||||
if ic.IsHardforkEnabled(config.HFAspidochelone) {
|
||||
invokeFee = fee.ECDSAVerifyPrice
|
||||
} else {
|
||||
invokeFee = 1 << 8
|
||||
}
|
||||
invokeFee *= ic.BaseExecFee()
|
||||
if !ic.VM.AddGas(invokeFee) {
|
||||
return errors.New("gas limit exceeded")
|
||||
}
|
||||
ic.VM.Estack().PushItem(stackitem.NewByteArray(p.GetScriptHash().BytesBE()))
|
||||
return nil
|
||||
}
|
||||
|
||||
// contractGetCallFlags returns current context calling flags.
|
||||
func contractGetCallFlags(ic *interop.Context) error {
|
||||
ic.VM.Estack().PushItem(stackitem.NewBigInteger(big.NewInt(int64(ic.VM.Context().GetCallFlags()))))
|
||||
return nil
|
||||
}
|
|
@ -1,984 +0,0 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/internal/contracts"
|
||||
"github.com/nspcc-dev/neo-go/internal/random"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/iterator"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/runtime"
|
||||
istorage "github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"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/nef"
|
||||
"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/vm/stackitem"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var pathToInternalContracts = filepath.Join("..", "..", "internal", "contracts")
|
||||
|
||||
// Tests are taken from
|
||||
// https://github.com/neo-project/neo/blob/master/tests/neo.UnitTests/SmartContract/UT_ApplicationEngine.Runtime.cs
|
||||
func TestRuntimeGetRandomCompatibility(t *testing.T) {
|
||||
bc := newTestChain(t)
|
||||
|
||||
b := getSharpTestGenesis(t)
|
||||
tx := getSharpTestTx(util.Uint160{})
|
||||
ic := bc.newInteropContext(trigger.Application, bc.dao.GetWrapped(), b, tx)
|
||||
ic.Network = 860833102 // Old mainnet magic used by C# tests.
|
||||
|
||||
ic.VM = vm.New()
|
||||
ic.VM.LoadScript([]byte{0x01})
|
||||
ic.VM.GasLimit = 1100_00000000
|
||||
|
||||
require.NoError(t, runtime.GetRandom(ic))
|
||||
require.Equal(t, "271339657438512451304577787170704246350", ic.VM.Estack().Pop().BigInt().String())
|
||||
|
||||
require.NoError(t, runtime.GetRandom(ic))
|
||||
require.Equal(t, "98548189559099075644778613728143131367", ic.VM.Estack().Pop().BigInt().String())
|
||||
|
||||
require.NoError(t, runtime.GetRandom(ic))
|
||||
require.Equal(t, "247654688993873392544380234598471205121", ic.VM.Estack().Pop().BigInt().String())
|
||||
|
||||
require.NoError(t, runtime.GetRandom(ic))
|
||||
require.Equal(t, "291082758879475329976578097236212073607", ic.VM.Estack().Pop().BigInt().String())
|
||||
|
||||
require.NoError(t, runtime.GetRandom(ic))
|
||||
require.Equal(t, "247152297361212656635216876565962360375", ic.VM.Estack().Pop().BigInt().String())
|
||||
}
|
||||
|
||||
func getSharpTestTx(sender util.Uint160) *transaction.Transaction {
|
||||
tx := transaction.New([]byte{byte(opcode.PUSH2)}, 0)
|
||||
tx.Nonce = 0
|
||||
tx.Signers = append(tx.Signers, transaction.Signer{
|
||||
Account: sender,
|
||||
Scopes: transaction.CalledByEntry,
|
||||
})
|
||||
tx.Attributes = []transaction.Attribute{}
|
||||
tx.Scripts = append(tx.Scripts, transaction.Witness{InvocationScript: []byte{}, VerificationScript: []byte{}})
|
||||
return tx
|
||||
}
|
||||
|
||||
func getSharpTestGenesis(t *testing.T) *block.Block {
|
||||
const configPath = "../../config"
|
||||
|
||||
cfg, err := config.Load(configPath, netmode.MainNet)
|
||||
require.NoError(t, err)
|
||||
b, err := createGenesisBlock(cfg.ProtocolConfiguration)
|
||||
require.NoError(t, err)
|
||||
return b
|
||||
}
|
||||
|
||||
func TestRuntimeGetNotifications(t *testing.T) {
|
||||
v, ic, _ := createVM(t)
|
||||
|
||||
ic.Notifications = []state.NotificationEvent{
|
||||
{ScriptHash: util.Uint160{1}, Name: "Event1", Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte{11})})},
|
||||
{ScriptHash: util.Uint160{2}, Name: "Event2", Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte{22})})},
|
||||
{ScriptHash: util.Uint160{1}, Name: "Event1", Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte{33})})},
|
||||
}
|
||||
|
||||
t.Run("NoFilter", func(t *testing.T) {
|
||||
v.Estack().PushVal(stackitem.Null{})
|
||||
require.NoError(t, runtime.GetNotifications(ic))
|
||||
|
||||
arr := v.Estack().Pop().Array()
|
||||
require.Equal(t, len(ic.Notifications), len(arr))
|
||||
for i := range arr {
|
||||
elem := arr[i].Value().([]stackitem.Item)
|
||||
require.Equal(t, ic.Notifications[i].ScriptHash.BytesBE(), elem[0].Value())
|
||||
name, err := stackitem.ToString(elem[1])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ic.Notifications[i].Name, name)
|
||||
ic.Notifications[i].Item.MarkAsReadOnly() // tiny hack for test to be able to compare object references.
|
||||
require.Equal(t, ic.Notifications[i].Item, elem[2])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("WithFilter", func(t *testing.T) {
|
||||
h := util.Uint160{2}.BytesBE()
|
||||
v.Estack().PushVal(h)
|
||||
require.NoError(t, runtime.GetNotifications(ic))
|
||||
|
||||
arr := v.Estack().Pop().Array()
|
||||
require.Equal(t, 1, len(arr))
|
||||
elem := arr[0].Value().([]stackitem.Item)
|
||||
require.Equal(t, h, elem[0].Value())
|
||||
name, err := stackitem.ToString(elem[1])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ic.Notifications[1].Name, name)
|
||||
require.Equal(t, ic.Notifications[1].Item, elem[2])
|
||||
})
|
||||
}
|
||||
|
||||
func TestRuntimeGetInvocationCounter(t *testing.T) {
|
||||
v, ic, bc := createVM(t)
|
||||
|
||||
cs, _ := contracts.GetTestContractState(t, pathToInternalContracts, 4, 5, random.Uint160()) // sender and IDs are not important for the test
|
||||
require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, cs))
|
||||
|
||||
ic.Invocations[hash.Hash160([]byte{2})] = 42
|
||||
|
||||
t.Run("No invocations", func(t *testing.T) {
|
||||
v.Load([]byte{1})
|
||||
// do not return an error in this case.
|
||||
require.NoError(t, runtime.GetInvocationCounter(ic))
|
||||
require.EqualValues(t, 1, v.Estack().Pop().BigInt().Int64())
|
||||
})
|
||||
t.Run("NonZero", func(t *testing.T) {
|
||||
v.Load([]byte{2})
|
||||
require.NoError(t, runtime.GetInvocationCounter(ic))
|
||||
require.EqualValues(t, 42, v.Estack().Pop().BigInt().Int64())
|
||||
})
|
||||
t.Run("Contract", func(t *testing.T) {
|
||||
w := io.NewBufBinWriter()
|
||||
emit.AppCall(w.BinWriter, cs.Hash, "invocCounter", callflag.All)
|
||||
v.LoadWithFlags(w.Bytes(), callflag.All)
|
||||
require.NoError(t, v.Run())
|
||||
require.EqualValues(t, 1, v.Estack().Pop().BigInt().Int64())
|
||||
})
|
||||
}
|
||||
|
||||
func TestStoragePut(t *testing.T) {
|
||||
_, cs, ic, bc := createVMAndContractState(t)
|
||||
|
||||
require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, cs))
|
||||
|
||||
initVM := func(t *testing.T, key, value []byte, gas int64) {
|
||||
v := ic.SpawnVM()
|
||||
v.LoadScript(cs.NEF.Script)
|
||||
v.GasLimit = gas
|
||||
v.Estack().PushVal(value)
|
||||
v.Estack().PushVal(key)
|
||||
require.NoError(t, storageGetContext(ic))
|
||||
}
|
||||
|
||||
t.Run("create, not enough gas", func(t *testing.T) {
|
||||
initVM(t, []byte{1}, []byte{2, 3}, 2*native.DefaultStoragePrice)
|
||||
err := storagePut(ic)
|
||||
require.True(t, errors.Is(err, errGasLimitExceeded), "got: %v", err)
|
||||
})
|
||||
|
||||
initVM(t, []byte{4}, []byte{5, 6}, 3*native.DefaultStoragePrice)
|
||||
require.NoError(t, storagePut(ic))
|
||||
|
||||
t.Run("update", func(t *testing.T) {
|
||||
t.Run("not enough gas", func(t *testing.T) {
|
||||
initVM(t, []byte{4}, []byte{5, 6, 7, 8}, native.DefaultStoragePrice)
|
||||
err := storagePut(ic)
|
||||
require.True(t, errors.Is(err, errGasLimitExceeded), "got: %v", err)
|
||||
})
|
||||
initVM(t, []byte{4}, []byte{5, 6, 7, 8}, 3*native.DefaultStoragePrice)
|
||||
require.NoError(t, storagePut(ic))
|
||||
initVM(t, []byte{4}, []byte{5, 6}, native.DefaultStoragePrice)
|
||||
require.NoError(t, storagePut(ic))
|
||||
})
|
||||
|
||||
t.Run("check limits", func(t *testing.T) {
|
||||
initVM(t, make([]byte, storage.MaxStorageKeyLen), make([]byte, storage.MaxStorageValueLen), -1)
|
||||
require.NoError(t, storagePut(ic))
|
||||
})
|
||||
|
||||
t.Run("bad", func(t *testing.T) {
|
||||
t.Run("readonly context", func(t *testing.T) {
|
||||
initVM(t, []byte{1}, []byte{1}, -1)
|
||||
require.NoError(t, storageContextAsReadOnly(ic))
|
||||
require.Error(t, storagePut(ic))
|
||||
})
|
||||
t.Run("big key", func(t *testing.T) {
|
||||
initVM(t, make([]byte, storage.MaxStorageKeyLen+1), []byte{1}, -1)
|
||||
require.Error(t, storagePut(ic))
|
||||
})
|
||||
t.Run("big value", func(t *testing.T) {
|
||||
initVM(t, []byte{1}, make([]byte, storage.MaxStorageValueLen+1), -1)
|
||||
require.Error(t, storagePut(ic))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestStorageDelete(t *testing.T) {
|
||||
v, cs, ic, bc := createVMAndContractState(t)
|
||||
|
||||
require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, cs))
|
||||
v.LoadScriptWithHash(cs.NEF.Script, cs.Hash, callflag.All)
|
||||
put := func(key, value string, flag int) {
|
||||
v.Estack().PushVal(value)
|
||||
v.Estack().PushVal(key)
|
||||
require.NoError(t, storageGetContext(ic))
|
||||
require.NoError(t, storagePut(ic))
|
||||
}
|
||||
put("key1", "value1", 0)
|
||||
put("key2", "value2", 0)
|
||||
put("key3", "value3", 0)
|
||||
|
||||
t.Run("good", func(t *testing.T) {
|
||||
v.Estack().PushVal("key1")
|
||||
require.NoError(t, storageGetContext(ic))
|
||||
require.NoError(t, storageDelete(ic))
|
||||
})
|
||||
t.Run("readonly context", func(t *testing.T) {
|
||||
v.Estack().PushVal("key2")
|
||||
require.NoError(t, storageGetReadOnlyContext(ic))
|
||||
require.Error(t, storageDelete(ic))
|
||||
})
|
||||
t.Run("readonly context (from normal)", func(t *testing.T) {
|
||||
v.Estack().PushVal("key3")
|
||||
require.NoError(t, storageGetContext(ic))
|
||||
require.NoError(t, storageContextAsReadOnly(ic))
|
||||
require.Error(t, storageDelete(ic))
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkStorageFind(b *testing.B) {
|
||||
for count := 10; count <= 10000; count *= 10 {
|
||||
b.Run(fmt.Sprintf("%dElements", count), func(b *testing.B) {
|
||||
v, contractState, context, chain := createVMAndContractState(b)
|
||||
require.NoError(b, chain.contracts.Management.PutContractState(chain.dao, contractState))
|
||||
|
||||
items := make(map[string]state.StorageItem)
|
||||
for i := 0; i < count; i++ {
|
||||
items["abc"+random.String(10)] = random.Bytes(10)
|
||||
}
|
||||
for k, v := range items {
|
||||
context.DAO.PutStorageItem(contractState.ID, []byte(k), v)
|
||||
context.DAO.PutStorageItem(contractState.ID+1, []byte(k), v)
|
||||
}
|
||||
changes, err := context.DAO.Persist()
|
||||
require.NoError(b, err)
|
||||
require.NotEqual(b, 0, changes)
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
b.StopTimer()
|
||||
v.Estack().PushVal(istorage.FindDefault)
|
||||
v.Estack().PushVal("abc")
|
||||
v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ID: contractState.ID}))
|
||||
b.StartTimer()
|
||||
err := storageFind(context)
|
||||
if err != nil {
|
||||
b.FailNow()
|
||||
}
|
||||
b.StopTimer()
|
||||
context.Finalize()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStorageFindIteratorNext(b *testing.B) {
|
||||
for count := 10; count <= 10000; count *= 10 {
|
||||
cases := map[string]int{
|
||||
"Pick1": 1,
|
||||
"PickHalf": count / 2,
|
||||
"PickAll": count,
|
||||
}
|
||||
b.Run(fmt.Sprintf("%dElements", count), func(b *testing.B) {
|
||||
for name, last := range cases {
|
||||
b.Run(name, func(b *testing.B) {
|
||||
v, contractState, context, chain := createVMAndContractState(b)
|
||||
require.NoError(b, chain.contracts.Management.PutContractState(chain.dao, contractState))
|
||||
|
||||
items := make(map[string]state.StorageItem)
|
||||
for i := 0; i < count; i++ {
|
||||
items["abc"+random.String(10)] = random.Bytes(10)
|
||||
}
|
||||
for k, v := range items {
|
||||
context.DAO.PutStorageItem(contractState.ID, []byte(k), v)
|
||||
context.DAO.PutStorageItem(contractState.ID+1, []byte(k), v)
|
||||
}
|
||||
changes, err := context.DAO.Persist()
|
||||
require.NoError(b, err)
|
||||
require.NotEqual(b, 0, changes)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
b.StopTimer()
|
||||
v.Estack().PushVal(istorage.FindDefault)
|
||||
v.Estack().PushVal("abc")
|
||||
v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ID: contractState.ID}))
|
||||
b.StartTimer()
|
||||
err := storageFind(context)
|
||||
b.StopTimer()
|
||||
if err != nil {
|
||||
b.FailNow()
|
||||
}
|
||||
res := context.VM.Estack().Pop().Item()
|
||||
for i := 0; i < last; i++ {
|
||||
context.VM.Estack().PushVal(res)
|
||||
b.StartTimer()
|
||||
require.NoError(b, iterator.Next(context))
|
||||
b.StopTimer()
|
||||
require.True(b, context.VM.Estack().Pop().Bool())
|
||||
}
|
||||
|
||||
context.VM.Estack().PushVal(res)
|
||||
require.NoError(b, iterator.Next(context))
|
||||
actual := context.VM.Estack().Pop().Bool()
|
||||
if last == count {
|
||||
require.False(b, actual)
|
||||
} else {
|
||||
require.True(b, actual)
|
||||
}
|
||||
context.Finalize()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStorageFind(t *testing.T) {
|
||||
v, contractState, context, chain := createVMAndContractState(t)
|
||||
|
||||
arr := []stackitem.Item{
|
||||
stackitem.NewBigInteger(big.NewInt(42)),
|
||||
stackitem.NewByteArray([]byte("second")),
|
||||
stackitem.Null{},
|
||||
}
|
||||
rawArr, err := stackitem.Serialize(stackitem.NewArray(arr))
|
||||
require.NoError(t, err)
|
||||
rawArr0, err := stackitem.Serialize(stackitem.NewArray(arr[:0]))
|
||||
require.NoError(t, err)
|
||||
rawArr1, err := stackitem.Serialize(stackitem.NewArray(arr[:1]))
|
||||
require.NoError(t, err)
|
||||
|
||||
skeys := [][]byte{{0x01, 0x02}, {0x02, 0x01}, {0x01, 0x01},
|
||||
{0x04, 0x00}, {0x05, 0x00}, {0x06}, {0x07}, {0x08},
|
||||
{0x09, 0x12, 0x34}, {0x09, 0x12, 0x56},
|
||||
}
|
||||
items := []state.StorageItem{
|
||||
[]byte{0x01, 0x02, 0x03, 0x04},
|
||||
[]byte{0x04, 0x03, 0x02, 0x01},
|
||||
[]byte{0x03, 0x04, 0x05, 0x06},
|
||||
[]byte{byte(stackitem.ByteArrayT), 2, 0xCA, 0xFE},
|
||||
[]byte{0xFF, 0xFF},
|
||||
rawArr,
|
||||
rawArr0,
|
||||
rawArr1,
|
||||
[]byte{111},
|
||||
[]byte{222},
|
||||
}
|
||||
|
||||
require.NoError(t, chain.contracts.Management.PutContractState(chain.dao, contractState))
|
||||
|
||||
id := contractState.ID
|
||||
|
||||
for i := range skeys {
|
||||
context.DAO.PutStorageItem(id, skeys[i], items[i])
|
||||
}
|
||||
|
||||
testFind := func(t *testing.T, prefix []byte, opts int64, expected []stackitem.Item) {
|
||||
v.Estack().PushVal(opts)
|
||||
v.Estack().PushVal(prefix)
|
||||
v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ID: id}))
|
||||
|
||||
err := storageFind(context)
|
||||
require.NoError(t, err)
|
||||
|
||||
var iter *stackitem.Interop
|
||||
require.NotPanics(t, func() { iter = v.Estack().Pop().Interop() })
|
||||
|
||||
for i := range expected { // sorted indices with mathing prefix
|
||||
v.Estack().PushVal(iter)
|
||||
require.NoError(t, iterator.Next(context))
|
||||
require.True(t, v.Estack().Pop().Bool())
|
||||
|
||||
v.Estack().PushVal(iter)
|
||||
if expected[i] == nil {
|
||||
require.Panics(t, func() { _ = iterator.Value(context) })
|
||||
return
|
||||
}
|
||||
require.NoError(t, iterator.Value(context))
|
||||
require.Equal(t, expected[i], v.Estack().Pop().Item())
|
||||
}
|
||||
|
||||
v.Estack().PushVal(iter)
|
||||
require.NoError(t, iterator.Next(context))
|
||||
require.False(t, v.Estack().Pop().Bool())
|
||||
}
|
||||
|
||||
t.Run("normal invocation", func(t *testing.T) {
|
||||
testFind(t, []byte{0x01}, istorage.FindDefault, []stackitem.Item{
|
||||
stackitem.NewStruct([]stackitem.Item{
|
||||
stackitem.NewByteArray(skeys[2]),
|
||||
stackitem.NewByteArray(items[2]),
|
||||
}),
|
||||
stackitem.NewStruct([]stackitem.Item{
|
||||
stackitem.NewByteArray(skeys[0]),
|
||||
stackitem.NewByteArray(items[0]),
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("keys only", func(t *testing.T) {
|
||||
testFind(t, []byte{0x01}, istorage.FindKeysOnly, []stackitem.Item{
|
||||
stackitem.NewByteArray(skeys[2]),
|
||||
stackitem.NewByteArray(skeys[0]),
|
||||
})
|
||||
})
|
||||
t.Run("remove prefix", func(t *testing.T) {
|
||||
testFind(t, []byte{0x01}, istorage.FindKeysOnly|istorage.FindRemovePrefix, []stackitem.Item{
|
||||
stackitem.NewByteArray(skeys[2][1:]),
|
||||
stackitem.NewByteArray(skeys[0][1:]),
|
||||
})
|
||||
testFind(t, []byte{0x09, 0x12}, istorage.FindKeysOnly|istorage.FindRemovePrefix, []stackitem.Item{
|
||||
stackitem.NewByteArray(skeys[8][2:]),
|
||||
stackitem.NewByteArray(skeys[9][2:]),
|
||||
})
|
||||
})
|
||||
t.Run("values only", func(t *testing.T) {
|
||||
testFind(t, []byte{0x01}, istorage.FindValuesOnly, []stackitem.Item{
|
||||
stackitem.NewByteArray(items[2]),
|
||||
stackitem.NewByteArray(items[0]),
|
||||
})
|
||||
})
|
||||
t.Run("deserialize values", func(t *testing.T) {
|
||||
testFind(t, []byte{0x04}, istorage.FindValuesOnly|istorage.FindDeserialize, []stackitem.Item{
|
||||
stackitem.NewByteArray(items[3][2:]),
|
||||
})
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
v.Estack().PushVal(istorage.FindDeserialize)
|
||||
v.Estack().PushVal([]byte{0x05})
|
||||
v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ID: id}))
|
||||
err := storageFind(context)
|
||||
require.NoError(t, err)
|
||||
|
||||
var iter *stackitem.Interop
|
||||
require.NotPanics(t, func() { iter = v.Estack().Pop().Interop() })
|
||||
|
||||
v.Estack().PushVal(iter)
|
||||
require.NoError(t, iterator.Next(context))
|
||||
|
||||
v.Estack().PushVal(iter)
|
||||
require.Panics(t, func() { _ = iterator.Value(context) })
|
||||
})
|
||||
})
|
||||
t.Run("PickN", func(t *testing.T) {
|
||||
testFind(t, []byte{0x06}, istorage.FindPick0|istorage.FindValuesOnly|istorage.FindDeserialize, arr[:1])
|
||||
testFind(t, []byte{0x06}, istorage.FindPick1|istorage.FindValuesOnly|istorage.FindDeserialize, arr[1:2])
|
||||
// Array with 0 elements.
|
||||
testFind(t, []byte{0x07}, istorage.FindPick0|istorage.FindValuesOnly|istorage.FindDeserialize,
|
||||
[]stackitem.Item{nil})
|
||||
// Array with 1 element.
|
||||
testFind(t, []byte{0x08}, istorage.FindPick1|istorage.FindValuesOnly|istorage.FindDeserialize,
|
||||
[]stackitem.Item{nil})
|
||||
// Not an array, but serialized ByteArray.
|
||||
testFind(t, []byte{0x04}, istorage.FindPick1|istorage.FindValuesOnly|istorage.FindDeserialize,
|
||||
[]stackitem.Item{nil})
|
||||
})
|
||||
|
||||
t.Run("normal invocation, empty result", func(t *testing.T) {
|
||||
testFind(t, []byte{0x03}, istorage.FindDefault, nil)
|
||||
})
|
||||
|
||||
t.Run("invalid options", func(t *testing.T) {
|
||||
invalid := []int64{
|
||||
istorage.FindKeysOnly | istorage.FindValuesOnly,
|
||||
^istorage.FindAll,
|
||||
istorage.FindKeysOnly | istorage.FindDeserialize,
|
||||
istorage.FindPick0,
|
||||
istorage.FindPick0 | istorage.FindPick1 | istorage.FindDeserialize,
|
||||
istorage.FindPick0 | istorage.FindPick1,
|
||||
}
|
||||
for _, opts := range invalid {
|
||||
v.Estack().PushVal(opts)
|
||||
v.Estack().PushVal([]byte{0x01})
|
||||
v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ID: id}))
|
||||
require.Error(t, storageFind(context))
|
||||
}
|
||||
})
|
||||
t.Run("invalid type for StorageContext", func(t *testing.T) {
|
||||
v.Estack().PushVal(istorage.FindDefault)
|
||||
v.Estack().PushVal([]byte{0x01})
|
||||
v.Estack().PushVal(stackitem.NewInterop(nil))
|
||||
|
||||
require.Error(t, storageFind(context))
|
||||
})
|
||||
|
||||
t.Run("invalid id", func(t *testing.T) {
|
||||
invalidID := id + 1
|
||||
|
||||
v.Estack().PushVal(istorage.FindDefault)
|
||||
v.Estack().PushVal([]byte{0x01})
|
||||
v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ID: invalidID}))
|
||||
|
||||
require.NoError(t, storageFind(context))
|
||||
require.NoError(t, iterator.Next(context))
|
||||
require.False(t, v.Estack().Pop().Bool())
|
||||
})
|
||||
}
|
||||
|
||||
// Helper functions to create VM, InteropContext, TX, Account, Contract.
|
||||
|
||||
func createVM(t testing.TB) (*vm.VM, *interop.Context, *Blockchain) {
|
||||
chain := newTestChain(t)
|
||||
context := chain.newInteropContext(trigger.Application,
|
||||
chain.dao.GetWrapped(), nil, nil)
|
||||
v := context.SpawnVM()
|
||||
return v, context, chain
|
||||
}
|
||||
|
||||
func createVMAndContractState(t testing.TB) (*vm.VM, *state.Contract, *interop.Context, *Blockchain) {
|
||||
script := []byte("testscript")
|
||||
m := manifest.NewManifest("Test")
|
||||
ne, err := nef.NewFile(script)
|
||||
require.NoError(t, err)
|
||||
contractState := &state.Contract{
|
||||
ContractBase: state.ContractBase{
|
||||
NEF: *ne,
|
||||
Hash: hash.Hash160(script),
|
||||
Manifest: *m,
|
||||
ID: 123,
|
||||
},
|
||||
}
|
||||
|
||||
v, context, chain := createVM(t)
|
||||
return v, contractState, context, chain
|
||||
}
|
||||
|
||||
func loadScript(ic *interop.Context, script []byte, args ...interface{}) {
|
||||
ic.SpawnVM()
|
||||
ic.VM.LoadScriptWithFlags(script, callflag.AllowCall)
|
||||
for i := range args {
|
||||
ic.VM.Estack().PushVal(args[i])
|
||||
}
|
||||
ic.VM.GasLimit = -1
|
||||
}
|
||||
|
||||
func loadScriptWithHashAndFlags(ic *interop.Context, script []byte, hash util.Uint160, f callflag.CallFlag, args ...interface{}) {
|
||||
ic.SpawnVM()
|
||||
ic.VM.LoadScriptWithHash(script, hash, f)
|
||||
for i := range args {
|
||||
ic.VM.Estack().PushVal(args[i])
|
||||
}
|
||||
ic.VM.GasLimit = -1
|
||||
}
|
||||
|
||||
func TestContractCall(t *testing.T) {
|
||||
_, ic, bc := createVM(t)
|
||||
|
||||
cs, currCs := contracts.GetTestContractState(t, pathToInternalContracts, 4, 5, random.Uint160()) // sender and IDs are not important for the test
|
||||
require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, cs))
|
||||
require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, currCs))
|
||||
|
||||
currScript := currCs.NEF.Script
|
||||
h := cs.Hash
|
||||
|
||||
addArgs := stackitem.NewArray([]stackitem.Item{stackitem.Make(1), stackitem.Make(2)})
|
||||
t.Run("Good", func(t *testing.T) {
|
||||
t.Run("2 arguments", func(t *testing.T) {
|
||||
loadScript(ic, currScript, 42)
|
||||
ic.VM.Estack().PushVal(addArgs)
|
||||
ic.VM.Estack().PushVal(callflag.All)
|
||||
ic.VM.Estack().PushVal("add")
|
||||
ic.VM.Estack().PushVal(h.BytesBE())
|
||||
require.NoError(t, contract.Call(ic))
|
||||
require.NoError(t, ic.VM.Run())
|
||||
require.Equal(t, 2, ic.VM.Estack().Len())
|
||||
require.Equal(t, big.NewInt(3), ic.VM.Estack().Pop().Value())
|
||||
require.Equal(t, big.NewInt(42), ic.VM.Estack().Pop().Value())
|
||||
})
|
||||
t.Run("3 arguments", func(t *testing.T) {
|
||||
loadScript(ic, currScript, 42)
|
||||
ic.VM.Estack().PushVal(stackitem.NewArray(
|
||||
append(addArgs.Value().([]stackitem.Item), stackitem.Make(3))))
|
||||
ic.VM.Estack().PushVal(callflag.All)
|
||||
ic.VM.Estack().PushVal("add")
|
||||
ic.VM.Estack().PushVal(h.BytesBE())
|
||||
require.NoError(t, contract.Call(ic))
|
||||
require.NoError(t, ic.VM.Run())
|
||||
require.Equal(t, 2, ic.VM.Estack().Len())
|
||||
require.Equal(t, big.NewInt(6), ic.VM.Estack().Pop().Value())
|
||||
require.Equal(t, big.NewInt(42), ic.VM.Estack().Pop().Value())
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("CallExInvalidFlag", func(t *testing.T) {
|
||||
loadScript(ic, currScript, 42)
|
||||
ic.VM.Estack().PushVal(addArgs)
|
||||
ic.VM.Estack().PushVal(byte(0xFF))
|
||||
ic.VM.Estack().PushVal("add")
|
||||
ic.VM.Estack().PushVal(h.BytesBE())
|
||||
require.Error(t, contract.Call(ic))
|
||||
})
|
||||
|
||||
runInvalid := func(args ...interface{}) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
loadScriptWithHashAndFlags(ic, currScript, h, callflag.All, 42)
|
||||
for i := range args {
|
||||
ic.VM.Estack().PushVal(args[i])
|
||||
}
|
||||
// interops can both return error and panic,
|
||||
// we don't care which kind of error has occurred
|
||||
require.Panics(t, func() {
|
||||
err := contract.Call(ic)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("Invalid", func(t *testing.T) {
|
||||
t.Run("Hash", runInvalid(addArgs, "add", h.BytesBE()[1:]))
|
||||
t.Run("MissingHash", runInvalid(addArgs, "add", util.Uint160{}.BytesBE()))
|
||||
t.Run("Method", runInvalid(addArgs, stackitem.NewInterop("add"), h.BytesBE()))
|
||||
t.Run("MissingMethod", runInvalid(addArgs, "sub", h.BytesBE()))
|
||||
t.Run("DisallowedMethod", runInvalid(stackitem.NewArray(nil), "ret7", h.BytesBE()))
|
||||
t.Run("Arguments", runInvalid(1, "add", h.BytesBE()))
|
||||
t.Run("NotEnoughArguments", runInvalid(
|
||||
stackitem.NewArray([]stackitem.Item{stackitem.Make(1)}), "add", h.BytesBE()))
|
||||
t.Run("TooMuchArguments", runInvalid(
|
||||
stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.Make(1), stackitem.Make(2), stackitem.Make(3), stackitem.Make(4)}),
|
||||
"add", h.BytesBE()))
|
||||
})
|
||||
|
||||
t.Run("ReturnValues", func(t *testing.T) {
|
||||
t.Run("Many", func(t *testing.T) {
|
||||
loadScript(ic, currScript, 42)
|
||||
ic.VM.Estack().PushVal(stackitem.NewArray(nil))
|
||||
ic.VM.Estack().PushVal(callflag.All)
|
||||
ic.VM.Estack().PushVal("invalidReturn")
|
||||
ic.VM.Estack().PushVal(h.BytesBE())
|
||||
require.NoError(t, contract.Call(ic))
|
||||
require.Error(t, ic.VM.Run())
|
||||
})
|
||||
t.Run("Void", func(t *testing.T) {
|
||||
loadScript(ic, currScript, 42)
|
||||
ic.VM.Estack().PushVal(stackitem.NewArray(nil))
|
||||
ic.VM.Estack().PushVal(callflag.All)
|
||||
ic.VM.Estack().PushVal("justReturn")
|
||||
ic.VM.Estack().PushVal(h.BytesBE())
|
||||
require.NoError(t, contract.Call(ic))
|
||||
require.NoError(t, ic.VM.Run())
|
||||
require.Equal(t, 2, ic.VM.Estack().Len())
|
||||
require.Equal(t, stackitem.Null{}, ic.VM.Estack().Pop().Item())
|
||||
require.Equal(t, big.NewInt(42), ic.VM.Estack().Pop().Value())
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("IsolatedStack", func(t *testing.T) {
|
||||
loadScript(ic, currScript, 42)
|
||||
ic.VM.Estack().PushVal(stackitem.NewArray(nil))
|
||||
ic.VM.Estack().PushVal(callflag.All)
|
||||
ic.VM.Estack().PushVal("drop")
|
||||
ic.VM.Estack().PushVal(h.BytesBE())
|
||||
require.NoError(t, contract.Call(ic))
|
||||
require.Error(t, ic.VM.Run())
|
||||
})
|
||||
|
||||
t.Run("CallInitialize", func(t *testing.T) {
|
||||
t.Run("Directly", runInvalid(stackitem.NewArray([]stackitem.Item{}), "_initialize", h.BytesBE()))
|
||||
|
||||
loadScript(ic, currScript, 42)
|
||||
ic.VM.Estack().PushVal(stackitem.NewArray([]stackitem.Item{stackitem.Make(5)}))
|
||||
ic.VM.Estack().PushVal(callflag.All)
|
||||
ic.VM.Estack().PushVal("add3")
|
||||
ic.VM.Estack().PushVal(h.BytesBE())
|
||||
require.NoError(t, contract.Call(ic))
|
||||
require.NoError(t, ic.VM.Run())
|
||||
require.Equal(t, 2, ic.VM.Estack().Len())
|
||||
require.Equal(t, big.NewInt(8), ic.VM.Estack().Pop().Value())
|
||||
require.Equal(t, big.NewInt(42), ic.VM.Estack().Pop().Value())
|
||||
})
|
||||
}
|
||||
|
||||
func TestContractGetCallFlags(t *testing.T) {
|
||||
v, ic, _ := createVM(t)
|
||||
|
||||
v.LoadScriptWithHash([]byte{byte(opcode.RET)}, util.Uint160{1, 2, 3}, callflag.All)
|
||||
require.NoError(t, contractGetCallFlags(ic))
|
||||
require.Equal(t, int64(callflag.All), v.Estack().Pop().Value().(*big.Int).Int64())
|
||||
}
|
||||
|
||||
func TestRuntimeCheckWitness(t *testing.T) {
|
||||
_, ic, bc := createVM(t)
|
||||
|
||||
script := []byte{byte(opcode.RET)}
|
||||
scriptHash := hash.Hash160(script)
|
||||
check := func(t *testing.T, ic *interop.Context, arg interface{}, shouldFail bool, expected ...bool) {
|
||||
ic.VM.Estack().PushVal(arg)
|
||||
err := runtime.CheckWitness(ic)
|
||||
if shouldFail {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, expected)
|
||||
actual, ok := ic.VM.Estack().Pop().Value().(bool)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, expected[0], actual)
|
||||
}
|
||||
}
|
||||
t.Run("error", func(t *testing.T) {
|
||||
t.Run("not a hash or key", func(t *testing.T) {
|
||||
check(t, ic, []byte{1, 2, 3}, true)
|
||||
})
|
||||
t.Run("script container is not a transaction", func(t *testing.T) {
|
||||
loadScriptWithHashAndFlags(ic, script, scriptHash, callflag.ReadStates)
|
||||
check(t, ic, random.Uint160().BytesBE(), true)
|
||||
})
|
||||
t.Run("check scope", func(t *testing.T) {
|
||||
t.Run("CustomGroups, missing ReadStates flag", func(t *testing.T) {
|
||||
hash := random.Uint160()
|
||||
tx := &transaction.Transaction{
|
||||
Signers: []transaction.Signer{
|
||||
{
|
||||
Account: hash,
|
||||
Scopes: transaction.CustomGroups,
|
||||
AllowedGroups: []*keys.PublicKey{},
|
||||
},
|
||||
},
|
||||
}
|
||||
ic.Tx = tx
|
||||
callingScriptHash := scriptHash
|
||||
loadScriptWithHashAndFlags(ic, script, callingScriptHash, callflag.All)
|
||||
ic.VM.LoadScriptWithHash([]byte{0x1}, random.Uint160(), callflag.AllowCall)
|
||||
check(t, ic, hash.BytesBE(), true)
|
||||
})
|
||||
t.Run("Rules, missing ReadStates flag", func(t *testing.T) {
|
||||
hash := random.Uint160()
|
||||
pk, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
tx := &transaction.Transaction{
|
||||
Signers: []transaction.Signer{
|
||||
{
|
||||
Account: hash,
|
||||
Scopes: transaction.Rules,
|
||||
Rules: []transaction.WitnessRule{{
|
||||
Action: transaction.WitnessAllow,
|
||||
Condition: (*transaction.ConditionGroup)(pk.PublicKey()),
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
ic.Tx = tx
|
||||
callingScriptHash := scriptHash
|
||||
loadScriptWithHashAndFlags(ic, script, callingScriptHash, callflag.All)
|
||||
ic.VM.LoadScriptWithHash([]byte{0x1}, random.Uint160(), callflag.AllowCall)
|
||||
check(t, ic, hash.BytesBE(), true)
|
||||
})
|
||||
})
|
||||
})
|
||||
t.Run("positive", func(t *testing.T) {
|
||||
t.Run("calling scripthash", func(t *testing.T) {
|
||||
t.Run("hashed witness", func(t *testing.T) {
|
||||
callingScriptHash := scriptHash
|
||||
loadScriptWithHashAndFlags(ic, script, callingScriptHash, callflag.All)
|
||||
ic.VM.LoadScriptWithHash([]byte{0x1}, random.Uint160(), callflag.All)
|
||||
check(t, ic, callingScriptHash.BytesBE(), false, true)
|
||||
})
|
||||
t.Run("keyed witness", func(t *testing.T) {
|
||||
pk, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
callingScriptHash := pk.PublicKey().GetScriptHash()
|
||||
loadScriptWithHashAndFlags(ic, script, callingScriptHash, callflag.All)
|
||||
ic.VM.LoadScriptWithHash([]byte{0x1}, random.Uint160(), callflag.All)
|
||||
check(t, ic, pk.PublicKey().Bytes(), false, true)
|
||||
})
|
||||
})
|
||||
t.Run("check scope", func(t *testing.T) {
|
||||
t.Run("Global", func(t *testing.T) {
|
||||
hash := random.Uint160()
|
||||
tx := &transaction.Transaction{
|
||||
Signers: []transaction.Signer{
|
||||
{
|
||||
Account: hash,
|
||||
Scopes: transaction.Global,
|
||||
},
|
||||
},
|
||||
}
|
||||
loadScriptWithHashAndFlags(ic, script, scriptHash, callflag.ReadStates)
|
||||
ic.Tx = tx
|
||||
check(t, ic, hash.BytesBE(), false, true)
|
||||
})
|
||||
t.Run("CalledByEntry", func(t *testing.T) {
|
||||
hash := random.Uint160()
|
||||
tx := &transaction.Transaction{
|
||||
Signers: []transaction.Signer{
|
||||
{
|
||||
Account: hash,
|
||||
Scopes: transaction.CalledByEntry,
|
||||
},
|
||||
},
|
||||
}
|
||||
loadScriptWithHashAndFlags(ic, script, scriptHash, callflag.ReadStates)
|
||||
ic.Tx = tx
|
||||
check(t, ic, hash.BytesBE(), false, true)
|
||||
})
|
||||
t.Run("CustomContracts", func(t *testing.T) {
|
||||
hash := random.Uint160()
|
||||
tx := &transaction.Transaction{
|
||||
Signers: []transaction.Signer{
|
||||
{
|
||||
Account: hash,
|
||||
Scopes: transaction.CustomContracts,
|
||||
AllowedContracts: []util.Uint160{scriptHash},
|
||||
},
|
||||
},
|
||||
}
|
||||
loadScriptWithHashAndFlags(ic, script, scriptHash, callflag.ReadStates)
|
||||
ic.Tx = tx
|
||||
check(t, ic, hash.BytesBE(), false, true)
|
||||
})
|
||||
t.Run("CustomGroups", func(t *testing.T) {
|
||||
t.Run("unknown scripthash", func(t *testing.T) {
|
||||
hash := random.Uint160()
|
||||
tx := &transaction.Transaction{
|
||||
Signers: []transaction.Signer{
|
||||
{
|
||||
Account: hash,
|
||||
Scopes: transaction.CustomGroups,
|
||||
AllowedGroups: []*keys.PublicKey{},
|
||||
},
|
||||
},
|
||||
}
|
||||
loadScriptWithHashAndFlags(ic, script, scriptHash, callflag.ReadStates)
|
||||
ic.Tx = tx
|
||||
check(t, ic, hash.BytesBE(), false, false)
|
||||
})
|
||||
t.Run("positive", func(t *testing.T) {
|
||||
targetHash := random.Uint160()
|
||||
pk, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
tx := &transaction.Transaction{
|
||||
Signers: []transaction.Signer{
|
||||
{
|
||||
Account: targetHash,
|
||||
Scopes: transaction.CustomGroups,
|
||||
AllowedGroups: []*keys.PublicKey{pk.PublicKey()},
|
||||
},
|
||||
},
|
||||
}
|
||||
contractScript := []byte{byte(opcode.PUSH1), byte(opcode.RET)}
|
||||
contractScriptHash := hash.Hash160(contractScript)
|
||||
ne, err := nef.NewFile(contractScript)
|
||||
require.NoError(t, err)
|
||||
contractState := &state.Contract{
|
||||
ContractBase: state.ContractBase{
|
||||
ID: 15,
|
||||
Hash: contractScriptHash,
|
||||
NEF: *ne,
|
||||
Manifest: manifest.Manifest{
|
||||
Groups: []manifest.Group{{PublicKey: pk.PublicKey(), Signature: make([]byte, keys.SignatureLen)}},
|
||||
},
|
||||
},
|
||||
}
|
||||
require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, contractState))
|
||||
loadScriptWithHashAndFlags(ic, contractScript, contractScriptHash, callflag.All)
|
||||
ic.Tx = tx
|
||||
check(t, ic, targetHash.BytesBE(), false, true)
|
||||
})
|
||||
})
|
||||
t.Run("Rules", func(t *testing.T) {
|
||||
t.Run("no match", func(t *testing.T) {
|
||||
hash := random.Uint160()
|
||||
tx := &transaction.Transaction{
|
||||
Signers: []transaction.Signer{
|
||||
{
|
||||
Account: hash,
|
||||
Scopes: transaction.Rules,
|
||||
Rules: []transaction.WitnessRule{{
|
||||
Action: transaction.WitnessAllow,
|
||||
Condition: (*transaction.ConditionScriptHash)(&hash),
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
loadScriptWithHashAndFlags(ic, script, scriptHash, callflag.ReadStates)
|
||||
ic.Tx = tx
|
||||
check(t, ic, hash.BytesBE(), false, false)
|
||||
})
|
||||
t.Run("allow", func(t *testing.T) {
|
||||
hash := random.Uint160()
|
||||
var cond = true
|
||||
tx := &transaction.Transaction{
|
||||
Signers: []transaction.Signer{
|
||||
{
|
||||
Account: hash,
|
||||
Scopes: transaction.Rules,
|
||||
Rules: []transaction.WitnessRule{{
|
||||
Action: transaction.WitnessAllow,
|
||||
Condition: (*transaction.ConditionBoolean)(&cond),
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
loadScriptWithHashAndFlags(ic, script, scriptHash, callflag.ReadStates)
|
||||
ic.Tx = tx
|
||||
check(t, ic, hash.BytesBE(), false, true)
|
||||
})
|
||||
t.Run("deny", func(t *testing.T) {
|
||||
hash := random.Uint160()
|
||||
var cond = true
|
||||
tx := &transaction.Transaction{
|
||||
Signers: []transaction.Signer{
|
||||
{
|
||||
Account: hash,
|
||||
Scopes: transaction.Rules,
|
||||
Rules: []transaction.WitnessRule{{
|
||||
Action: transaction.WitnessDeny,
|
||||
Condition: (*transaction.ConditionBoolean)(&cond),
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
loadScriptWithHashAndFlags(ic, script, scriptHash, callflag.ReadStates)
|
||||
ic.Tx = tx
|
||||
check(t, ic, hash.BytesBE(), false, false)
|
||||
})
|
||||
})
|
||||
t.Run("bad scope", func(t *testing.T) {
|
||||
hash := random.Uint160()
|
||||
tx := &transaction.Transaction{
|
||||
Signers: []transaction.Signer{
|
||||
{
|
||||
Account: hash,
|
||||
Scopes: transaction.None,
|
||||
},
|
||||
},
|
||||
}
|
||||
loadScriptWithHashAndFlags(ic, script, scriptHash, callflag.ReadStates)
|
||||
ic.Tx = tx
|
||||
check(t, ic, hash.BytesBE(), false, false)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// TestNativeGetMethod is needed to ensure that methods list has the same sorting
|
||||
// rule as we expect inside the `ContractMD.GetMethod`.
|
||||
func TestNativeGetMethod(t *testing.T) {
|
||||
cfg := config.ProtocolConfiguration{P2PSigExtensions: true}
|
||||
cs := native.NewContracts(cfg)
|
||||
for _, c := range cs.Contracts {
|
||||
t.Run(c.Metadata().Name, func(t *testing.T) {
|
||||
for _, m := range c.Metadata().Methods {
|
||||
_, ok := c.Metadata().GetMethod(m.MD.Name, len(m.MD.Parameters))
|
||||
require.True(t, ok)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/iterator"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/runtime"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
|
@ -33,9 +34,9 @@ var systemInterops = []interop.Function{
|
|||
{Name: interopnames.SystemContractCall, Func: contract.Call, Price: 1 << 15,
|
||||
RequiredFlags: callflag.ReadStates | callflag.AllowCall, ParamCount: 4},
|
||||
{Name: interopnames.SystemContractCallNative, Func: native.Call, Price: 0, ParamCount: 1},
|
||||
{Name: interopnames.SystemContractCreateMultisigAccount, Func: contractCreateMultisigAccount, Price: 0, ParamCount: 2},
|
||||
{Name: interopnames.SystemContractCreateStandardAccount, Func: contractCreateStandardAccount, Price: 0, ParamCount: 1},
|
||||
{Name: interopnames.SystemContractGetCallFlags, Func: contractGetCallFlags, Price: 1 << 10},
|
||||
{Name: interopnames.SystemContractCreateMultisigAccount, Func: contract.CreateMultisigAccount, Price: 0, ParamCount: 2},
|
||||
{Name: interopnames.SystemContractCreateStandardAccount, Func: contract.CreateStandardAccount, Price: 0, ParamCount: 1},
|
||||
{Name: interopnames.SystemContractGetCallFlags, Func: contract.GetCallFlags, Price: 1 << 10},
|
||||
{Name: interopnames.SystemContractNativeOnPersist, Func: native.OnPersist, Price: 0, RequiredFlags: callflag.States},
|
||||
{Name: interopnames.SystemContractNativePostPersist, Func: native.PostPersist, Price: 0, RequiredFlags: callflag.States},
|
||||
{Name: interopnames.SystemCryptoCheckMultisig, Func: crypto.ECDSASecp256r1CheckMultisig, Price: 0, ParamCount: 2},
|
||||
|
@ -54,7 +55,7 @@ var systemInterops = []interop.Function{
|
|||
{Name: interopnames.SystemRuntimeGetNetwork, Func: runtime.GetNetwork, Price: 1 << 3},
|
||||
{Name: interopnames.SystemRuntimeGetNotifications, Func: runtime.GetNotifications, Price: 1 << 12, ParamCount: 1},
|
||||
{Name: interopnames.SystemRuntimeGetRandom, Func: runtime.GetRandom, Price: 0},
|
||||
{Name: interopnames.SystemRuntimeGetScriptContainer, Func: engineGetScriptContainer, Price: 1 << 3},
|
||||
{Name: interopnames.SystemRuntimeGetScriptContainer, Func: runtime.GetScriptContainer, Price: 1 << 3},
|
||||
{Name: interopnames.SystemRuntimeGetTime, Func: runtime.GetTime, Price: 1 << 3, RequiredFlags: callflag.ReadStates},
|
||||
{Name: interopnames.SystemRuntimeGetTrigger, Func: runtime.GetTrigger, Price: 1 << 3},
|
||||
{Name: interopnames.SystemRuntimeLog, Func: runtime.Log, Price: 1 << 15, RequiredFlags: callflag.AllowNotify,
|
||||
|
@ -62,19 +63,19 @@ var systemInterops = []interop.Function{
|
|||
{Name: interopnames.SystemRuntimeNotify, Func: runtime.Notify, Price: 1 << 15, RequiredFlags: callflag.AllowNotify,
|
||||
ParamCount: 2},
|
||||
{Name: interopnames.SystemRuntimePlatform, Func: runtime.Platform, Price: 1 << 3},
|
||||
{Name: interopnames.SystemStorageDelete, Func: storageDelete, Price: 1 << 15,
|
||||
{Name: interopnames.SystemStorageDelete, Func: storage.Delete, Price: 1 << 15,
|
||||
RequiredFlags: callflag.WriteStates, ParamCount: 2},
|
||||
{Name: interopnames.SystemStorageFind, Func: storageFind, Price: 1 << 15, RequiredFlags: callflag.ReadStates,
|
||||
{Name: interopnames.SystemStorageFind, Func: storage.Find, Price: 1 << 15, RequiredFlags: callflag.ReadStates,
|
||||
ParamCount: 3},
|
||||
{Name: interopnames.SystemStorageGet, Func: storageGet, Price: 1 << 15, RequiredFlags: callflag.ReadStates,
|
||||
{Name: interopnames.SystemStorageGet, Func: storage.Get, Price: 1 << 15, RequiredFlags: callflag.ReadStates,
|
||||
ParamCount: 2},
|
||||
{Name: interopnames.SystemStorageGetContext, Func: storageGetContext, Price: 1 << 4,
|
||||
{Name: interopnames.SystemStorageGetContext, Func: storage.GetContext, Price: 1 << 4,
|
||||
RequiredFlags: callflag.ReadStates},
|
||||
{Name: interopnames.SystemStorageGetReadOnlyContext, Func: storageGetReadOnlyContext, Price: 1 << 4,
|
||||
{Name: interopnames.SystemStorageGetReadOnlyContext, Func: storage.GetReadOnlyContext, Price: 1 << 4,
|
||||
RequiredFlags: callflag.ReadStates},
|
||||
{Name: interopnames.SystemStoragePut, Func: storagePut, Price: 1 << 15, RequiredFlags: callflag.WriteStates,
|
||||
{Name: interopnames.SystemStoragePut, Func: storage.Put, Price: 1 << 15, RequiredFlags: callflag.WriteStates,
|
||||
ParamCount: 3},
|
||||
{Name: interopnames.SystemStorageAsReadOnly, Func: storageContextAsReadOnly, Price: 1 << 4,
|
||||
{Name: interopnames.SystemStorageAsReadOnly, Func: storage.ContextAsReadOnly, Price: 1 << 4,
|
||||
RequiredFlags: callflag.ReadStates, ParamCount: 1},
|
||||
}
|
||||
|
||||
|
|
23
pkg/core/native/contract_test.go
Normal file
23
pkg/core/native/contract_test.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
package native
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestNativeGetMethod is needed to ensure that methods list has the same sorting
|
||||
// rule as we expect inside the `ContractMD.GetMethod`.
|
||||
func TestNativeGetMethod(t *testing.T) {
|
||||
cfg := config.ProtocolConfiguration{P2PSigExtensions: true}
|
||||
cs := NewContracts(cfg)
|
||||
for _, c := range cs.Contracts {
|
||||
t.Run(c.Metadata().Name, func(t *testing.T) {
|
||||
for _, m := range c.Metadata().Methods {
|
||||
_, ok := c.Metadata().GetMethod(m.MD.Name, len(m.MD.Parameters))
|
||||
require.True(t, ok)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
package core_test
|
||||
package native_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
@ -22,6 +23,8 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var pathToInternalContracts = filepath.Join("..", "..", "..", "internal", "contracts")
|
||||
|
||||
func TestNativeContract_Invoke(t *testing.T) {
|
||||
const (
|
||||
transferCPUFee = 1 << 17
|
|
@ -5,7 +5,6 @@ import (
|
|||
"math"
|
||||
"math/big"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||
|
@ -117,7 +116,7 @@ func (l *Ledger) getBlock(ic *interop.Context, params []stackitem.Item) stackite
|
|||
if err != nil || !isTraceableBlock(ic, block.Index) {
|
||||
return stackitem.Null{}
|
||||
}
|
||||
return BlockToStackItem(block)
|
||||
return block.ToStackItem()
|
||||
}
|
||||
|
||||
// getTransaction returns transaction to the SC.
|
||||
|
@ -126,7 +125,7 @@ func (l *Ledger) getTransaction(ic *interop.Context, params []stackitem.Item) st
|
|||
if err != nil || !isTraceableBlock(ic, h) {
|
||||
return stackitem.Null{}
|
||||
}
|
||||
return TransactionToStackItem(tx)
|
||||
return tx.ToStackItem()
|
||||
}
|
||||
|
||||
// getTransactionHeight returns transaction height to the SC.
|
||||
|
@ -150,7 +149,7 @@ func (l *Ledger) getTransactionFromBlock(ic *interop.Context, params []stackitem
|
|||
if index >= uint32(len(block.Transactions)) {
|
||||
panic("wrong transaction index")
|
||||
}
|
||||
return TransactionToStackItem(block.Transactions[index])
|
||||
return block.Transactions[index].ToStackItem()
|
||||
}
|
||||
|
||||
// getTransactionSigners returns transaction signers to the SC.
|
||||
|
@ -228,35 +227,6 @@ func getTransactionAndHeight(d *dao.Simple, item stackitem.Item) (*transaction.T
|
|||
return d.GetTransaction(hash)
|
||||
}
|
||||
|
||||
// BlockToStackItem converts block.Block to stackitem.Item.
|
||||
func BlockToStackItem(b *block.Block) stackitem.Item {
|
||||
return stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.NewByteArray(b.Hash().BytesBE()),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(b.Version))),
|
||||
stackitem.NewByteArray(b.PrevHash.BytesBE()),
|
||||
stackitem.NewByteArray(b.MerkleRoot.BytesBE()),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(b.Timestamp))),
|
||||
stackitem.NewBigInteger(new(big.Int).SetUint64(b.Nonce)),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(b.Index))),
|
||||
stackitem.NewByteArray(b.NextConsensus.BytesBE()),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(len(b.Transactions)))),
|
||||
})
|
||||
}
|
||||
|
||||
// TransactionToStackItem converts transaction.Transaction to stackitem.Item.
|
||||
func TransactionToStackItem(t *transaction.Transaction) stackitem.Item {
|
||||
return stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.NewByteArray(t.Hash().BytesBE()),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(t.Version))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(t.Nonce))),
|
||||
stackitem.NewByteArray(t.Sender().BytesBE()),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(t.SystemFee))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(t.NetworkFee))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(t.ValidUntilBlock))),
|
||||
stackitem.NewByteArray(t.Script),
|
||||
})
|
||||
}
|
||||
|
||||
// SignersToStackItem converts transaction.Signers to stackitem.Item.
|
||||
func SignersToStackItem(signers []transaction.Signer) stackitem.Item {
|
||||
res := make([]stackitem.Item, len(signers))
|
||||
|
|
|
@ -272,8 +272,8 @@ func (m *Management) deployWithData(ic *interop.Context, args []stackitem.Item)
|
|||
return contractToStack(newcontract)
|
||||
}
|
||||
|
||||
func (m *Management) markUpdated(d *dao.Simple, hash util.Uint160, cs *state.Contract) {
|
||||
cache := d.GetRWCache(m.ID).(*ManagementCache)
|
||||
func markUpdated(d *dao.Simple, hash util.Uint160, cs *state.Contract) {
|
||||
cache := d.GetRWCache(ManagementContractID).(*ManagementCache)
|
||||
delete(cache.nep11, hash)
|
||||
delete(cache.nep17, hash)
|
||||
if cs == nil {
|
||||
|
@ -314,7 +314,7 @@ func (m *Management) Deploy(d *dao.Simple, sender util.Uint160, neff *nef.File,
|
|||
Manifest: *manif,
|
||||
},
|
||||
}
|
||||
err = m.PutContractState(d, newcontract)
|
||||
err = PutContractState(d, newcontract)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -378,7 +378,7 @@ func (m *Management) Update(d *dao.Simple, hash util.Uint160, neff *nef.File, ma
|
|||
return nil, err
|
||||
}
|
||||
contract.UpdateCounter++
|
||||
err = m.PutContractState(d, &contract)
|
||||
err = PutContractState(d, &contract)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -412,7 +412,7 @@ func (m *Management) Destroy(d *dao.Simple, hash util.Uint160) error {
|
|||
return true
|
||||
})
|
||||
m.Policy.blockAccountInternal(d, hash)
|
||||
m.markUpdated(d, hash, nil)
|
||||
markUpdated(d, hash, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -489,7 +489,7 @@ func (m *Management) OnPersist(ic *interop.Context) error {
|
|||
if err := native.Initialize(ic); err != nil {
|
||||
return fmt.Errorf("initializing %s native contract: %w", md.Name, err)
|
||||
}
|
||||
err := m.putContractState(ic.DAO, cs, false) // Perform cache update manually.
|
||||
err := putContractState(ic.DAO, cs, false) // Perform cache update manually.
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -573,18 +573,18 @@ func (m *Management) Initialize(ic *interop.Context) error {
|
|||
}
|
||||
|
||||
// PutContractState saves given contract state into given DAO.
|
||||
func (m *Management) PutContractState(d *dao.Simple, cs *state.Contract) error {
|
||||
return m.putContractState(d, cs, true)
|
||||
func PutContractState(d *dao.Simple, cs *state.Contract) error {
|
||||
return putContractState(d, cs, true)
|
||||
}
|
||||
|
||||
// putContractState is an internal PutContractState representation.
|
||||
func (m *Management) putContractState(d *dao.Simple, cs *state.Contract, updateCache bool) error {
|
||||
func putContractState(d *dao.Simple, cs *state.Contract, updateCache bool) error {
|
||||
key := MakeContractKey(cs.Hash)
|
||||
if err := putConvertibleToDAO(m.ID, d, key, cs); err != nil {
|
||||
if err := putConvertibleToDAO(ManagementContractID, d, key, cs); err != nil {
|
||||
return err
|
||||
}
|
||||
if updateCache {
|
||||
m.markUpdated(d, cs.Hash, cs)
|
||||
markUpdated(d, cs.Hash, cs)
|
||||
}
|
||||
if cs.UpdateCounter != 0 { // Update.
|
||||
return nil
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
package core_test
|
||||
package native_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/internal/basicchain"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||
|
@ -22,10 +23,10 @@ func TestManagement_GetNEP17Contracts(t *testing.T) {
|
|||
|
||||
t.Run("basic chain", func(t *testing.T) {
|
||||
bc, validators, committee := chain.NewMultiWithCustomConfig(t, func(c *config.ProtocolConfiguration) {
|
||||
c.P2PSigExtensions = true // `initBasicChain` requires Notary enabled
|
||||
c.P2PSigExtensions = true // `basicchain.Init` requires Notary enabled
|
||||
})
|
||||
e := neotest.NewExecutor(t, bc, validators, committee)
|
||||
initBasicChain(t, e)
|
||||
basicchain.Init(t, "../../../", e)
|
||||
|
||||
require.ElementsMatch(t, []util.Uint160{e.NativeHash(t, nativenames.Neo),
|
||||
e.NativeHash(t, nativenames.Gas), e.ContractHash(t, 1)}, bc.GetNEP17Contracts())
|
|
@ -279,7 +279,7 @@ func TestNEO_RecursiveGASMint(t *testing.T) {
|
|||
e := neoCommitteeInvoker.Executor
|
||||
gasValidatorInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Gas))
|
||||
|
||||
c := neotest.CompileFile(t, e.Validator.ScriptHash(), "../../../rpc/server/testdata/test_contract.go", "../../../rpc/server/testdata/test_contract.yml")
|
||||
c := neotest.CompileFile(t, e.Validator.ScriptHash(), "../../../../internal/basicchain/testdata/test_contract.go", "../../../../internal/basicchain/testdata/test_contract.yml")
|
||||
e.DeployContract(t, c, nil)
|
||||
|
||||
gasValidatorInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(), c.Hash, int64(2_0000_0000), nil)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package core_test
|
||||
package native_test
|
||||
|
||||
import (
|
||||
"fmt"
|
|
@ -15,6 +15,9 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Technically this test belongs to the native package, but it's so deeply tied to
|
||||
// the core internals that it needs to be rewritten to be moved. So let it be
|
||||
// there just to remind us about the imperfect world we live in.
|
||||
func TestDesignate_DesignateAsRole(t *testing.T) {
|
||||
bc := newTestChain(t)
|
||||
|
||||
|
|
|
@ -1,125 +0,0 @@
|
|||
package core_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func BenchmarkNEO_GetGASPerVote(t *testing.B) {
|
||||
var stores = map[string]func(testing.TB) storage.Store{
|
||||
"MemPS": func(t testing.TB) storage.Store {
|
||||
return storage.NewMemoryStore()
|
||||
},
|
||||
"BoltPS": newBoltStoreForTesting,
|
||||
"LevelPS": newLevelDBForTesting,
|
||||
}
|
||||
for psName, newPS := range stores {
|
||||
for nRewardRecords := 10; nRewardRecords <= 1000; nRewardRecords *= 10 {
|
||||
for rewardDistance := 1; rewardDistance <= 1000; rewardDistance *= 10 {
|
||||
t.Run(fmt.Sprintf("%s_%dRewardRecords_%dRewardDistance", psName, nRewardRecords, rewardDistance), func(t *testing.B) {
|
||||
ps := newPS(t)
|
||||
t.Cleanup(func() { ps.Close() })
|
||||
benchmarkGasPerVote(t, ps, nRewardRecords, rewardDistance)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newLevelDBForTesting(t testing.TB) storage.Store {
|
||||
dbPath := t.TempDir()
|
||||
dbOptions := storage.LevelDBOptions{
|
||||
DataDirectoryPath: dbPath,
|
||||
}
|
||||
newLevelStore, err := storage.NewLevelDBStore(dbOptions)
|
||||
require.Nil(t, err, "NewLevelDBStore error")
|
||||
return newLevelStore
|
||||
}
|
||||
|
||||
func newBoltStoreForTesting(t testing.TB) storage.Store {
|
||||
d := t.TempDir()
|
||||
dbPath := filepath.Join(d, "test_bolt_db")
|
||||
boltDBStore, err := storage.NewBoltDBStore(storage.BoltDBOptions{FilePath: dbPath})
|
||||
require.NoError(t, err)
|
||||
return boltDBStore
|
||||
}
|
||||
|
||||
func benchmarkGasPerVote(t *testing.B, ps storage.Store, nRewardRecords int, rewardDistance int) {
|
||||
bc, validators, committee := chain.NewMultiWithCustomConfigAndStore(t, nil, ps, true)
|
||||
cfg := bc.GetConfig()
|
||||
|
||||
e := neotest.NewExecutor(t, bc, validators, committee)
|
||||
neoHash := e.NativeHash(t, nativenames.Neo)
|
||||
gasHash := e.NativeHash(t, nativenames.Gas)
|
||||
neoSuperInvoker := e.NewInvoker(neoHash, validators, committee)
|
||||
neoValidatorsInvoker := e.ValidatorInvoker(neoHash)
|
||||
gasValidatorsInvoker := e.ValidatorInvoker(gasHash)
|
||||
|
||||
// Vote for new committee.
|
||||
sz := len(cfg.StandbyCommittee)
|
||||
voters := make([]*wallet.Account, sz)
|
||||
candidates := make(keys.PublicKeys, sz)
|
||||
txs := make([]*transaction.Transaction, 0, len(voters)*3)
|
||||
for i := 0; i < sz; i++ {
|
||||
priv, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
candidates[i] = priv.PublicKey()
|
||||
voters[i], err = wallet.NewAccount()
|
||||
require.NoError(t, err)
|
||||
registerTx := neoSuperInvoker.PrepareInvoke(t, "registerCandidate", candidates[i].Bytes())
|
||||
txs = append(txs, registerTx)
|
||||
|
||||
to := voters[i].Contract.ScriptHash()
|
||||
transferNeoTx := neoValidatorsInvoker.PrepareInvoke(t, "transfer", e.Validator.ScriptHash(), to, big.NewInt(int64(sz-i)*1000000).Int64(), nil)
|
||||
txs = append(txs, transferNeoTx)
|
||||
|
||||
transferGasTx := gasValidatorsInvoker.PrepareInvoke(t, "transfer", e.Validator.ScriptHash(), to, int64(1_000_000_000), nil)
|
||||
txs = append(txs, transferGasTx)
|
||||
}
|
||||
e.AddNewBlock(t, txs...)
|
||||
for _, tx := range txs {
|
||||
e.CheckHalt(t, tx.Hash())
|
||||
}
|
||||
voteTxs := make([]*transaction.Transaction, 0, sz)
|
||||
for i := 0; i < sz; i++ {
|
||||
priv := voters[i].PrivateKey()
|
||||
h := priv.GetScriptHash()
|
||||
voteTx := e.NewTx(t, []neotest.Signer{neotest.NewSingleSigner(voters[i])}, neoHash, "vote", h, candidates[i].Bytes())
|
||||
voteTxs = append(voteTxs, voteTx)
|
||||
}
|
||||
e.AddNewBlock(t, voteTxs...)
|
||||
for _, tx := range voteTxs {
|
||||
e.CheckHalt(t, tx.Hash())
|
||||
}
|
||||
|
||||
// Collect set of nRewardRecords reward records for each voter.
|
||||
e.GenerateNewBlocks(t, len(cfg.StandbyCommittee))
|
||||
|
||||
// Transfer some more NEO to first voter to update his balance height.
|
||||
to := voters[0].Contract.ScriptHash()
|
||||
neoValidatorsInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(), to, int64(1), nil)
|
||||
|
||||
// Advance chain one more time to avoid same start/end rewarding bounds.
|
||||
e.GenerateNewBlocks(t, rewardDistance)
|
||||
end := bc.BlockHeight()
|
||||
|
||||
t.ResetTimer()
|
||||
t.ReportAllocs()
|
||||
t.StartTimer()
|
||||
for i := 0; i < t.N; i++ {
|
||||
_, err := bc.CalculateClaimable(to, end)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
t.StopTimer()
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
package core_test
|
||||
package statesync_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/internal/basicchain"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/mpt"
|
||||
|
@ -291,13 +292,13 @@ func TestStateSyncModule_RestoreBasicChain(t *testing.T) {
|
|||
c.P2PStateExchangeExtensions = true
|
||||
c.StateSyncInterval = stateSyncInterval
|
||||
c.MaxTraceableBlocks = maxTraceable
|
||||
c.P2PSigExtensions = true // `initBasicChain` assumes Notary is enabled.
|
||||
c.P2PSigExtensions = true // `basicchain.Init` assumes Notary is enabled.
|
||||
}
|
||||
bcSpoutStore := storage.NewMemoryStore()
|
||||
bcSpout, validators, committee := chain.NewMultiWithCustomConfigAndStore(t, spoutCfg, bcSpoutStore, false)
|
||||
go bcSpout.Run() // Will close it manually at the end.
|
||||
e := neotest.NewExecutor(t, bcSpout, validators, committee)
|
||||
initBasicChain(t, e)
|
||||
basicchain.Init(t, "../../../", e)
|
||||
|
||||
// make spout chain higher that latest state sync point (add several blocks up to stateSyncPoint+2)
|
||||
e.AddNewBlock(t)
|
|
@ -6,12 +6,14 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -451,3 +453,17 @@ func (t *Transaction) HasSigner(hash util.Uint160) bool {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ToStackItem converts Transaction to stackitem.Item.
|
||||
func (t *Transaction) ToStackItem() stackitem.Item {
|
||||
return stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.NewByteArray(t.Hash().BytesBE()),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(t.Version))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(t.Nonce))),
|
||||
stackitem.NewByteArray(t.Sender().BytesBE()),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(t.SystemFee))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(t.NetworkFee))),
|
||||
stackitem.NewBigInteger(big.NewInt(int64(t.ValidUntilBlock))),
|
||||
stackitem.NewByteArray(t.Script),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -13,8 +13,8 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
)
|
||||
|
||||
// createGenesisBlock creates a genesis block based on the given configuration.
|
||||
func createGenesisBlock(cfg config.ProtocolConfiguration) (*block.Block, error) {
|
||||
// CreateGenesisBlock creates a genesis block based on the given configuration.
|
||||
func CreateGenesisBlock(cfg config.ProtocolConfiguration) (*block.Block, error) {
|
||||
validators, err := validatorsFromConfig(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -14,7 +14,7 @@ func TestGenesisBlockMainNet(t *testing.T) {
|
|||
cfg, err := config.Load("../../config", netmode.MainNet)
|
||||
require.NoError(t, err)
|
||||
|
||||
block, err := createGenesisBlock(cfg.ProtocolConfiguration)
|
||||
block, err := CreateGenesisBlock(cfg.ProtocolConfiguration)
|
||||
require.NoError(t, err)
|
||||
|
||||
expect := "1f4d1defa46faa5e7b9b8d3f79a06bec777d7c26c4aa5f6f5899a291daa87c15"
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
package core_test
|
||||
package notary_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -40,7 +38,7 @@ func getTestNotary(t *testing.T, bc *core.Blockchain, walletPath, pass string, o
|
|||
mainCfg := config.P2PNotary{
|
||||
Enabled: true,
|
||||
UnlockWallet: config.Wallet{
|
||||
Path: filepath.Join(notaryModulePath, walletPath),
|
||||
Path: walletPath,
|
||||
Password: pass,
|
||||
},
|
||||
}
|
||||
|
@ -53,7 +51,7 @@ func getTestNotary(t *testing.T, bc *core.Blockchain, walletPath, pass string, o
|
|||
ntr, err := notary.NewNotary(cfg, netmode.UnitTestNet, mp, onTx)
|
||||
require.NoError(t, err)
|
||||
|
||||
w, err := wallet.NewWalletFromFile(path.Join(notaryModulePath, walletPath))
|
||||
w, err := wallet.NewWalletFromFile(walletPath)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, w.Accounts[0].Decrypt(pass, w.Scrypt))
|
||||
return w.Accounts[0], ntr, mp
|
|
@ -1,4 +1,4 @@
|
|||
package core_test
|
||||
package oracle_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -8,7 +8,6 @@ import (
|
|||
"fmt"
|
||||
gio "io"
|
||||
"net/http"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -38,7 +37,7 @@ import (
|
|||
"go.uber.org/zap/zaptest"
|
||||
)
|
||||
|
||||
var oracleModulePath = filepath.Join("..", "services", "oracle")
|
||||
var pathToInternalContracts = filepath.Join("..", "..", "..", "internal", "contracts")
|
||||
|
||||
func putOracleRequest(t *testing.T, oracleValidatorInvoker *neotest.ContractInvoker,
|
||||
url string, filter *string, cb string, userData []byte, gas int64) util.Uint256 {
|
||||
|
@ -57,7 +56,7 @@ func getOracleConfig(t *testing.T, bc *core.Blockchain, w, pass string, returnOr
|
|||
RefreshInterval: time.Second,
|
||||
AllowedContentTypes: []string{"application/json"},
|
||||
UnlockWallet: config.Wallet{
|
||||
Path: filepath.Join(oracleModulePath, w),
|
||||
Path: w,
|
||||
Password: pass,
|
||||
},
|
||||
},
|
||||
|
@ -81,7 +80,7 @@ func getTestOracle(t *testing.T, bc *core.Blockchain, walletPath, pass string) (
|
|||
orc, err := oracle.NewOracle(orcCfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
w, err := wallet.NewWalletFromFile(path.Join(oracleModulePath, walletPath))
|
||||
w, err := wallet.NewWalletFromFile(walletPath)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, w.Accounts[0].Decrypt(pass, w.Scrypt))
|
||||
return w.Accounts[0], orc, m, ch
|
|
@ -1,4 +1,4 @@
|
|||
package core_test
|
||||
package stateroot_test
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
|
@ -8,6 +8,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/internal/basicchain"
|
||||
"github.com/nspcc-dev/neo-go/internal/testserdes"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||
|
@ -302,7 +303,7 @@ func TestStateroot_GetLatestStateHeight(t *testing.T) {
|
|||
c.P2PSigExtensions = true
|
||||
})
|
||||
e := neotest.NewExecutor(t, bc, validators, committee)
|
||||
initBasicChain(t, e)
|
||||
basicchain.Init(t, "../../../", e)
|
||||
|
||||
m := bc.GetStateModule()
|
||||
for i := uint32(0); i < bc.BlockHeight(); i++ {
|
Loading…
Reference in a new issue