nativetest: migrate NEO contract tests to neotest
This commit is contained in:
parent
e0ca05f62c
commit
2f18b114f2
4 changed files with 482 additions and 460 deletions
|
@ -1827,3 +1827,10 @@ func TestBlockchain_InitWithIncompleteStateJump(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func setSigner(tx *transaction.Transaction, h util.Uint160) {
|
||||
tx.Signers = []transaction.Signer{{
|
||||
Account: h,
|
||||
Scopes: transaction.Global,
|
||||
}}
|
||||
}
|
||||
|
|
|
@ -739,6 +739,13 @@ func checkResult(t *testing.T, result *state.AppExecResult, expected stackitem.I
|
|||
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)
|
||||
}
|
||||
|
||||
func checkFAULTState(t *testing.T, result *state.AppExecResult) {
|
||||
require.Equal(t, vm.FaultState, result.VMState)
|
||||
}
|
||||
|
|
468
pkg/core/native/native_test/neo_test.go
Normal file
468
pkg/core/native/native_test/neo_test.go
Normal file
|
@ -0,0 +1,468 @@
|
|||
package native_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"math/big"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/internal/random"
|
||||
"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/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/smartcontract/trigger"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func newNeoCommitteeClient(t *testing.T, expectedGASBalance int) *neotest.ContractInvoker {
|
||||
bc, validators, committee := chain.NewMulti(t)
|
||||
e := neotest.NewExecutor(t, bc, validators, committee)
|
||||
|
||||
if expectedGASBalance > 0 {
|
||||
e.ValidatorInvoker(e.NativeHash(t, nativenames.Gas)).Invoke(t, true, "transfer", e.Validator.ScriptHash(), e.CommitteeHash, 100_0000_0000, nil)
|
||||
}
|
||||
|
||||
return e.CommitteeInvoker(e.NativeHash(t, nativenames.Neo))
|
||||
}
|
||||
|
||||
func newNeoValidatorsClient(t *testing.T) *neotest.ContractInvoker {
|
||||
c := newNeoCommitteeClient(t, 100_0000_0000)
|
||||
return c.ValidatorInvoker(c.NativeHash(t, nativenames.Neo))
|
||||
}
|
||||
|
||||
func TestNEO_GasPerBlock(t *testing.T) {
|
||||
testGetSet(t, newNeoCommitteeClient(t, 100_0000_0000), "GasPerBlock", 5*native.GASFactor, 0, 10*native.GASFactor)
|
||||
}
|
||||
|
||||
func TestNEO_RegisterPrice(t *testing.T) {
|
||||
testGetSet(t, newNeoCommitteeClient(t, 100_0000_0000), "RegisterPrice", native.DefaultRegisterPrice, 1, math.MaxInt64)
|
||||
}
|
||||
|
||||
func TestNEO_Vote(t *testing.T) {
|
||||
neoCommitteeInvoker := newNeoCommitteeClient(t, 100_0000_0000)
|
||||
neoValidatorsInvoker := neoCommitteeInvoker.WithSigners(neoCommitteeInvoker.Validator)
|
||||
e := neoCommitteeInvoker.Executor
|
||||
|
||||
committeeSize := len(neoValidatorsInvoker.Chain.GetConfig().StandbyCommittee)
|
||||
validatorsCount := neoCommitteeInvoker.Chain.GetConfig().ValidatorsCount
|
||||
freq := validatorsCount + committeeSize
|
||||
advanceChain := func(t *testing.T) {
|
||||
for i := 0; i < freq; i++ {
|
||||
neoCommitteeInvoker.AddNewBlock(t)
|
||||
}
|
||||
}
|
||||
|
||||
standBySorted := e.Chain.GetStandByValidators()
|
||||
sort.Sort(standBySorted)
|
||||
pubs, err := e.Chain.GetValidators()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, standBySorted, keys.PublicKeys(pubs))
|
||||
|
||||
// voters vote for candidates. The aim of this test is to check that voting
|
||||
// reward is proportional to the NEO balance.
|
||||
voters := make([]neotest.Signer, committeeSize)
|
||||
// referenceAccounts perform the same actions as voters except voting, i.e. we
|
||||
// will transfer the same amount of NEO to referenceAccounts and see how much
|
||||
// GAS they receive for NEO ownership. We need these values to be able to define
|
||||
// how much GAS voters receive for NEO ownership.
|
||||
referenceAccounts := make([]neotest.Signer, committeeSize)
|
||||
candidates := make([]neotest.Signer, committeeSize)
|
||||
for i := 0; i < committeeSize; i++ {
|
||||
voters[i] = e.NewAccount(t, 10_0000_0000)
|
||||
referenceAccounts[i] = e.NewAccount(t, 10_0000_0000)
|
||||
candidates[i] = e.NewAccount(t, 2000_0000_0000) // enough for one registration
|
||||
}
|
||||
txes := make([]*transaction.Transaction, 0, committeeSize*4-2)
|
||||
for i := 0; i < committeeSize; i++ {
|
||||
transferTx := neoValidatorsInvoker.PrepareInvoke(t, "transfer", e.Validator.ScriptHash(), voters[i].(neotest.SingleSigner).Account().PrivateKey().GetScriptHash(), int64(committeeSize-i)*1000000, nil)
|
||||
txes = append(txes, transferTx)
|
||||
transferTx = neoValidatorsInvoker.PrepareInvoke(t, "transfer", e.Validator.ScriptHash(), referenceAccounts[i].(neotest.SingleSigner).Account().PrivateKey().GetScriptHash(), int64(committeeSize-i)*1000000, nil)
|
||||
txes = append(txes, transferTx)
|
||||
if i > 0 {
|
||||
registerTx := neoValidatorsInvoker.WithSigners(candidates[i]).PrepareInvoke(t, "registerCandidate", candidates[i].(neotest.SingleSigner).Account().PrivateKey().PublicKey().Bytes())
|
||||
txes = append(txes, registerTx)
|
||||
voteTx := neoValidatorsInvoker.WithSigners(voters[i]).PrepareInvoke(t, "vote", voters[i].(neotest.SingleSigner).Account().PrivateKey().GetScriptHash(), candidates[i].(neotest.SingleSigner).Account().PrivateKey().PublicKey().Bytes())
|
||||
txes = append(txes, voteTx)
|
||||
}
|
||||
}
|
||||
neoValidatorsInvoker.AddNewBlock(t, txes...)
|
||||
for _, tx := range txes {
|
||||
e.CheckHalt(t, tx.Hash(), stackitem.Make(true)) // luckily, both `transfer`, `registerCandidate` and `vote` return boolean values
|
||||
}
|
||||
|
||||
// We still haven't voted enough validators in.
|
||||
pubs, err = e.Chain.GetValidators()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, standBySorted, keys.PublicKeys(pubs))
|
||||
|
||||
advanceChain(t)
|
||||
pubs, err = e.Chain.GetNextBlockValidators()
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, standBySorted, keys.PublicKeys(pubs))
|
||||
|
||||
// Register and give some value to the last validator.
|
||||
txes = txes[:0]
|
||||
registerTx := neoValidatorsInvoker.WithSigners(candidates[0]).PrepareInvoke(t, "registerCandidate", candidates[0].(neotest.SingleSigner).Account().PrivateKey().PublicKey().Bytes())
|
||||
txes = append(txes, registerTx)
|
||||
voteTx := neoValidatorsInvoker.WithSigners(voters[0]).PrepareInvoke(t, "vote", voters[0].(neotest.SingleSigner).Account().PrivateKey().GetScriptHash(), candidates[0].(neotest.SingleSigner).Account().PrivateKey().PublicKey().Bytes())
|
||||
txes = append(txes, voteTx)
|
||||
neoValidatorsInvoker.AddNewBlock(t, txes...)
|
||||
for _, tx := range txes {
|
||||
e.CheckHalt(t, tx.Hash(), stackitem.Make(true)) // luckily, both `transfer`, `registerCandidate` and `vote` return boolean values
|
||||
}
|
||||
|
||||
advanceChain(t)
|
||||
pubs, err = neoCommitteeInvoker.Chain.GetNextBlockValidators()
|
||||
require.NoError(t, err)
|
||||
sortedCandidates := make(keys.PublicKeys, validatorsCount)
|
||||
for i := range candidates[:validatorsCount] {
|
||||
sortedCandidates[i] = candidates[i].(neotest.SingleSigner).Account().PrivateKey().PublicKey()
|
||||
}
|
||||
sort.Sort(sortedCandidates)
|
||||
require.EqualValues(t, sortedCandidates, keys.PublicKeys(pubs))
|
||||
|
||||
pubs, err = neoCommitteeInvoker.Chain.GetNextBlockValidators()
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, sortedCandidates, pubs)
|
||||
|
||||
t.Run("check voter rewards", func(t *testing.T) {
|
||||
gasBalance := make([]*big.Int, len(voters))
|
||||
referenceGASBalance := make([]*big.Int, len(referenceAccounts))
|
||||
neoBalance := make([]*big.Int, len(voters))
|
||||
txes = make([]*transaction.Transaction, 0, len(voters))
|
||||
var refTxFee int64
|
||||
for i := range voters {
|
||||
h := voters[i].ScriptHash()
|
||||
refH := referenceAccounts[i].ScriptHash()
|
||||
gasBalance[i] = e.Chain.GetUtilityTokenBalance(h)
|
||||
neoBalance[i], _ = e.Chain.GetGoverningTokenBalance(h)
|
||||
referenceGASBalance[i] = e.Chain.GetUtilityTokenBalance(refH)
|
||||
|
||||
tx := neoCommitteeInvoker.WithSigners(voters[i]).PrepareInvoke(t, "transfer", h.BytesBE(), h.BytesBE(), int64(1), nil)
|
||||
txes = append(txes, tx)
|
||||
tx = neoCommitteeInvoker.WithSigners(referenceAccounts[i]).PrepareInvoke(t, "transfer", refH.BytesBE(), refH.BytesBE(), int64(1), nil)
|
||||
txes = append(txes, tx)
|
||||
refTxFee = tx.SystemFee + tx.NetworkFee
|
||||
}
|
||||
neoCommitteeInvoker.AddNewBlock(t, txes...)
|
||||
for _, tx := range txes {
|
||||
e.CheckHalt(t, tx.Hash(), stackitem.Make(true))
|
||||
}
|
||||
|
||||
// Define reference reward for NEO holding for each voter account.
|
||||
for i := range referenceGASBalance {
|
||||
newBalance := e.Chain.GetUtilityTokenBalance(referenceAccounts[i].ScriptHash())
|
||||
referenceGASBalance[i].Sub(newBalance, referenceGASBalance[i])
|
||||
referenceGASBalance[i].Add(referenceGASBalance[i], big.NewInt(refTxFee))
|
||||
}
|
||||
|
||||
// GAS increase consists of 2 parts: NEO holding + voting for committee nodes.
|
||||
// Here we check that 2-nd part exists and is proportional to the amount of NEO given.
|
||||
for i := range voters {
|
||||
newGAS := e.Chain.GetUtilityTokenBalance(voters[i].ScriptHash())
|
||||
newGAS.Sub(newGAS, gasBalance[i])
|
||||
gasForHold := referenceGASBalance[i]
|
||||
newGAS.Sub(newGAS, gasForHold)
|
||||
require.True(t, newGAS.Sign() > 0)
|
||||
gasBalance[i] = newGAS
|
||||
}
|
||||
// First account voted later than the others.
|
||||
require.Equal(t, -1, gasBalance[0].Cmp(gasBalance[1]))
|
||||
for i := 2; i < validatorsCount; i++ {
|
||||
require.Equal(t, 0, gasBalance[i].Cmp(gasBalance[1]))
|
||||
}
|
||||
require.Equal(t, 1, gasBalance[1].Cmp(gasBalance[validatorsCount]))
|
||||
for i := validatorsCount; i < committeeSize; i++ {
|
||||
require.Equal(t, 0, gasBalance[i].Cmp(gasBalance[validatorsCount]))
|
||||
}
|
||||
})
|
||||
|
||||
neoCommitteeInvoker.WithSigners(candidates[0]).Invoke(t, true, "unregisterCandidate", candidates[0].(neotest.SingleSigner).Account().PrivateKey().PublicKey().Bytes())
|
||||
neoCommitteeInvoker.WithSigners(voters[0]).Invoke(t, false, "vote", voters[0].(neotest.SingleSigner).Account().PrivateKey().GetScriptHash(), candidates[0].(neotest.SingleSigner).Account().PrivateKey().PublicKey().Bytes())
|
||||
|
||||
advanceChain(t)
|
||||
|
||||
pubs, err = e.Chain.GetValidators()
|
||||
require.NoError(t, err)
|
||||
for i := range pubs {
|
||||
require.NotEqual(t, candidates[0], pubs[i])
|
||||
}
|
||||
}
|
||||
|
||||
// TestNEO_RecursiveDistribution is a test for https://github.com/nspcc-dev/neo-go/pull/2181.
|
||||
func TestNEO_RecursiveGASMint(t *testing.T) {
|
||||
neoCommitteeInvoker := newNeoCommitteeClient(t, 100_0000_0000)
|
||||
neoValidatorInvoker := neoCommitteeInvoker.WithSigners(neoCommitteeInvoker.Validator)
|
||||
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")
|
||||
e.DeployContract(t, c, nil)
|
||||
|
||||
gasValidatorInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(), c.Hash, int64(2_0000_0000), nil)
|
||||
|
||||
// Transfer 10 NEO to test contract, the contract should earn some GAS by owning this NEO.
|
||||
neoValidatorInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(), c.Hash, int64(10), nil)
|
||||
|
||||
// Add blocks to be able to trigger NEO transfer from contract address to owner
|
||||
// address inside onNEP17Payment (the contract starts NEO transfers from chain height = 100).
|
||||
for i := e.Chain.BlockHeight(); i < 100; i++ {
|
||||
e.AddNewBlock(t)
|
||||
}
|
||||
|
||||
// Transfer 1 more NEO to the contract. Transfer will trigger onNEP17Payment. OnNEP17Payment will
|
||||
// trigger transfer of 11 NEO to the contract owner (based on the contract code). 11 NEO Transfer will
|
||||
// trigger GAS distribution. GAS transfer will trigger OnNEP17Payment one more time. The recursion
|
||||
// shouldn't occur here, because contract's balance LastUpdated height has already been updated in
|
||||
// this block.
|
||||
neoValidatorInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(), c.Hash, int64(1), nil)
|
||||
}
|
||||
|
||||
func TestNEO_GetAccountState(t *testing.T) {
|
||||
neoValidatorInvoker := newNeoValidatorsClient(t)
|
||||
e := neoValidatorInvoker.Executor
|
||||
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
neoValidatorInvoker.Invoke(t, stackitem.Null{}, "getAccountState", util.Uint160{})
|
||||
})
|
||||
|
||||
t.Run("with funds", func(t *testing.T) {
|
||||
amount := int64(1)
|
||||
acc := e.NewAccount(t)
|
||||
neoValidatorInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(), acc.ScriptHash(), amount, nil)
|
||||
lub := e.Chain.BlockHeight()
|
||||
neoValidatorInvoker.Invoke(t, stackitem.NewStruct([]stackitem.Item{
|
||||
stackitem.Make(amount),
|
||||
stackitem.Make(lub),
|
||||
stackitem.Null{},
|
||||
}), "getAccountState", acc.ScriptHash())
|
||||
})
|
||||
}
|
||||
|
||||
func TestNEO_CommitteeBountyOnPersist(t *testing.T) {
|
||||
neoCommitteeInvoker := newNeoCommitteeClient(t, 0)
|
||||
e := neoCommitteeInvoker.Executor
|
||||
|
||||
hs := e.Chain.GetStandByCommittee()
|
||||
committeeSize := len(hs)
|
||||
|
||||
const singleBounty = 50000000
|
||||
bs := map[int]int64{0: singleBounty}
|
||||
checkBalances := func() {
|
||||
for i := 0; i < committeeSize; i++ {
|
||||
require.EqualValues(t, bs[i], e.Chain.GetUtilityTokenBalance(hs[i].GetScriptHash()).Int64(), i)
|
||||
}
|
||||
}
|
||||
for i := 0; i < committeeSize*2; i++ {
|
||||
e.AddNewBlock(t)
|
||||
bs[(i+1)%committeeSize] += singleBounty
|
||||
checkBalances()
|
||||
}
|
||||
}
|
||||
|
||||
func TestNEO_TransferOnPayment(t *testing.T) {
|
||||
neoValidatorsInvoker := newNeoValidatorsClient(t)
|
||||
e := neoValidatorsInvoker.Executor
|
||||
managementValidatorsInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Management))
|
||||
|
||||
cs, _ := getTestContractState(t, 1, 2, e.CommitteeHash)
|
||||
cs.Hash = state.CreateContractHash(e.Validator.ScriptHash(), cs.NEF.Checksum, cs.Manifest.Name) // set proper hash
|
||||
manifB, err := json.Marshal(cs.Manifest)
|
||||
require.NoError(t, err)
|
||||
nefB, err := cs.NEF.Bytes()
|
||||
require.NoError(t, err)
|
||||
si, err := cs.ToStackItem()
|
||||
require.NoError(t, err)
|
||||
managementValidatorsInvoker.Invoke(t, si, "deploy", nefB, manifB)
|
||||
|
||||
const amount int64 = 2
|
||||
|
||||
h := neoValidatorsInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(), cs.Hash, amount, nil)
|
||||
aer := e.GetTxExecResult(t, h)
|
||||
require.Equal(t, 3, len(aer.Events)) // transfer + GAS claim for sender + onPayment
|
||||
e.CheckTxNotificationEvent(t, h, 2, state.NotificationEvent{
|
||||
ScriptHash: cs.Hash,
|
||||
Name: "LastPayment",
|
||||
Item: stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.NewByteArray(neoValidatorsInvoker.Hash.BytesBE()),
|
||||
stackitem.NewByteArray(e.Validator.ScriptHash().BytesBE()),
|
||||
stackitem.NewBigInteger(big.NewInt(amount)),
|
||||
stackitem.Null{},
|
||||
}),
|
||||
})
|
||||
|
||||
h = neoValidatorsInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(), cs.Hash, amount, nil)
|
||||
aer = e.GetTxExecResult(t, h)
|
||||
require.Equal(t, 5, len(aer.Events)) // Now we must also have GAS claim for contract and corresponding `onPayment`.
|
||||
e.CheckTxNotificationEvent(t, h, 2, state.NotificationEvent{ // onPayment for GAS claim
|
||||
ScriptHash: cs.Hash,
|
||||
Name: "LastPayment",
|
||||
Item: stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.NewByteArray(e.NativeHash(t, nativenames.Gas).BytesBE()),
|
||||
stackitem.Null{},
|
||||
stackitem.NewBigInteger(big.NewInt(1)),
|
||||
stackitem.Null{},
|
||||
}),
|
||||
})
|
||||
e.CheckTxNotificationEvent(t, h, 4, state.NotificationEvent{ // onPayment for NEO transfer
|
||||
ScriptHash: cs.Hash,
|
||||
Name: "LastPayment",
|
||||
Item: stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.NewByteArray(e.NativeHash(t, nativenames.Neo).BytesBE()),
|
||||
stackitem.NewByteArray(e.Validator.ScriptHash().BytesBE()),
|
||||
stackitem.NewBigInteger(big.NewInt(amount)),
|
||||
stackitem.Null{},
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
func TestNEO_Roundtrip(t *testing.T) {
|
||||
neoValidatorsInvoker := newNeoValidatorsClient(t)
|
||||
e := neoValidatorsInvoker.Executor
|
||||
validatorH := neoValidatorsInvoker.Validator.ScriptHash()
|
||||
|
||||
initialBalance, initialHeight := e.Chain.GetGoverningTokenBalance(validatorH)
|
||||
require.NotNil(t, initialBalance)
|
||||
|
||||
t.Run("bad: amount > initial balance", func(t *testing.T) {
|
||||
h := neoValidatorsInvoker.Invoke(t, false, "transfer", validatorH, validatorH, initialBalance.Int64()+1, nil)
|
||||
aer, err := e.Chain.GetAppExecResults(h, trigger.Application)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(aer[0].Events)) // failed transfer => no events
|
||||
// check balance and height were not changed
|
||||
updatedBalance, updatedHeight := e.Chain.GetGoverningTokenBalance(validatorH)
|
||||
require.Equal(t, initialBalance, updatedBalance)
|
||||
require.Equal(t, initialHeight, updatedHeight)
|
||||
})
|
||||
|
||||
t.Run("good: amount == initial balance", func(t *testing.T) {
|
||||
h := neoValidatorsInvoker.Invoke(t, true, "transfer", validatorH, validatorH, initialBalance.Int64(), nil)
|
||||
aer, err := e.Chain.GetAppExecResults(h, trigger.Application)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(aer[0].Events)) // roundtrip + GAS claim
|
||||
// check balance wasn't changed and height was updated
|
||||
updatedBalance, updatedHeight := e.Chain.GetGoverningTokenBalance(validatorH)
|
||||
require.Equal(t, initialBalance, updatedBalance)
|
||||
require.Equal(t, e.Chain.BlockHeight(), updatedHeight)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNEO_TransferZeroWithZeroBalance(t *testing.T) {
|
||||
neoValidatorsInvoker := newNeoValidatorsClient(t)
|
||||
e := neoValidatorsInvoker.Executor
|
||||
|
||||
check := func(t *testing.T, roundtrip bool) {
|
||||
acc := neoValidatorsInvoker.WithSigners(e.NewAccount(t))
|
||||
accH := acc.Signers[0].ScriptHash()
|
||||
to := accH
|
||||
if !roundtrip {
|
||||
to = random.Uint160()
|
||||
}
|
||||
h := acc.Invoke(t, true, "transfer", accH, to, int64(0), nil)
|
||||
aer, err := e.Chain.GetAppExecResults(h, trigger.Application)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(aer[0].Events)) // roundtrip/transfer only, no GAS claim
|
||||
require.Equal(t, stackitem.NewBigInteger(big.NewInt(0)), aer[0].Events[0].Item.Value().([]stackitem.Item)[2]) // amount is 0
|
||||
// check balance wasn't changed and height was updated
|
||||
updatedBalance, updatedHeight := e.Chain.GetGoverningTokenBalance(accH)
|
||||
require.Equal(t, int64(0), updatedBalance.Int64())
|
||||
require.Equal(t, uint32(0), updatedHeight)
|
||||
}
|
||||
t.Run("roundtrip: amount == initial balance == 0", func(t *testing.T) {
|
||||
check(t, true)
|
||||
})
|
||||
t.Run("non-roundtrip: amount == initial balance == 0", func(t *testing.T) {
|
||||
check(t, false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNEO_TransferZeroWithNonZeroBalance(t *testing.T) {
|
||||
neoValidatorsInvoker := newNeoValidatorsClient(t)
|
||||
e := neoValidatorsInvoker.Executor
|
||||
|
||||
check := func(t *testing.T, roundtrip bool) {
|
||||
acc := e.NewAccount(t)
|
||||
neoValidatorsInvoker.Invoke(t, true, "transfer", neoValidatorsInvoker.Validator.ScriptHash(), acc.ScriptHash(), int64(100), nil)
|
||||
neoAccInvoker := neoValidatorsInvoker.WithSigners(acc)
|
||||
initialBalance, _ := e.Chain.GetGoverningTokenBalance(acc.ScriptHash())
|
||||
require.True(t, initialBalance.Sign() > 0)
|
||||
to := acc.ScriptHash()
|
||||
if !roundtrip {
|
||||
to = random.Uint160()
|
||||
}
|
||||
h := neoAccInvoker.Invoke(t, true, "transfer", acc.ScriptHash(), to, int64(0), nil)
|
||||
|
||||
aer, err := e.Chain.GetAppExecResults(h, trigger.Application)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(aer[0].Events)) // roundtrip + GAS claim
|
||||
require.Equal(t, stackitem.NewBigInteger(big.NewInt(0)), aer[0].Events[1].Item.Value().([]stackitem.Item)[2]) // amount is 0
|
||||
// check balance wasn't changed and height was updated
|
||||
updatedBalance, updatedHeight := e.Chain.GetGoverningTokenBalance(acc.ScriptHash())
|
||||
require.Equal(t, initialBalance, updatedBalance)
|
||||
require.Equal(t, e.Chain.BlockHeight(), updatedHeight)
|
||||
}
|
||||
t.Run("roundtrip", func(t *testing.T) {
|
||||
check(t, true)
|
||||
})
|
||||
t.Run("non-roundtrip", func(t *testing.T) {
|
||||
check(t, false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNEO_CalculateBonus(t *testing.T) {
|
||||
neoCommitteeInvoker := newNeoCommitteeClient(t, 10_0000_0000)
|
||||
e := neoCommitteeInvoker.Executor
|
||||
neoValidatorsInvoker := neoCommitteeInvoker.WithSigners(e.Validator)
|
||||
|
||||
acc := neoValidatorsInvoker.WithSigners(e.NewAccount(t))
|
||||
accH := acc.Signers[0].ScriptHash()
|
||||
rewardDistance := 10
|
||||
|
||||
t.Run("Zero", func(t *testing.T) {
|
||||
initialGASBalance := e.Chain.GetUtilityTokenBalance(accH)
|
||||
for i := 0; i < rewardDistance; i++ {
|
||||
e.AddNewBlock(t)
|
||||
}
|
||||
// Claim GAS, but there's no NEO on the account, so no GAS should be earned.
|
||||
h := acc.Invoke(t, true, "transfer", accH, accH, 0, nil)
|
||||
claimTx, _ := e.GetTransaction(t, h)
|
||||
|
||||
e.CheckGASBalance(t, accH, big.NewInt(initialGASBalance.Int64()-claimTx.SystemFee-claimTx.NetworkFee))
|
||||
})
|
||||
|
||||
t.Run("Many blocks", func(t *testing.T) {
|
||||
amount := 100
|
||||
defaultGASParBlock := 5
|
||||
newGASPerBlock := 1
|
||||
|
||||
initialGASBalance := e.Chain.GetUtilityTokenBalance(accH)
|
||||
|
||||
// Five blocks of NEO owning with default GasPerBlockValue.
|
||||
neoValidatorsInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(), accH, amount, nil)
|
||||
for i := 0; i < rewardDistance/2-2; i++ {
|
||||
e.AddNewBlock(t)
|
||||
}
|
||||
neoCommitteeInvoker.Invoke(t, stackitem.Null{}, "setGasPerBlock", newGASPerBlock*native.GASFactor)
|
||||
|
||||
// Five blocks more with modified GasPerBlock value.
|
||||
for i := 0; i < rewardDistance/2; i++ {
|
||||
e.AddNewBlock(t)
|
||||
}
|
||||
|
||||
// GAS claim for the last 10 blocks of NEO owning.
|
||||
h := acc.Invoke(t, true, "transfer", accH, accH, amount, nil)
|
||||
claimTx, _ := e.GetTransaction(t, h)
|
||||
|
||||
firstPart := int64(amount*rewardDistance/2*defaultGASParBlock) / int64(rewardDistance)
|
||||
secondPart := int64(amount*rewardDistance/2*newGASPerBlock) / int64(rewardDistance)
|
||||
e.CheckGASBalance(t, accH, big.NewInt(initialGASBalance.Int64()-
|
||||
claimTx.SystemFee-claimTx.NetworkFee + +firstPart + secondPart))
|
||||
})
|
||||
}
|
|
@ -2,483 +2,23 @@ package core
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/internal/random"
|
||||
"github.com/nspcc-dev/neo-go/internal/testchain"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||
"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/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/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/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func setSigner(tx *transaction.Transaction, h util.Uint160) {
|
||||
tx.Signers = []transaction.Signer{{
|
||||
Account: h,
|
||||
Scopes: transaction.Global,
|
||||
}}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func TestNEO_Vote(t *testing.T) {
|
||||
bc := newTestChain(t)
|
||||
|
||||
neo := bc.contracts.NEO
|
||||
tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
|
||||
ic := bc.newInteropContext(trigger.Application, bc.dao, nil, tx)
|
||||
ic.SpawnVM()
|
||||
ic.Block = bc.newBlock(tx)
|
||||
|
||||
freq := testchain.ValidatorsCount + testchain.CommitteeSize()
|
||||
advanceChain := func(t *testing.T) {
|
||||
for i := 0; i < freq; i++ {
|
||||
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||
ic.Block.Index++
|
||||
}
|
||||
}
|
||||
|
||||
standBySorted := bc.GetStandByValidators()
|
||||
sort.Sort(standBySorted)
|
||||
pubs, err := neo.ComputeNextBlockValidators(bc, ic.DAO)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, standBySorted, pubs)
|
||||
|
||||
sz := testchain.CommitteeSize()
|
||||
accs := make([]*wallet.Account, sz)
|
||||
candidates := make(keys.PublicKeys, sz)
|
||||
txs := make([]*transaction.Transaction, 0, len(accs))
|
||||
for i := 0; i < sz; i++ {
|
||||
priv, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
candidates[i] = priv.PublicKey()
|
||||
accs[i], err = wallet.NewAccount()
|
||||
require.NoError(t, err)
|
||||
if i > 0 {
|
||||
require.NoError(t, neo.RegisterCandidateInternal(ic, candidates[i]))
|
||||
}
|
||||
|
||||
to := accs[i].Contract.ScriptHash()
|
||||
w := io.NewBufBinWriter()
|
||||
emit.AppCall(w.BinWriter, bc.contracts.NEO.Hash, "transfer", callflag.All,
|
||||
neoOwner.BytesBE(), to.BytesBE(),
|
||||
big.NewInt(int64(sz-i)*1000000).Int64(), nil)
|
||||
emit.Opcodes(w.BinWriter, opcode.ASSERT)
|
||||
emit.AppCall(w.BinWriter, bc.contracts.GAS.Hash, "transfer", callflag.All,
|
||||
neoOwner.BytesBE(), to.BytesBE(),
|
||||
int64(1_000_000_000), nil)
|
||||
emit.Opcodes(w.BinWriter, opcode.ASSERT)
|
||||
require.NoError(t, w.Err)
|
||||
tx := transaction.New(w.Bytes(), 1000_000_000)
|
||||
tx.ValidUntilBlock = bc.BlockHeight() + 1
|
||||
setSigner(tx, testchain.MultisigScriptHash())
|
||||
require.NoError(t, testchain.SignTx(bc, tx))
|
||||
txs = append(txs, tx)
|
||||
}
|
||||
require.NoError(t, bc.AddBlock(bc.newBlock(txs...)))
|
||||
for _, tx := range txs {
|
||||
checkTxHalt(t, bc, tx.Hash())
|
||||
}
|
||||
transferBlock := bc.BlockHeight()
|
||||
|
||||
for i := 1; i < sz; i++ {
|
||||
priv := accs[i].PrivateKey()
|
||||
h := priv.GetScriptHash()
|
||||
setSigner(tx, h)
|
||||
ic.VM.Load(priv.PublicKey().GetVerificationScript())
|
||||
require.NoError(t, neo.VoteInternal(ic, h, candidates[i]))
|
||||
}
|
||||
|
||||
// We still haven't voted enough validators in.
|
||||
pubs, err = neo.ComputeNextBlockValidators(bc, ic.DAO)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, standBySorted, pubs)
|
||||
|
||||
advanceChain(t)
|
||||
pubs = neo.GetNextBlockValidatorsInternal()
|
||||
require.EqualValues(t, standBySorted, pubs)
|
||||
|
||||
// Register and give some value to the last validator.
|
||||
require.NoError(t, neo.RegisterCandidateInternal(ic, candidates[0]))
|
||||
priv := accs[0].PrivateKey()
|
||||
h := priv.GetScriptHash()
|
||||
setSigner(tx, h)
|
||||
ic.VM.Load(priv.PublicKey().GetVerificationScript())
|
||||
require.NoError(t, neo.VoteInternal(ic, h, candidates[0]))
|
||||
|
||||
_, err = ic.DAO.Persist()
|
||||
require.NoError(t, err)
|
||||
advanceChain(t)
|
||||
pubs, err = neo.ComputeNextBlockValidators(bc, ic.DAO)
|
||||
require.NoError(t, err)
|
||||
sortedCandidates := candidates.Copy()[:testchain.Size()]
|
||||
sort.Sort(sortedCandidates)
|
||||
require.EqualValues(t, sortedCandidates, pubs)
|
||||
|
||||
pubs = neo.GetNextBlockValidatorsInternal()
|
||||
require.EqualValues(t, sortedCandidates, pubs)
|
||||
|
||||
t.Run("check voter rewards", func(t *testing.T) {
|
||||
gasBalance := make([]*big.Int, len(accs))
|
||||
neoBalance := make([]*big.Int, len(accs))
|
||||
txs := make([]*transaction.Transaction, 0, len(accs))
|
||||
for i := range accs {
|
||||
w := io.NewBufBinWriter()
|
||||
h := accs[i].PrivateKey().GetScriptHash()
|
||||
gasBalance[i] = bc.GetUtilityTokenBalance(h)
|
||||
neoBalance[i], _ = bc.GetGoverningTokenBalance(h)
|
||||
emit.AppCall(w.BinWriter, bc.contracts.NEO.Hash, "transfer", callflag.All,
|
||||
h.BytesBE(), h.BytesBE(), int64(1), nil)
|
||||
emit.Opcodes(w.BinWriter, opcode.ASSERT)
|
||||
require.NoError(t, w.Err)
|
||||
tx := transaction.New(w.Bytes(), 0)
|
||||
tx.ValidUntilBlock = bc.BlockHeight() + 1
|
||||
tx.NetworkFee = 2_000_000
|
||||
tx.SystemFee = 11_000_000
|
||||
setSigner(tx, h)
|
||||
require.NoError(t, accs[i].SignTx(netmode.UnitTestNet, tx))
|
||||
txs = append(txs, tx)
|
||||
}
|
||||
require.NoError(t, bc.AddBlock(bc.newBlock(txs...)))
|
||||
for _, tx := range txs {
|
||||
checkTxHalt(t, bc, tx.Hash())
|
||||
}
|
||||
|
||||
// GAS increase consists of 2 parts: NEO holding + voting for committee nodes.
|
||||
// Here we check that 2-nd part exists and is proportional to the amount of NEO given.
|
||||
for i := range accs {
|
||||
newGAS := bc.GetUtilityTokenBalance(accs[i].Contract.ScriptHash())
|
||||
newGAS.Sub(newGAS, gasBalance[i])
|
||||
|
||||
gasForHold, err := bc.contracts.NEO.CalculateNEOHolderReward(bc.dao, neoBalance[i], transferBlock, bc.BlockHeight())
|
||||
require.NoError(t, err)
|
||||
newGAS.Sub(newGAS, gasForHold)
|
||||
require.True(t, newGAS.Sign() > 0)
|
||||
gasBalance[i] = newGAS
|
||||
}
|
||||
// First account voted later than the others.
|
||||
require.Equal(t, -1, gasBalance[0].Cmp(gasBalance[1]))
|
||||
for i := 2; i < testchain.ValidatorsCount; i++ {
|
||||
require.Equal(t, 0, gasBalance[i].Cmp(gasBalance[1]))
|
||||
}
|
||||
require.Equal(t, 1, gasBalance[1].Cmp(gasBalance[testchain.ValidatorsCount]))
|
||||
for i := testchain.ValidatorsCount; i < testchain.CommitteeSize(); i++ {
|
||||
require.Equal(t, 0, gasBalance[i].Cmp(gasBalance[testchain.ValidatorsCount]))
|
||||
}
|
||||
})
|
||||
|
||||
require.NoError(t, neo.UnregisterCandidateInternal(ic, candidates[0]))
|
||||
require.Error(t, neo.VoteInternal(ic, h, candidates[0]))
|
||||
advanceChain(t)
|
||||
|
||||
pubs, err = neo.ComputeNextBlockValidators(bc, ic.DAO)
|
||||
require.NoError(t, err)
|
||||
for i := range pubs {
|
||||
require.NotEqual(t, candidates[0], pubs[i])
|
||||
}
|
||||
}
|
||||
|
||||
// TestNEO_RecursiveDistribution is a test for https://github.com/nspcc-dev/neo-go/pull/2181.
|
||||
func TestNEO_RecursiveGASMint(t *testing.T) {
|
||||
bc := newTestChain(t)
|
||||
initBasicChain(t, bc)
|
||||
|
||||
contractHash, err := bc.GetContractScriptHash(1) // deployed rpc/server/testdata/test_contract.go contract
|
||||
require.NoError(t, err)
|
||||
tx := transferTokenFromMultisigAccount(t, bc, contractHash, bc.contracts.GAS.Hash, 2_0000_0000)
|
||||
checkTxHalt(t, bc, tx.Hash())
|
||||
|
||||
// Transfer 10 NEO to test contract, the contract should earn some GAS by owning this NEO.
|
||||
tx = transferTokenFromMultisigAccount(t, bc, contractHash, bc.contracts.NEO.Hash, 10)
|
||||
res, err := bc.GetAppExecResults(tx.Hash(), trigger.Application)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, vm.HaltState, res[0].VMState)
|
||||
|
||||
// Add blocks to be able to trigger NEO transfer from contract address to owner
|
||||
// address inside onNEP17Payment (the contract starts NEO transfers from chain height = 100).
|
||||
for i := bc.BlockHeight(); i < 100; i++ {
|
||||
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||
}
|
||||
|
||||
// Transfer 1 more NEO to the contract. Transfer will trigger onNEP17Payment. OnNEP17Payment will
|
||||
// trigger transfer of 11 NEO to the contract owner (based on the contract code). 11 NEO Transfer will
|
||||
// trigger GAS distribution. GAS transfer will trigger OnNEP17Payment one more time. The recursion
|
||||
// shouldn't occur here, because contract's balance LastUpdated height has already been updated in
|
||||
// this block.
|
||||
tx = transferTokenFromMultisigAccount(t, bc, contractHash, bc.contracts.NEO.Hash, 1)
|
||||
res, err = bc.GetAppExecResults(tx.Hash(), trigger.Application)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, vm.HaltState, res[0].VMState, res[0].FaultException)
|
||||
}
|
||||
|
||||
func TestNEO_SetGasPerBlock(t *testing.T) {
|
||||
bc := newTestChain(t)
|
||||
|
||||
testGetSet(t, bc, bc.contracts.NEO.Hash, "GasPerBlock",
|
||||
5*native.GASFactor, 0, 10*native.GASFactor)
|
||||
}
|
||||
|
||||
func TestNEO_CalculateBonus(t *testing.T) {
|
||||
bc := newTestChain(t)
|
||||
|
||||
neo := bc.contracts.NEO
|
||||
tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
|
||||
ic := bc.newInteropContext(trigger.Application, bc.dao, nil, tx)
|
||||
ic.SpawnVM()
|
||||
ic.VM.LoadScript([]byte{byte(opcode.RET)})
|
||||
t.Run("Invalid", func(t *testing.T) {
|
||||
_, err := neo.CalculateNEOHolderReward(ic.DAO, new(big.Int).SetInt64(-1), 0, 1)
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("Zero", func(t *testing.T) {
|
||||
res, err := neo.CalculateNEOHolderReward(ic.DAO, big.NewInt(0), 0, 100)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 0, res.Int64())
|
||||
})
|
||||
t.Run("ManyBlocks", func(t *testing.T) {
|
||||
setSigner(tx, neo.GetCommitteeAddress())
|
||||
err := neo.SetGASPerBlock(ic, 10, big.NewInt(1*native.GASFactor))
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := neo.CalculateNEOHolderReward(ic.DAO, big.NewInt(100), 5, 15)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, (100*5*5/10)+(100*5*1/10), res.Int64())
|
||||
})
|
||||
}
|
||||
|
||||
func TestNEO_GetAccountState(t *testing.T) {
|
||||
bc := newTestChain(t)
|
||||
|
||||
acc, err := wallet.NewAccount()
|
||||
require.NoError(t, err)
|
||||
|
||||
h := acc.Contract.ScriptHash()
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
res, err := invokeContractMethod(bc, 1_0000000, bc.contracts.NEO.Hash, "getAccountState", h)
|
||||
require.NoError(t, err)
|
||||
checkResult(t, res, stackitem.Null{})
|
||||
})
|
||||
|
||||
const amount = 123
|
||||
transferTokenFromMultisigAccountCheckOK(t, bc, h, bc.GoverningTokenHash(), int64(amount))
|
||||
|
||||
t.Run("with funds", func(t *testing.T) {
|
||||
bs := stackitem.NewStruct([]stackitem.Item{
|
||||
stackitem.Make(123),
|
||||
stackitem.Make(bc.BlockHeight()),
|
||||
stackitem.Null{},
|
||||
})
|
||||
res, err := invokeContractMethod(bc, 1_0000000, bc.contracts.NEO.Hash, "getAccountState", h)
|
||||
require.NoError(t, err)
|
||||
checkResult(t, res, bs)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNEO_CommitteeBountyOnPersist(t *testing.T) {
|
||||
bc := newTestChain(t)
|
||||
|
||||
hs := make([]util.Uint160, testchain.CommitteeSize())
|
||||
for i := range hs {
|
||||
hs[i] = testchain.PrivateKeyByID(i).GetScriptHash()
|
||||
}
|
||||
|
||||
const singleBounty = 50000000
|
||||
bs := map[int]int64{0: singleBounty}
|
||||
checkBalances := func() {
|
||||
for i := 0; i < testchain.CommitteeSize(); i++ {
|
||||
require.EqualValues(t, bs[i], bc.GetUtilityTokenBalance(hs[i]).Int64(), i)
|
||||
}
|
||||
}
|
||||
for i := 0; i < testchain.CommitteeSize()*2; i++ {
|
||||
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||
bs[(i+1)%testchain.CommitteeSize()] += singleBounty
|
||||
checkBalances()
|
||||
}
|
||||
}
|
||||
|
||||
func TestNEO_TransferOnPayment(t *testing.T) {
|
||||
bc := newTestChain(t)
|
||||
|
||||
cs, _ := getTestContractState(t, 4, 5, random.Uint160()) // sender and IDs are not important for the test
|
||||
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
|
||||
|
||||
const amount = 2
|
||||
tx := transferTokenFromMultisigAccount(t, bc, cs.Hash, bc.contracts.NEO.Hash, amount)
|
||||
aer, err := bc.GetAppExecResults(tx.Hash(), trigger.Application)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, vm.HaltState, aer[0].VMState)
|
||||
require.Len(t, aer[0].Events, 3) // transfer + GAS claim for sender + onPayment
|
||||
|
||||
e := aer[0].Events[2]
|
||||
require.Equal(t, "LastPayment", e.Name)
|
||||
arr := e.Item.Value().([]stackitem.Item)
|
||||
require.Equal(t, bc.contracts.NEO.Hash.BytesBE(), arr[0].Value())
|
||||
require.Equal(t, neoOwner.BytesBE(), arr[1].Value())
|
||||
require.Equal(t, big.NewInt(amount), arr[2].Value())
|
||||
|
||||
tx = transferTokenFromMultisigAccount(t, bc, cs.Hash, bc.contracts.NEO.Hash, amount)
|
||||
aer, err = bc.GetAppExecResults(tx.Hash(), trigger.Application)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, vm.HaltState, aer[0].VMState)
|
||||
// Now we must also have GAS claim for contract and corresponding `onPayment`.
|
||||
require.Len(t, aer[0].Events, 5)
|
||||
|
||||
e = aer[0].Events[2] // onPayment for GAS claim
|
||||
require.Equal(t, "LastPayment", e.Name)
|
||||
arr = e.Item.Value().([]stackitem.Item)
|
||||
require.Equal(t, stackitem.Null{}, arr[1])
|
||||
require.Equal(t, bc.contracts.GAS.Hash.BytesBE(), arr[0].Value())
|
||||
|
||||
e = aer[0].Events[4] // onPayment for NEO transfer
|
||||
require.Equal(t, "LastPayment", e.Name)
|
||||
arr = e.Item.Value().([]stackitem.Item)
|
||||
require.Equal(t, bc.contracts.NEO.Hash.BytesBE(), arr[0].Value())
|
||||
require.Equal(t, neoOwner.BytesBE(), arr[1].Value())
|
||||
require.Equal(t, big.NewInt(amount), arr[2].Value())
|
||||
}
|
||||
|
||||
func TestRegisterPrice(t *testing.T) {
|
||||
bc := newTestChain(t)
|
||||
testGetSet(t, bc, bc.contracts.NEO.Hash, "RegisterPrice",
|
||||
native.DefaultRegisterPrice, 1, math.MaxInt64)
|
||||
}
|
||||
|
||||
func TestNEO_Roundtrip(t *testing.T) {
|
||||
bc := newTestChain(t)
|
||||
initialBalance, initialHeight := bc.GetGoverningTokenBalance(neoOwner)
|
||||
require.NotNil(t, initialBalance)
|
||||
|
||||
t.Run("bad: amount > initial balance", func(t *testing.T) {
|
||||
tx := transferTokenFromMultisigAccountWithAssert(t, bc, neoOwner, bc.contracts.NEO.Hash, initialBalance.Int64()+1, false)
|
||||
aer, err := bc.GetAppExecResults(tx.Hash(), trigger.Application)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, vm.HaltState, aer[0].VMState, aer[0].FaultException) // transfer without assert => HALT state
|
||||
checkResult(t, &aer[0], stackitem.NewBool(false))
|
||||
require.Len(t, aer[0].Events, 0) // failed transfer => no events
|
||||
// check balance and height were not changed
|
||||
updatedBalance, updatedHeight := bc.GetGoverningTokenBalance(neoOwner)
|
||||
require.Equal(t, initialBalance, updatedBalance)
|
||||
require.Equal(t, initialHeight, updatedHeight)
|
||||
})
|
||||
|
||||
t.Run("good: amount == initial balance", func(t *testing.T) {
|
||||
tx := transferTokenFromMultisigAccountWithAssert(t, bc, neoOwner, bc.contracts.NEO.Hash, initialBalance.Int64(), false)
|
||||
aer, err := bc.GetAppExecResults(tx.Hash(), trigger.Application)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, vm.HaltState, aer[0].VMState, aer[0].FaultException)
|
||||
checkResult(t, &aer[0], stackitem.NewBool(true))
|
||||
require.Len(t, aer[0].Events, 2) // roundtrip + GAS claim
|
||||
// check balance wasn't changed and height was updated
|
||||
updatedBalance, updatedHeight := bc.GetGoverningTokenBalance(neoOwner)
|
||||
require.Equal(t, initialBalance, updatedBalance)
|
||||
require.Equal(t, bc.BlockHeight(), updatedHeight)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNEO_TransferZeroWithZeroBalance(t *testing.T) {
|
||||
bc := newTestChain(t)
|
||||
|
||||
check := func(t *testing.T, roundtrip bool) {
|
||||
acc := newAccountWithGAS(t, bc)
|
||||
from := acc.PrivateKey().GetScriptHash()
|
||||
to := from
|
||||
if !roundtrip {
|
||||
to = random.Uint160()
|
||||
}
|
||||
transferTx := newNEP17TransferWithAssert(bc.contracts.NEO.Hash, acc.PrivateKey().GetScriptHash(), to, 0, true)
|
||||
transferTx.SystemFee = 100000000
|
||||
transferTx.NetworkFee = 10000000
|
||||
transferTx.ValidUntilBlock = bc.BlockHeight() + 1
|
||||
addSigners(acc.PrivateKey().GetScriptHash(), transferTx)
|
||||
require.NoError(t, acc.SignTx(bc.config.Magic, transferTx))
|
||||
b := bc.newBlock(transferTx)
|
||||
require.NoError(t, bc.AddBlock(b))
|
||||
|
||||
aer, err := bc.GetAppExecResults(transferTx.Hash(), trigger.Application)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, vm.HaltState, aer[0].VMState, aer[0].FaultException)
|
||||
require.Len(t, aer[0].Events, 1) // roundtrip only, no GAS claim
|
||||
require.Equal(t, stackitem.NewBigInteger(big.NewInt(0)), aer[0].Events[0].Item.Value().([]stackitem.Item)[2]) // amount is 0
|
||||
// check balance wasn't changed and height wasn't updated
|
||||
updatedBalance, updatedHeight := bc.GetGoverningTokenBalance(acc.PrivateKey().GetScriptHash())
|
||||
require.Equal(t, big.NewInt(0), updatedBalance)
|
||||
require.Equal(t, uint32(0), updatedHeight)
|
||||
}
|
||||
t.Run("roundtrip: amount == initial balance == 0", func(t *testing.T) {
|
||||
check(t, true)
|
||||
})
|
||||
t.Run("non-roundtrip: amount == initial balance == 0", func(t *testing.T) {
|
||||
check(t, false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNEO_TransferZeroWithNonZeroBalance(t *testing.T) {
|
||||
bc := newTestChain(t)
|
||||
|
||||
check := func(t *testing.T, roundtrip bool) {
|
||||
acc := newAccountWithGAS(t, bc)
|
||||
transferTokenFromMultisigAccount(t, bc, acc.PrivateKey().GetScriptHash(), bc.contracts.NEO.Hash, 100)
|
||||
initialBalance, _ := bc.GetGoverningTokenBalance(acc.PrivateKey().GetScriptHash())
|
||||
require.True(t, initialBalance.Sign() > 0)
|
||||
|
||||
from := acc.PrivateKey().GetScriptHash()
|
||||
to := from
|
||||
if !roundtrip {
|
||||
to = random.Uint160()
|
||||
}
|
||||
transferTx := newNEP17TransferWithAssert(bc.contracts.NEO.Hash, acc.PrivateKey().GetScriptHash(), to, 0, true)
|
||||
transferTx.SystemFee = 100000000
|
||||
transferTx.NetworkFee = 10000000
|
||||
transferTx.ValidUntilBlock = bc.BlockHeight() + 1
|
||||
addSigners(acc.PrivateKey().GetScriptHash(), transferTx)
|
||||
require.NoError(t, acc.SignTx(bc.config.Magic, transferTx))
|
||||
b := bc.newBlock(transferTx)
|
||||
require.NoError(t, bc.AddBlock(b))
|
||||
|
||||
aer, err := bc.GetAppExecResults(transferTx.Hash(), trigger.Application)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, vm.HaltState, aer[0].VMState, aer[0].FaultException)
|
||||
require.Len(t, aer[0].Events, 2) // roundtrip + GAS claim
|
||||
require.Equal(t, stackitem.NewBigInteger(big.NewInt(0)), aer[0].Events[1].Item.Value().([]stackitem.Item)[2]) // amount is 0
|
||||
// check balance wasn't changed and height was updated
|
||||
updatedBalance, updatedHeight := bc.GetGoverningTokenBalance(acc.PrivateKey().GetScriptHash())
|
||||
require.Equal(t, initialBalance, updatedBalance)
|
||||
require.Equal(t, bc.BlockHeight(), updatedHeight)
|
||||
}
|
||||
t.Run("roundtrip", func(t *testing.T) {
|
||||
check(t, true)
|
||||
})
|
||||
t.Run("non-roundtrip", func(t *testing.T) {
|
||||
check(t, false)
|
||||
})
|
||||
}
|
||||
|
||||
func newAccountWithGAS(t *testing.T, bc *Blockchain) *wallet.Account {
|
||||
acc, err := wallet.NewAccount()
|
||||
require.NoError(t, err)
|
||||
transferTokenFromMultisigAccount(t, bc, acc.PrivateKey().GetScriptHash(), bc.contracts.GAS.Hash, 1000_00000000)
|
||||
return acc
|
||||
}
|
||||
|
||||
func BenchmarkNEO_GetGASPerVote(t *testing.B) {
|
||||
var stores = map[string]func(testing.TB) storage.Store{
|
||||
"MemPS": func(t testing.TB) storage.Store {
|
||||
|
|
Loading…
Reference in a new issue