neo-go/pkg/core/helper_test.go

838 lines
30 KiB
Go
Raw Normal View History

package core
import (
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"math/big"
"os"
"path"
"path/filepath"
"strings"
"testing"
"time"
"github.com/nspcc-dev/neo-go/internal/testchain"
"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"
2021-01-26 16:37:19 +00:00
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer"
"github.com/nspcc-dev/neo-go/pkg/core/chaindump"
"github.com/nspcc-dev/neo-go/pkg/core/fee"
"github.com/nspcc-dev/neo-go/pkg/core/native"
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
2020-11-19 10:00:46 +00:00
"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/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/rpc/client/nns"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
2020-11-19 10:00:46 +00:00
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/util"
2020-11-19 10:00:46 +00:00
"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"
2020-11-19 10:00:46 +00:00
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
2019-12-30 07:43:05 +00:00
"go.uber.org/zap/zaptest"
)
// multisig address which possess all NEO.
2020-04-21 15:09:34 +00:00
var neoOwner = testchain.MultisigScriptHash()
// examplesPrefix is a prefix of the example smart-contracts.
const examplesPrefix = "../../examples/"
// newTestChain should be called before newBlock invocation to properly setup
// global state.
func newTestChain(t testing.TB) *Blockchain {
return newTestChainWithCustomCfg(t, nil)
}
func newTestChainWithCustomCfg(t testing.TB, f func(*config.Config)) *Blockchain {
return newTestChainWithCustomCfgAndStore(t, nil, f)
}
func newTestChainWithCustomCfgAndStore(t testing.TB, st storage.Store, f func(*config.Config)) *Blockchain {
chain := initTestChain(t, st, f)
go chain.Run()
2021-03-01 11:14:15 +00:00
t.Cleanup(chain.Close)
return chain
}
func newLevelDBForTesting(t testing.TB) storage.Store {
newLevelStore, _ := newLevelDBForTestingWithPath(t, "")
return newLevelStore
}
func newLevelDBForTestingWithPath(t testing.TB, dbPath string) (storage.Store, string) {
if dbPath == "" {
dbPath = t.TempDir()
}
dbOptions := storage.LevelDBOptions{
DataDirectoryPath: dbPath,
}
newLevelStore, err := storage.NewLevelDBStore(dbOptions)
require.Nil(t, err, "NewLevelDBStore error")
return newLevelStore, dbPath
}
func newBoltStoreForTesting(t testing.TB) storage.Store {
boltDBStore, _ := newBoltStoreForTestingWithPath(t, "")
return boltDBStore
}
func newBoltStoreForTestingWithPath(t testing.TB, dbPath string) (storage.Store, string) {
if dbPath == "" {
d := t.TempDir()
dbPath = filepath.Join(d, "test_bolt_db")
}
boltDBStore, err := storage.NewBoltDBStore(storage.BoltDBOptions{FilePath: dbPath})
require.NoError(t, err)
return boltDBStore, dbPath
}
func initTestChain(t testing.TB, st storage.Store, f func(*config.Config)) *Blockchain {
chain, err := initTestChainNoCheck(t, st, f)
require.NoError(t, err)
return chain
}
func initTestChainNoCheck(t testing.TB, st storage.Store, f func(*config.Config)) (*Blockchain, error) {
unitTestNetCfg, err := config.Load("../../config", testchain.Network())
require.NoError(t, err)
if f != nil {
f(&unitTestNetCfg)
}
if st == nil {
st = storage.NewMemoryStore()
}
log := zaptest.NewLogger(t)
if _, ok := t.(*testing.B); ok {
log = zap.NewNop()
}
return NewBlockchain(st, unitTestNetCfg.ProtocolConfiguration, log)
}
func (bc *Blockchain) newBlock(txs ...*transaction.Transaction) *block.Block {
lastBlock, ok := bc.topBlock.Load().(*block.Block)
if !ok {
var err error
lastBlock, err = bc.GetBlock(bc.GetHeaderHash(int(bc.BlockHeight())))
if err != nil {
panic(err)
}
}
if bc.config.StateRootInHeader {
sr, err := bc.GetStateModule().GetStateRoot(bc.BlockHeight())
if err != nil {
panic(err)
}
return newBlockWithState(bc.config, lastBlock.Index+1, lastBlock.Hash(), &sr.Root, txs...)
}
return newBlock(bc.config, lastBlock.Index+1, lastBlock.Hash(), txs...)
}
func newBlock(cfg config.ProtocolConfiguration, index uint32, prev util.Uint256, txs ...*transaction.Transaction) *block.Block {
return newBlockWithState(cfg, index, prev, nil, txs...)
}
2021-01-22 12:12:09 +00:00
func newBlockCustom(cfg config.ProtocolConfiguration, f func(b *block.Block),
txs ...*transaction.Transaction) *block.Block {
validators, _ := validatorsFromConfig(cfg)
valScript, _ := smartcontract.CreateDefaultMultiSigRedeemScript(validators)
witness := transaction.Witness{
VerificationScript: valScript,
}
b := &block.Block{
2021-03-01 13:44:47 +00:00
Header: block.Header{
NextConsensus: witness.ScriptHash(),
Script: witness,
},
Transactions: txs,
}
2021-01-22 12:12:09 +00:00
f(b)
b.RebuildMerkleRoot()
b.Script.InvocationScript = testchain.Sign(b)
return b
}
2021-01-22 12:12:09 +00:00
func newBlockWithState(cfg config.ProtocolConfiguration, index uint32, prev util.Uint256,
prevState *util.Uint256, txs ...*transaction.Transaction) *block.Block {
return newBlockCustom(cfg, func(b *block.Block) {
b.PrevHash = prev
b.Timestamp = uint64(time.Now().UTC().Unix())*1000 + uint64(index)
b.Index = index
if prevState != nil {
b.StateRootEnabled = true
b.PrevStateRoot = *prevState
}
}, txs...)
}
func (bc *Blockchain) genBlocks(n int) ([]*block.Block, error) {
blocks := make([]*block.Block, n)
lastHash := bc.topBlock.Load().(*block.Block).Hash()
lastIndex := bc.topBlock.Load().(*block.Block).Index
for i := 0; i < n; i++ {
blocks[i] = newBlock(bc.config, uint32(i)+lastIndex+1, lastHash)
if err := bc.AddBlock(blocks[i]); err != nil {
return blocks, err
}
lastHash = blocks[i].Hash()
}
return blocks, nil
}
func TestBug1728(t *testing.T) {
src := `package example
import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
func init() { if true { } else { } }
func _deploy(_ interface{}, isUpdate bool) {
runtime.Log("Deploy")
}`
nf, di, err := compiler.CompileWithOptions("foo.go", strings.NewReader(src), nil)
require.NoError(t, err)
m, err := di.ConvertToManifest(&compiler.Options{Name: "TestContract"})
require.NoError(t, err)
rawManifest, err := json.Marshal(m)
require.NoError(t, err)
rawNef, err := nf.Bytes()
require.NoError(t, err)
bc := newTestChain(t)
aer, err := invokeContractMethod(bc, 10000000000,
bc.contracts.Management.Hash, "deploy", rawNef, rawManifest)
require.NoError(t, err)
require.Equal(t, aer.VMState, vm.HaltState)
}
// This function generates "../rpc/testdata/testblocks.acc" file which contains data
// for RPC unit tests. It also is a nice integration test.
// To generate new "../rpc/testdata/testblocks.acc", follow the steps:
// 1. Set saveChain down below to true
// 2. Run tests with `$ make test`
func TestCreateBasicChain(t *testing.T) {
const saveChain = false
const prefix = "../rpc/server/testdata/"
bc := newTestChain(t)
initBasicChain(t, bc)
if saveChain {
outStream, err := os.Create(prefix + "testblocks.acc")
require.NoError(t, err)
2021-03-01 11:14:15 +00:00
t.Cleanup(func() {
outStream.Close()
})
writer := io.NewBinWriterFromIO(outStream)
writer.WriteU32LE(bc.BlockHeight())
err = chaindump.Dump(bc, writer, 1, bc.BlockHeight())
require.NoError(t, err)
}
priv0 := testchain.PrivateKeyByID(0)
priv1 := testchain.PrivateKeyByID(1)
priv0ScriptHash := priv0.GetScriptHash()
acc0 := wallet.NewAccountFromPrivateKey(priv0)
// Prepare some transaction for future submission.
txSendRaw := newNEP17Transfer(bc.contracts.NEO.Hash, priv0ScriptHash, priv1.GetScriptHash(), int64(fixedn.Fixed8FromInt64(1000)))
txSendRaw.ValidUntilBlock = bc.config.MaxValidUntilBlockIncrement
txSendRaw.Nonce = 0x1234
txSendRaw.Signers = []transaction.Signer{{
Account: priv0ScriptHash,
Scopes: transaction.CalledByEntry,
AllowedContracts: nil,
AllowedGroups: nil,
}}
require.NoError(t, addNetworkFee(bc, txSendRaw, acc0))
require.NoError(t, acc0.SignTx(testchain.Network(), txSendRaw))
bw := io.NewBufBinWriter()
txSendRaw.EncodeBinary(bw.BinWriter)
t.Logf("sendrawtransaction: \n\tbase64: %s\n\tHash LE: %s", base64.StdEncoding.EncodeToString(bw.Bytes()), txSendRaw.Hash().StringLE())
require.False(t, saveChain)
}
func initBasicChain(t *testing.T, bc *Blockchain) {
const prefix = "../rpc/server/testdata/"
// Increase in case if you need more blocks
const validUntilBlock = 1200
// To be incremented after each created transaction to keep chain constant.
var testNonce uint32 = 1
// Use as nonce when new transaction is created to avoid random data in tests.
getNextNonce := func() uint32 {
testNonce++
return testNonce
}
const neoAmount = 99999000
gasHash := bc.contracts.GAS.Hash
neoHash := bc.contracts.NEO.Hash
policyHash := bc.contracts.Policy.Hash
notaryHash := bc.contracts.Notary.Hash
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", bc.GetHeaderHash(0).StringLE())
priv0 := testchain.PrivateKeyByID(0)
priv0ScriptHash := priv0.GetScriptHash()
priv1 := testchain.PrivateKeyByID(1)
priv1ScriptHash := priv1.GetScriptHash()
acc0 := wallet.NewAccountFromPrivateKey(priv0)
acc1 := wallet.NewAccountFromPrivateKey(priv1)
deployContractFromPriv0 := func(t *testing.T, path, contractName string, configPath *string, expectedID int32) (util.Uint256, util.Uint256, util.Uint160) {
txDeploy, _ := newDeployTx(t, bc, priv0ScriptHash, path, contractName, configPath)
txDeploy.Nonce = getNextNonce()
txDeploy.ValidUntilBlock = validUntilBlock
require.NoError(t, addNetworkFee(bc, txDeploy, acc0))
require.NoError(t, acc0.SignTx(testchain.Network(), txDeploy))
b := bc.newBlock(txDeploy)
require.NoError(t, bc.AddBlock(b)) // block #11
checkTxHalt(t, bc, txDeploy.Hash())
sh, err := bc.GetContractScriptHash(expectedID)
require.NoError(t, err)
return b.Hash(), txDeploy.Hash(), sh
}
require.Equal(t, big.NewInt(5000_0000), bc.GetUtilityTokenBalance(priv0ScriptHash)) // gas bounty
// Block #1: move 1000 GAS and neoAmount NEO to priv0.
txMoveNeo, err := testchain.NewTransferFromOwner(bc, neoHash, priv0ScriptHash, neoAmount, getNextNonce(), validUntilBlock)
require.NoError(t, err)
// Move some GAS to one simple account.
txMoveGas, err := testchain.NewTransferFromOwner(bc, gasHash, priv0ScriptHash, int64(fixedn.Fixed8FromInt64(1000)),
getNextNonce(), validUntilBlock)
require.NoError(t, err)
b := bc.newBlock(txMoveNeo, txMoveGas)
require.NoError(t, bc.AddBlock(b))
checkTxHalt(t, bc, txMoveGas.Hash())
checkTxHalt(t, bc, txMoveNeo.Hash())
t.Logf("Block1 hash: %s", b.Hash().StringLE())
bw := io.NewBufBinWriter()
b.EncodeBinary(bw.BinWriter)
require.NoError(t, bw.Err)
2021-03-17 13:40:24 +00:00
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())
2021-03-17 13:40:24 +00:00
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())
require.True(t, bc.GetUtilityTokenBalance(priv0ScriptHash).Cmp(big.NewInt(1000*native.GASFactor)) >= 0)
// info for getblockheader rpc tests
t.Logf("header hash: %s", b.Hash().StringLE())
buf := io.NewBufBinWriter()
2021-03-01 13:44:47 +00:00
b.Header.EncodeBinary(buf.BinWriter)
t.Logf("header: %s", hex.EncodeToString(buf.Bytes()))
// Block #2: deploy test_contract.
cfgPath := prefix + "test_contract.yml"
block2H, txDeployH, cHash := deployContractFromPriv0(t, prefix+"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.
script := io.NewBufBinWriter()
emit.AppCall(script.BinWriter, cHash, "putValue", callflag.All, "testkey", "testvalue")
txInv := transaction.New(script.Bytes(), 1*native.GASFactor)
txInv.Nonce = getNextNonce()
txInv.ValidUntilBlock = validUntilBlock
txInv.Signers = []transaction.Signer{{Account: priv0ScriptHash}}
require.NoError(t, addNetworkFee(bc, txInv, acc0))
require.NoError(t, acc0.SignTx(testchain.Network(), txInv))
b = bc.newBlock(txInv)
require.NoError(t, bc.AddBlock(b))
checkTxHalt(t, bc, txInv.Hash())
t.Logf("txInv: %s", txInv.Hash().StringLE())
// Block #4: transfer 0.0000_1 NEO from priv0 to priv1.
txNeo0to1 := newNEP17Transfer(neoHash, priv0ScriptHash, priv1ScriptHash, 1000)
txNeo0to1.Nonce = getNextNonce()
txNeo0to1.ValidUntilBlock = validUntilBlock
txNeo0to1.Signers = []transaction.Signer{
{
Account: priv0ScriptHash,
Scopes: transaction.CalledByEntry,
AllowedContracts: nil,
AllowedGroups: nil,
},
}
require.NoError(t, addNetworkFee(bc, txNeo0to1, acc0))
require.NoError(t, acc0.SignTx(testchain.Network(), txNeo0to1))
b = bc.newBlock(txNeo0to1)
require.NoError(t, bc.AddBlock(b))
checkTxHalt(t, bc, txNeo0to1.Hash())
// Block #5: initialize rubles contract and transfer 1000 rubles from the contract to priv0.
w := io.NewBufBinWriter()
emit.AppCall(w.BinWriter, cHash, "init", callflag.All)
initTx := transaction.New(w.Bytes(), 1*native.GASFactor)
initTx.Nonce = getNextNonce()
initTx.ValidUntilBlock = validUntilBlock
initTx.Signers = []transaction.Signer{{Account: priv0ScriptHash}}
require.NoError(t, addNetworkFee(bc, initTx, acc0))
require.NoError(t, acc0.SignTx(testchain.Network(), initTx))
transferTx := newNEP17Transfer(cHash, cHash, priv0ScriptHash, 1000)
transferTx.Nonce = getNextNonce()
transferTx.ValidUntilBlock = validUntilBlock
transferTx.Signers = []transaction.Signer{
{
Account: priv0ScriptHash,
Scopes: transaction.CalledByEntry,
AllowedContracts: nil,
AllowedGroups: nil,
},
}
require.NoError(t, addNetworkFee(bc, transferTx, acc0))
transferTx.SystemFee += 1000000
require.NoError(t, acc0.SignTx(testchain.Network(), transferTx))
b = bc.newBlock(initTx, transferTx)
require.NoError(t, bc.AddBlock(b))
checkTxHalt(t, bc, initTx.Hash())
checkTxHalt(t, bc, transferTx.Hash())
t.Logf("recieveRublesTx: %v", transferTx.Hash().StringLE())
// Block #6: transfer 123 rubles from priv0 to priv1
transferTx = newNEP17Transfer(cHash, priv0.GetScriptHash(), priv1ScriptHash, 123)
transferTx.Nonce = getNextNonce()
transferTx.ValidUntilBlock = validUntilBlock
transferTx.Signers = []transaction.Signer{
{
Account: priv0ScriptHash,
Scopes: transaction.CalledByEntry,
AllowedContracts: nil,
AllowedGroups: nil,
},
}
require.NoError(t, addNetworkFee(bc, transferTx, acc0))
transferTx.SystemFee += 1000000
require.NoError(t, acc0.SignTx(testchain.Network(), transferTx))
b = bc.newBlock(transferTx)
require.NoError(t, bc.AddBlock(b))
checkTxHalt(t, bc, transferTx.Hash())
t.Logf("sendRublesTx: %v", transferTx.Hash().StringLE())
// Block #7: push verification contract into the chain.
verifyPath := filepath.Join(prefix, "verify", "verification_contract.go")
_, _, _ = deployContractFromPriv0(t, verifyPath, "Verify", nil, 2)
// Block #8: deposit some GAS to notary contract for priv0.
transferTx = newNEP17Transfer(gasHash, priv0.GetScriptHash(), notaryHash, 10_0000_0000, priv0.GetScriptHash(), int64(bc.BlockHeight()+1000))
transferTx.Nonce = getNextNonce()
transferTx.ValidUntilBlock = validUntilBlock
transferTx.Signers = []transaction.Signer{
{
Account: priv0ScriptHash,
Scopes: transaction.CalledByEntry,
},
}
require.NoError(t, addNetworkFee(bc, transferTx, acc0))
transferTx.SystemFee += 10_0000
require.NoError(t, acc0.SignTx(testchain.Network(), transferTx))
b = bc.newBlock(transferTx)
require.NoError(t, bc.AddBlock(b))
checkTxHalt(t, bc, transferTx.Hash())
t.Logf("notaryDepositTxPriv0: %v", transferTx.Hash().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))
bc.setNodesByRole(t, true, noderoles.P2PNotary, keys.PublicKeys{ntr.Accounts[0].PrivateKey().PublicKey()})
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(prefix, "verify_args", "verification_with_args_contract.go")
_, _, _ = deployContractFromPriv0(t, verifyPath, "VerifyWithArgs", nil, 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
// Block #12: transfer funds to committee for futher NS record registration.
transferFundsToCommittee(t, bc) // block #12
// Block #13: add `.com` root to NNS.
res, err := invokeContractMethodGeneric(bc, -1,
nsHash, "addRoot", true, "com") // block #13
require.NoError(t, err)
checkResult(t, res, stackitem.Null{})
// Block #14: register `neo.com` via NNS.
res, err = invokeContractMethodGeneric(bc, -1,
nsHash, "register", acc0, "neo.com", priv0ScriptHash) // block #14
require.NoError(t, err)
checkResult(t, res, stackitem.NewBool(true))
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.
res, err = invokeContractMethodGeneric(bc, -1, nsHash,
"setRecord", acc0, "neo.com", int64(nns.A), "1.2.3.4") // block #15
require.NoError(t, err)
checkResult(t, res, stackitem.Null{})
// Block #16: invoke `test_contract.go`: put new value with the same key to check `getstate` RPC call
2021-10-07 09:03:37 +00:00
script.Reset()
emit.AppCall(script.BinWriter, cHash, "putValue", callflag.All, "testkey", "newtestvalue")
// Invoke `test_contract.go`: put values to check `findstates` RPC call
emit.AppCall(script.BinWriter, cHash, "putValue", callflag.All, "aa", "v1")
emit.AppCall(script.BinWriter, cHash, "putValue", callflag.All, "aa10", "v2")
emit.AppCall(script.BinWriter, cHash, "putValue", callflag.All, "aa50", "v3")
2021-10-07 09:03:37 +00:00
txInv = transaction.New(script.Bytes(), 1*native.GASFactor)
txInv.Nonce = getNextNonce()
txInv.ValidUntilBlock = validUntilBlock
txInv.Signers = []transaction.Signer{{Account: priv0ScriptHash}}
require.NoError(t, addNetworkFee(bc, txInv, acc0))
require.NoError(t, acc0.SignTx(testchain.Network(), txInv))
b = bc.newBlock(txInv)
require.NoError(t, bc.AddBlock(b)) // block #16
2021-10-07 09:03:37 +00:00
checkTxHalt(t, bc, txInv.Hash())
// 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
// 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}
txGas0toNFS := newNEP17Transfer(gasHash, priv0ScriptHash, nfsHash, 10_0000_0000, containerID.BytesBE(), objectID.BytesBE())
txGas0toNFS.SystemFee += 4000_0000
txGas0toNFS.Nonce = getNextNonce()
txGas0toNFS.ValidUntilBlock = validUntilBlock
txGas0toNFS.Signers = []transaction.Signer{
{
Account: priv0ScriptHash,
Scopes: transaction.CalledByEntry,
},
}
require.NoError(t, addNetworkFee(bc, txGas0toNFS, acc0))
require.NoError(t, acc0.SignTx(testchain.Network(), txGas0toNFS))
b = bc.newBlock(txGas0toNFS)
require.NoError(t, bc.AddBlock(b)) // block #18
checkTxHalt(t, bc, txGas0toNFS.Hash())
aer, _ := bc.GetAppExecResults(txGas0toNFS.Hash(), trigger.Application)
require.Equal(t, 2, len(aer[0].Events)) // GAS transfer + NFSO transfer
tokenID, err = aer[0].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.
script.Reset()
emit.AppCall(script.BinWriter, nfsHash, "transfer", callflag.All, priv0ScriptHash, priv1ScriptHash, 25, tokenID, nil)
emit.Opcodes(script.BinWriter, opcode.ASSERT)
require.NoError(t, script.Err)
txNFS0to1 := transaction.New(script.Bytes(), 1*native.GASFactor)
txNFS0to1.Nonce = getNextNonce()
txNFS0to1.ValidUntilBlock = validUntilBlock
txNFS0to1.Signers = []transaction.Signer{{Account: priv0ScriptHash, Scopes: transaction.CalledByEntry}}
require.NoError(t, addNetworkFee(bc, txNFS0to1, acc0))
require.NoError(t, acc0.SignTx(testchain.Network(), txNFS0to1))
b = bc.newBlock(txNFS0to1)
require.NoError(t, bc.AddBlock(b)) // block #19
checkTxHalt(t, bc, txNFS0to1.Hash())
// Block #20: transfer 1000 GAS to priv1.
txMoveGas, err = testchain.NewTransferFromOwner(bc, gasHash, priv1ScriptHash, int64(fixedn.Fixed8FromInt64(1000)),
getNextNonce(), validUntilBlock)
require.NoError(t, err)
require.NoError(t, bc.AddBlock(bc.newBlock(txMoveGas)))
checkTxHalt(t, bc, txMoveGas.Hash()) // block #20
// Block #21: transfer 0.05 NFSO from priv1 back to priv0.
script.Reset()
emit.AppCall(script.BinWriter, nfsHash, "transfer", callflag.All, priv1ScriptHash, priv0.GetScriptHash(), 5, tokenID, nil)
emit.Opcodes(script.BinWriter, opcode.ASSERT)
require.NoError(t, script.Err)
txNFS1to0 := transaction.New(script.Bytes(), 1*native.GASFactor)
txNFS1to0.Nonce = getNextNonce()
txNFS1to0.ValidUntilBlock = validUntilBlock
txNFS1to0.Signers = []transaction.Signer{{Account: priv1ScriptHash, Scopes: transaction.CalledByEntry}}
require.NoError(t, addNetworkFee(bc, txNFS1to0, acc0))
require.NoError(t, acc1.SignTx(testchain.Network(), txNFS1to0))
b = bc.newBlock(txNFS1to0)
require.NoError(t, bc.AddBlock(b)) // block #21
checkTxHalt(t, bc, txNFS1to0.Hash())
// Compile contract to test `invokescript` RPC call
invokePath := filepath.Join(prefix, "invoke", "invokescript_contract.go")
invokeCfg := filepath.Join(prefix, "invoke", "invoke.yml")
_, _ = newDeployTx(t, bc, priv0ScriptHash, invokePath, "ContractForInvokescriptTest", &invokeCfg)
}
2020-11-19 10:00:46 +00:00
func newNEP17Transfer(sc, from, to util.Uint160, amount int64, additionalArgs ...interface{}) *transaction.Transaction {
core: do not allow NEP17 roundtrip in case of insufficient funds NEP17 roundtrip is prohibited if from account doesn't have enough funds. This commit fixes states diff in block 92057 where account NfuwpaQ1A2xaeVbxWe8FRtaRgaMa8yF3YM initiates two NEO roundtrips with amount exceeding the account's balance: block 92057: value mismatch for key +////xTbYWBH3r5qhRKZAPFPHabKfb2vhQ==: QQMhAkwBIQOZZwEA vs QQMhAkwBIQN/ZwEA block 92057: value mismatch for key +v///ws=: kqlddcitCg== vs tphddcitCg== block 92057: value mismatch for key +v///xTbYWBH3r5qhRKZAPFPHabKfb2vhQ==: QQEhBUWyDu0W vs QQEhBWmhDu0W C#'s applog (contains False and False on stack for both transfers): ``` { "id" : 1, "jsonrpc" : "2.0", "result" : { "executions" : [ { "gasconsumed" : "11955500", "exception" : null, "stack" : [ { "value" : false, "type" : "Boolean" }, { "value" : false, "type" : "Boolean" } ], "vmstate" : "HALT", "trigger" : "Application", "notifications" : [] } ], "txid" : "0x8e73a7e9a566a514813907272ad65fc965002c3b098eacc5bdda529af19d7688" } } ``` Go's applog (both transfers succeeded and GAS minted): ``` { "result" : { "executions" : [ { "gasconsumed" : "11955500", "trigger" : "Application", "stack" : [ { "type" : "Boolean", "value" : true }, { "type" : "Boolean", "value" : true } ], "vmstate" : "HALT", "notifications" : [ { "eventname" : "Transfer", "contract" : "0xd2a4cff31913016155e38e474a2c06d08be276cf", "state" : { "value" : [ { "type" : "Any" }, { "value" : "22FgR96+aoUSmQDxTx2myn29r4U=", "type" : "ByteString" }, { "value" : "4316", "type" : "Integer" } ], "type" : "Array" } }, { "state" : { "type" : "Array", "value" : [ { "value" : "22FgR96+aoUSmQDxTx2myn29r4U=", "type" : "ByteString" }, { "type" : "ByteString", "value" : "22FgR96+aoUSmQDxTx2myn29r4U=" }, { "type" : "Integer", "value" : "1111111111" } ] }, "contract" : "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5", "eventname" : "Transfer" }, { "contract" : "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5", "state" : { "type" : "Array", "value" : [ { "value" : "22FgR96+aoUSmQDxTx2myn29r4U=", "type" : "ByteString" }, { "type" : "ByteString", "value" : "22FgR96+aoUSmQDxTx2myn29r4U=" }, { "type" : "Integer", "value" : "1111111" } ] }, "eventname" : "Transfer" } ] } ], "txid" : "0x8e73a7e9a566a514813907272ad65fc965002c3b098eacc5bdda529af19d7688" }, "id" : 1, "jsonrpc" : "2.0" } ```
2021-06-09 08:58:58 +00:00
return newNEP17TransferWithAssert(sc, from, to, amount, true, additionalArgs...)
}
func newNEP17TransferWithAssert(sc, from, to util.Uint160, amount int64, needAssert bool, additionalArgs ...interface{}) *transaction.Transaction {
w := io.NewBufBinWriter()
emit.AppCall(w.BinWriter, sc, "transfer", callflag.All, from, to, amount, additionalArgs)
core: do not allow NEP17 roundtrip in case of insufficient funds NEP17 roundtrip is prohibited if from account doesn't have enough funds. This commit fixes states diff in block 92057 where account NfuwpaQ1A2xaeVbxWe8FRtaRgaMa8yF3YM initiates two NEO roundtrips with amount exceeding the account's balance: block 92057: value mismatch for key +////xTbYWBH3r5qhRKZAPFPHabKfb2vhQ==: QQMhAkwBIQOZZwEA vs QQMhAkwBIQN/ZwEA block 92057: value mismatch for key +v///ws=: kqlddcitCg== vs tphddcitCg== block 92057: value mismatch for key +v///xTbYWBH3r5qhRKZAPFPHabKfb2vhQ==: QQEhBUWyDu0W vs QQEhBWmhDu0W C#'s applog (contains False and False on stack for both transfers): ``` { "id" : 1, "jsonrpc" : "2.0", "result" : { "executions" : [ { "gasconsumed" : "11955500", "exception" : null, "stack" : [ { "value" : false, "type" : "Boolean" }, { "value" : false, "type" : "Boolean" } ], "vmstate" : "HALT", "trigger" : "Application", "notifications" : [] } ], "txid" : "0x8e73a7e9a566a514813907272ad65fc965002c3b098eacc5bdda529af19d7688" } } ``` Go's applog (both transfers succeeded and GAS minted): ``` { "result" : { "executions" : [ { "gasconsumed" : "11955500", "trigger" : "Application", "stack" : [ { "type" : "Boolean", "value" : true }, { "type" : "Boolean", "value" : true } ], "vmstate" : "HALT", "notifications" : [ { "eventname" : "Transfer", "contract" : "0xd2a4cff31913016155e38e474a2c06d08be276cf", "state" : { "value" : [ { "type" : "Any" }, { "value" : "22FgR96+aoUSmQDxTx2myn29r4U=", "type" : "ByteString" }, { "value" : "4316", "type" : "Integer" } ], "type" : "Array" } }, { "state" : { "type" : "Array", "value" : [ { "value" : "22FgR96+aoUSmQDxTx2myn29r4U=", "type" : "ByteString" }, { "type" : "ByteString", "value" : "22FgR96+aoUSmQDxTx2myn29r4U=" }, { "type" : "Integer", "value" : "1111111111" } ] }, "contract" : "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5", "eventname" : "Transfer" }, { "contract" : "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5", "state" : { "type" : "Array", "value" : [ { "value" : "22FgR96+aoUSmQDxTx2myn29r4U=", "type" : "ByteString" }, { "type" : "ByteString", "value" : "22FgR96+aoUSmQDxTx2myn29r4U=" }, { "type" : "Integer", "value" : "1111111" } ] }, "eventname" : "Transfer" } ] } ], "txid" : "0x8e73a7e9a566a514813907272ad65fc965002c3b098eacc5bdda529af19d7688" }, "id" : 1, "jsonrpc" : "2.0" } ```
2021-06-09 08:58:58 +00:00
if needAssert {
emit.Opcodes(w.BinWriter, opcode.ASSERT)
}
2021-01-26 16:37:19 +00:00
if w.Err != nil {
panic(fmt.Errorf("failed to create NEP-17 transfer transaction: %w", w.Err))
2021-01-26 16:37:19 +00:00
}
script := w.Bytes()
return transaction.New(script, 11000000)
}
func newDeployTx(t *testing.T, bc *Blockchain, sender util.Uint160, name, ctrName string, cfgName *string) (*transaction.Transaction, util.Uint160) {
tx, h, avm, err := testchain.NewDeployTx(bc, name, sender, nil, cfgName)
require.NoError(t, err)
t.Logf("contract (%s): \n\tHash: %s\n\tAVM: %s", name, h.StringLE(), base64.StdEncoding.EncodeToString(avm))
return tx, h
}
func addSigners(sender util.Uint160, txs ...*transaction.Transaction) {
for _, tx := range txs {
tx.Signers = []transaction.Signer{{
Account: sender,
Scopes: transaction.Global,
AllowedContracts: nil,
AllowedGroups: nil,
}}
}
}
func addNetworkFee(bc *Blockchain, tx *transaction.Transaction, sender *wallet.Account) error {
size := io.GetVarSize(tx)
netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), sender.Contract.Script)
tx.NetworkFee += netFee
size += sizeDelta
for _, cosigner := range tx.Signers {
contract := bc.GetContractState(cosigner.Account)
if contract != nil {
netFee, sizeDelta = fee.Calculate(bc.GetBaseExecFee(), contract.NEF.Script)
tx.NetworkFee += netFee
size += sizeDelta
}
}
tx.NetworkFee += int64(size) * bc.FeePerByte()
return nil
}
2020-11-19 10:00:46 +00:00
// Signer can be either bool or *wallet.Account.
// In the first case `true` means sign by committee, `false` means sign by validators.
func prepareContractMethodInvokeGeneric(chain *Blockchain, sysfee int64,
hash util.Uint160, method string, signer interface{}, args ...interface{}) (*transaction.Transaction, error) {
2020-11-19 10:00:46 +00:00
w := io.NewBufBinWriter()
emit.AppCall(w.BinWriter, hash, method, callflag.All, args...)
2020-11-19 10:00:46 +00:00
if w.Err != nil {
return nil, w.Err
}
script := w.Bytes()
tx := transaction.New(script, 0)
2020-11-19 10:00:46 +00:00
tx.ValidUntilBlock = chain.blockHeight + 1
var err error
switch s := signer.(type) {
case bool:
if s {
addSigners(testchain.CommitteeScriptHash(), tx)
setTxSystemFee(chain, sysfee, tx)
err = testchain.SignTxCommittee(chain, tx)
} else {
addSigners(neoOwner, tx)
setTxSystemFee(chain, sysfee, tx)
err = testchain.SignTx(chain, tx)
}
case *wallet.Account:
signTxWithAccounts(chain, sysfee, tx, s)
case []*wallet.Account:
signTxWithAccounts(chain, sysfee, tx, s...)
default:
panic("invalid signer")
}
2020-11-19 10:00:46 +00:00
if err != nil {
return nil, err
}
return tx, nil
}
func setTxSystemFee(bc *Blockchain, sysFee int64, tx *transaction.Transaction) {
if sysFee >= 0 {
tx.SystemFee = sysFee
return
}
lastBlock := bc.topBlock.Load().(*block.Block)
b := &block.Block{
Header: block.Header{
Index: lastBlock.Index + 1,
Timestamp: lastBlock.Timestamp + 1000,
},
Transactions: []*transaction.Transaction{tx},
}
ttx := *tx // prevent setting 'hash' field
ic := bc.GetTestVM(trigger.Application, &ttx, b)
defer ic.Finalize()
ic.VM.LoadWithFlags(tx.Script, callflag.All)
_ = ic.VM.Run()
tx.SystemFee = ic.VM.GasConsumed()
}
func signTxWithAccounts(chain *Blockchain, sysFee int64, tx *transaction.Transaction, accs ...*wallet.Account) {
scope := transaction.CalledByEntry
for _, acc := range accs {
accH, _ := address.StringToUint160(acc.Address)
tx.Signers = append(tx.Signers, transaction.Signer{
Account: accH,
Scopes: scope,
})
scope = transaction.Global
}
setTxSystemFee(chain, sysFee, tx)
size := io.GetVarSize(tx)
for _, acc := range accs {
if acc.Contract.Deployed {
// don't need precise calculation for tests
tx.NetworkFee += 1000_0000
continue
}
netFee, sizeDelta := fee.Calculate(chain.GetBaseExecFee(), acc.Contract.Script)
size += sizeDelta
tx.NetworkFee += netFee
}
tx.NetworkFee += int64(size) * chain.FeePerByte()
for _, acc := range accs {
if err := acc.SignTx(testchain.Network(), tx); err != nil {
panic(err)
}
}
}
func persistBlock(chain *Blockchain, txs ...*transaction.Transaction) ([]*state.AppExecResult, error) {
b := chain.newBlock(txs...)
err := chain.AddBlock(b)
2020-11-19 10:00:46 +00:00
if err != nil {
return nil, err
}
aers := make([]*state.AppExecResult, len(txs))
for i, tx := range txs {
res, err := chain.GetAppExecResults(tx.Hash(), trigger.Application)
if err != nil {
return nil, err
}
aers[i] = &res[0]
}
return aers, nil
}
func invokeContractMethod(chain *Blockchain, sysfee int64, hash util.Uint160, method string, args ...interface{}) (*state.AppExecResult, error) {
return invokeContractMethodGeneric(chain, sysfee, hash, method, false, args...)
}
func invokeContractMethodGeneric(chain *Blockchain, sysfee int64, hash util.Uint160, method string,
signer interface{}, args ...interface{}) (*state.AppExecResult, error) {
tx, err := prepareContractMethodInvokeGeneric(chain, sysfee, hash,
method, signer, args...)
if err != nil {
return nil, err
}
aers, err := persistBlock(chain, tx)
2020-11-19 10:00:46 +00:00
if err != nil {
return nil, err
}
return aers[0], nil
2020-11-19 10:00:46 +00:00
}
2020-12-30 08:01:13 +00:00
func transferTokenFromMultisigAccountCheckOK(t *testing.T, chain *Blockchain, to, tokenHash util.Uint160, amount int64, additionalArgs ...interface{}) {
transferTx := transferTokenFromMultisigAccount(t, chain, to, tokenHash, amount, additionalArgs...)
res, err := chain.GetAppExecResults(transferTx.Hash(), trigger.Application)
require.NoError(t, err)
require.Equal(t, vm.HaltState, res[0].VMState)
require.Equal(t, 0, len(res[0].Stack))
}
2020-11-19 10:00:46 +00:00
func transferTokenFromMultisigAccount(t *testing.T, chain *Blockchain, to, tokenHash util.Uint160, amount int64, additionalArgs ...interface{}) *transaction.Transaction {
core: do not allow NEP17 roundtrip in case of insufficient funds NEP17 roundtrip is prohibited if from account doesn't have enough funds. This commit fixes states diff in block 92057 where account NfuwpaQ1A2xaeVbxWe8FRtaRgaMa8yF3YM initiates two NEO roundtrips with amount exceeding the account's balance: block 92057: value mismatch for key +////xTbYWBH3r5qhRKZAPFPHabKfb2vhQ==: QQMhAkwBIQOZZwEA vs QQMhAkwBIQN/ZwEA block 92057: value mismatch for key +v///ws=: kqlddcitCg== vs tphddcitCg== block 92057: value mismatch for key +v///xTbYWBH3r5qhRKZAPFPHabKfb2vhQ==: QQEhBUWyDu0W vs QQEhBWmhDu0W C#'s applog (contains False and False on stack for both transfers): ``` { "id" : 1, "jsonrpc" : "2.0", "result" : { "executions" : [ { "gasconsumed" : "11955500", "exception" : null, "stack" : [ { "value" : false, "type" : "Boolean" }, { "value" : false, "type" : "Boolean" } ], "vmstate" : "HALT", "trigger" : "Application", "notifications" : [] } ], "txid" : "0x8e73a7e9a566a514813907272ad65fc965002c3b098eacc5bdda529af19d7688" } } ``` Go's applog (both transfers succeeded and GAS minted): ``` { "result" : { "executions" : [ { "gasconsumed" : "11955500", "trigger" : "Application", "stack" : [ { "type" : "Boolean", "value" : true }, { "type" : "Boolean", "value" : true } ], "vmstate" : "HALT", "notifications" : [ { "eventname" : "Transfer", "contract" : "0xd2a4cff31913016155e38e474a2c06d08be276cf", "state" : { "value" : [ { "type" : "Any" }, { "value" : "22FgR96+aoUSmQDxTx2myn29r4U=", "type" : "ByteString" }, { "value" : "4316", "type" : "Integer" } ], "type" : "Array" } }, { "state" : { "type" : "Array", "value" : [ { "value" : "22FgR96+aoUSmQDxTx2myn29r4U=", "type" : "ByteString" }, { "type" : "ByteString", "value" : "22FgR96+aoUSmQDxTx2myn29r4U=" }, { "type" : "Integer", "value" : "1111111111" } ] }, "contract" : "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5", "eventname" : "Transfer" }, { "contract" : "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5", "state" : { "type" : "Array", "value" : [ { "value" : "22FgR96+aoUSmQDxTx2myn29r4U=", "type" : "ByteString" }, { "type" : "ByteString", "value" : "22FgR96+aoUSmQDxTx2myn29r4U=" }, { "type" : "Integer", "value" : "1111111" } ] }, "eventname" : "Transfer" } ] } ], "txid" : "0x8e73a7e9a566a514813907272ad65fc965002c3b098eacc5bdda529af19d7688" }, "id" : 1, "jsonrpc" : "2.0" } ```
2021-06-09 08:58:58 +00:00
return transferTokenFromMultisigAccountWithAssert(t, chain, to, tokenHash, amount, true, additionalArgs...)
}
func transferTokenFromMultisigAccountWithAssert(t *testing.T, chain *Blockchain, to, tokenHash util.Uint160, amount int64, needAssert bool, additionalArgs ...interface{}) *transaction.Transaction {
transferTx := newNEP17TransferWithAssert(tokenHash, testchain.MultisigScriptHash(), to, amount, needAssert, additionalArgs...)
2020-11-19 10:00:46 +00:00
transferTx.SystemFee = 100000000
transferTx.ValidUntilBlock = chain.BlockHeight() + 1
addSigners(neoOwner, transferTx)
2020-11-19 10:00:46 +00:00
require.NoError(t, testchain.SignTx(chain, transferTx))
b := chain.newBlock(transferTx)
require.NoError(t, chain.AddBlock(b))
return transferTx
}
func checkResult(t *testing.T, result *state.AppExecResult, expected stackitem.Item) {
2020-11-27 10:55:48 +00:00
require.Equal(t, vm.HaltState, result.VMState, result.FaultException)
2020-11-19 10:00:46 +00:00
require.Equal(t, 1, len(result.Stack))
require.Equal(t, expected, result.Stack[0])
}
func checkTxHalt(t testing.TB, bc *Blockchain, h util.Uint256) {
aer, err := bc.GetAppExecResults(h, trigger.Application)
require.NoError(t, err)
require.Equal(t, 1, len(aer))
require.Equal(t, vm.HaltState, aer[0].VMState, aer[0].FaultException)
}
2020-11-19 10:00:46 +00:00
func checkFAULTState(t *testing.T, result *state.AppExecResult) {
require.Equal(t, vm.FaultState, result.VMState)
}
func checkBalanceOf(t *testing.T, chain *Blockchain, addr util.Uint160, expected int) {
balance := chain.GetUtilityTokenBalance(addr)
require.Equal(t, int64(expected), balance.Int64())
2020-11-19 10:00:46 +00:00
}
2021-01-26 16:37:19 +00:00
type NotaryFeerStub struct {
bc blockchainer.Blockchainer
}
func (f NotaryFeerStub) FeePerByte() int64 { return f.bc.FeePerByte() }
func (f NotaryFeerStub) GetUtilityTokenBalance(acc util.Uint160) *big.Int {
return f.bc.GetNotaryBalance(acc)
}
func (f NotaryFeerStub) BlockHeight() uint32 { return f.bc.BlockHeight() }
func (f NotaryFeerStub) P2PSigExtensionsEnabled() bool { return f.bc.P2PSigExtensionsEnabled() }
func NewNotaryFeerStub(bc blockchainer.Blockchainer) NotaryFeerStub {
return NotaryFeerStub{
bc: bc,
}
}