Merge pull request #2548 from nspcc-dev/shuffle-tests

Shuffle tests and some other code
This commit is contained in:
Roman Khimov 2022-06-09 12:28:03 +03:00 committed by GitHub
commit 9919fd2cc9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 2157 additions and 2037 deletions

1
.gitignore vendored
View file

@ -46,6 +46,7 @@ examples/*/*.json
# Fuzzing testdata.
testdata/
!cli/testdata
!internal/basicchain/testdata
!pkg/compiler/testdata
!pkg/config/testdata
!pkg/consensus/testdata

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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)
})
})
}

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

View 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)
}

View file

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

View file

@ -1,180 +1,173 @@
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")
})
}
func TestSystemContractCreateMultisigAccount(t *testing.T) {
bc, acc := chain.NewSingle(t)
e := neotest.NewExecutor(t, bc, acc, acc)
w := io.NewBufBinWriter()
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))
})
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()
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])
}
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()
// 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("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", 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("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("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("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 TestSystemRuntimeGasLeft(t *testing.T) {
const runtimeGasLeftPrice = 1 << 4
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())
})
bc, acc := chain.NewSingle(t)
e := neotest.NewExecutor(t, bc, acc, acc)
w := io.NewBufBinWriter()
t.Run("CallInitialize", func(t *testing.T) {
t.Run("Directly", runInvalid(stackitem.NewArray([]stackitem.Item{}), "_initialize", h.BytesBE()))
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())
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 TestLoadToken(t *testing.T) {
@ -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
}

View file

@ -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")))

View 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())
}

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

View 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()
}
})
}
})
}
}

View file

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

View file

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

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

View file

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

View file

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

View file

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

View 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)
}
})
}
}

View file

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

View file

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

View file

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

View file

@ -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())

View file

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

View file

@ -1,4 +1,4 @@
package core_test
package native_test
import (
"fmt"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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++ {