2020-08-03 13:24:22 +00:00
|
|
|
package core
|
|
|
|
|
|
|
|
import (
|
2021-03-05 11:17:58 +00:00
|
|
|
"math"
|
2020-08-03 13:24:22 +00:00
|
|
|
"math/big"
|
2020-08-10 16:16:54 +00:00
|
|
|
"sort"
|
2020-08-03 13:24:22 +00:00
|
|
|
"testing"
|
|
|
|
|
core: allow transfer 0 GAS/NEO with zero balance
This commit fixes states diff at 131795 block of mainnet.
Transaction:
```
NEO-GO-VM > loadbase64 DAAQDBSPsxdYh6cITC3gUKI4oWmYxJs49gwUj7MXWIenCEwt4FCiOKFpmMSbOPYUwB8MCHRyYW5zZmVyDBT1Y+pAvCg9TQ4FxI6jBbPyoHNA70FifVtSOQwAEQwUj7MXWIenCEwt4FCiOKFpmMSbOPYMFL1Mb4Fqp6gHiEwzM6xSc8fLS+RpFMAfDAh0cmFuc2ZlcgwU9WPqQLwoPU0OBcSOowWz8qBzQO9BYn1bUjk=
READY: loaded 176 instructions
NEO-GO-VM 0 > ops
INDEX OPCODE PARAMETER
0 PUSHDATA1 ("") <<
2 PUSH0
3 PUSHDATA1 8fb3175887a7084c2de050a238a16998c49b38f6
25 PUSHDATA1 8fb3175887a7084c2de050a238a16998c49b38f6
47 PUSH4
48 PACK
49 PUSH15
50 PUSHDATA1 7472616e73666572 ("transfer")
60 PUSHDATA1 f563ea40bc283d4d0e05c48ea305b3f2a07340ef // NEO token
82 SYSCALL System.Contract.Call (627d5b52)
87 ASSERT
88 PUSHDATA1 ("")
90 PUSH1
91 PUSHDATA1 8fb3175887a7084c2de050a238a16998c49b38f6
113 PUSHDATA1 bd4c6f816aa7a807884c3333ac5273c7cb4be469
135 PUSH4
136 PACK
137 PUSH15
138 PUSHDATA1 7472616e73666572 ("transfer")
148 PUSHDATA1 f563ea40bc283d4d0e05c48ea305b3f2a07340ef // NEO token
170 SYSCALL System.Contract.Call (627d5b52)
175 ASSERT
```
Go's applog:
```
{
"id" : 1,
"result" : {
"txid" : "0x97d2ccb01467b22c73a2cb95f7af298f3a5bd8c849d7044371898b8efecdaabd",
"executions" : [
{
"exception" : "at instruction 87 (ASSERT): ASSERT failed",
"stack" : [],
"gasconsumed" : "4988995",
"notifications" : [],
"trigger" : "Application",
"vmstate" : "FAULT"
}
]
},
"jsonrpc" : "2.0"
}
```
C#'s applog:
```
{
"jsonrpc" : "2.0",
"result" : {
"executions" : [
{
"stack" : [],
"notifications" : [
{
"contract" : "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5",
"state" : {
"type" : "Array",
"value" : [
{
"type" : "ByteString",
"value" : "j7MXWIenCEwt4FCiOKFpmMSbOPY="
},
{
"type" : "ByteString",
"value" : "j7MXWIenCEwt4FCiOKFpmMSbOPY="
},
{
"value" : "0",
"type" : "Integer"
}
]
},
"eventname" : "Transfer"
},
{
"contract" : "0xd2a4cff31913016155e38e474a2c06d08be276cf",
"state" : {
"value" : [
{
"type" : "Any"
},
{
"type" : "ByteString",
"value" : "vUxvgWqnqAeITDMzrFJzx8tL5Gk="
},
{
"value" : "2490",
"type" : "Integer"
}
],
"type" : "Array"
},
"eventname" : "Transfer"
},
{
"contract" : "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5",
"state" : {
"value" : [
{
"value" : "vUxvgWqnqAeITDMzrFJzx8tL5Gk=",
"type" : "ByteString"
},
{
"value" : "j7MXWIenCEwt4FCiOKFpmMSbOPY=",
"type" : "ByteString"
},
{
"value" : "1",
"type" : "Integer"
}
],
"type" : "Array"
},
"eventname" : "Transfer"
}
],
"vmstate" : "HALT",
"gasconsumed" : "9977990",
"trigger" : "Application",
"exception" : null
}
],
"txid" : "0x97d2ccb01467b22c73a2cb95f7af298f3a5bd8c849d7044371898b8efecdaabd"
},
"id" : 1
}
```
2021-09-10 14:18:09 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/internal/random"
|
2020-11-23 11:09:00 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/internal/testchain"
|
2020-08-03 13:24:22 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
2020-08-26 10:06:19 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
2020-08-03 13:24:22 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
2020-11-03 15:08:58 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
2020-12-29 10:44:07 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
2020-08-03 13:24:22 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
2020-11-03 15:08:58 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
2020-08-26 13:16:57 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
2020-11-19 15:01:42 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
2020-11-03 15:08:58 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
2020-08-03 13:24:22 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
)
|
|
|
|
|
|
|
|
func setSigner(tx *transaction.Transaction, h util.Uint160) {
|
|
|
|
tx.Signers = []transaction.Signer{{
|
|
|
|
Account: h,
|
|
|
|
Scopes: transaction.Global,
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
2020-11-03 15:08:58 +00:00
|
|
|
func checkTxHalt(t *testing.T, bc *Blockchain, h util.Uint256) {
|
2020-11-11 15:43:28 +00:00
|
|
|
aer, err := bc.GetAppExecResults(h, trigger.Application)
|
2020-11-03 15:08:58 +00:00
|
|
|
require.NoError(t, err)
|
2020-11-11 15:43:28 +00:00
|
|
|
require.Equal(t, 1, len(aer))
|
|
|
|
require.Equal(t, vm.HaltState, aer[0].VMState, aer[0].FaultException)
|
2020-11-03 15:08:58 +00:00
|
|
|
}
|
|
|
|
|
2020-08-03 13:24:22 +00:00
|
|
|
func TestNEO_Vote(t *testing.T) {
|
|
|
|
bc := newTestChain(t)
|
|
|
|
|
|
|
|
neo := bc.contracts.NEO
|
2021-03-25 16:18:01 +00:00
|
|
|
tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
|
2020-10-29 16:14:49 +00:00
|
|
|
ic := bc.newInteropContext(trigger.Application, bc.dao, nil, tx)
|
2020-10-29 16:10:28 +00:00
|
|
|
ic.SpawnVM()
|
2020-08-26 13:16:57 +00:00
|
|
|
ic.Block = bc.newBlock(tx)
|
2020-08-03 13:24:22 +00:00
|
|
|
|
2020-09-22 10:03:34 +00:00
|
|
|
freq := testchain.ValidatorsCount + testchain.CommitteeSize()
|
|
|
|
advanceChain := func(t *testing.T) {
|
|
|
|
for i := 0; i < freq; i++ {
|
2020-11-03 15:08:58 +00:00
|
|
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
2020-09-22 10:03:34 +00:00
|
|
|
ic.Block.Index++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-10 16:16:54 +00:00
|
|
|
standBySorted := bc.GetStandByValidators()
|
|
|
|
sort.Sort(standBySorted)
|
2020-09-22 09:53:44 +00:00
|
|
|
pubs, err := neo.ComputeNextBlockValidators(bc, ic.DAO)
|
2020-08-03 13:24:22 +00:00
|
|
|
require.NoError(t, err)
|
2020-08-10 16:16:54 +00:00
|
|
|
require.Equal(t, standBySorted, pubs)
|
2020-08-03 13:24:22 +00:00
|
|
|
|
2020-11-03 15:08:58 +00:00
|
|
|
sz := testchain.CommitteeSize()
|
|
|
|
accs := make([]*wallet.Account, sz)
|
2020-08-03 13:24:22 +00:00
|
|
|
candidates := make(keys.PublicKeys, sz)
|
2020-11-03 15:08:58 +00:00
|
|
|
txs := make([]*transaction.Transaction, 0, len(accs))
|
2020-08-03 13:24:22 +00:00
|
|
|
for i := 0; i < sz; i++ {
|
|
|
|
priv, err := keys.NewPrivateKey()
|
|
|
|
require.NoError(t, err)
|
|
|
|
candidates[i] = priv.PublicKey()
|
2020-11-03 15:08:58 +00:00
|
|
|
accs[i], err = wallet.NewAccount()
|
|
|
|
require.NoError(t, err)
|
2020-08-03 13:24:22 +00:00
|
|
|
if i > 0 {
|
|
|
|
require.NoError(t, neo.RegisterCandidateInternal(ic, candidates[i]))
|
|
|
|
}
|
|
|
|
|
2020-11-03 15:08:58 +00:00
|
|
|
to := accs[i].Contract.ScriptHash()
|
|
|
|
w := io.NewBufBinWriter()
|
2020-12-29 10:44:07 +00:00
|
|
|
emit.AppCall(w.BinWriter, bc.contracts.NEO.Hash, "transfer", callflag.All,
|
2020-11-03 15:08:58 +00:00
|
|
|
neoOwner.BytesBE(), to.BytesBE(),
|
2020-11-19 15:01:42 +00:00
|
|
|
big.NewInt(int64(sz-i)*1000000).Int64(), nil)
|
2020-11-03 15:08:58 +00:00
|
|
|
emit.Opcodes(w.BinWriter, opcode.ASSERT)
|
2020-12-29 10:44:07 +00:00
|
|
|
emit.AppCall(w.BinWriter, bc.contracts.GAS.Hash, "transfer", callflag.All,
|
2020-11-03 15:08:58 +00:00
|
|
|
neoOwner.BytesBE(), to.BytesBE(),
|
2020-11-19 15:01:42 +00:00
|
|
|
int64(1_000_000_000), nil)
|
2020-11-03 15:08:58 +00:00
|
|
|
emit.Opcodes(w.BinWriter, opcode.ASSERT)
|
|
|
|
require.NoError(t, w.Err)
|
2021-03-25 16:18:01 +00:00
|
|
|
tx := transaction.New(w.Bytes(), 1000_000_000)
|
2020-11-03 15:08:58 +00:00
|
|
|
tx.ValidUntilBlock = bc.BlockHeight() + 1
|
|
|
|
setSigner(tx, testchain.MultisigScriptHash())
|
2020-11-23 11:09:45 +00:00
|
|
|
require.NoError(t, testchain.SignTx(bc, tx))
|
2020-11-03 15:08:58 +00:00
|
|
|
txs = append(txs, tx)
|
|
|
|
}
|
|
|
|
require.NoError(t, bc.AddBlock(bc.newBlock(txs...)))
|
|
|
|
for _, tx := range txs {
|
|
|
|
checkTxHalt(t, bc, tx.Hash())
|
2020-08-03 13:24:22 +00:00
|
|
|
}
|
2020-11-03 15:08:58 +00:00
|
|
|
transferBlock := bc.BlockHeight()
|
2020-08-03 13:24:22 +00:00
|
|
|
|
|
|
|
for i := 1; i < sz; i++ {
|
2020-11-03 15:08:58 +00:00
|
|
|
priv := accs[i].PrivateKey()
|
2020-08-10 06:20:21 +00:00
|
|
|
h := priv.GetScriptHash()
|
2020-08-03 13:24:22 +00:00
|
|
|
setSigner(tx, h)
|
2020-08-10 06:20:21 +00:00
|
|
|
ic.VM.Load(priv.PublicKey().GetVerificationScript())
|
2020-08-03 13:24:22 +00:00
|
|
|
require.NoError(t, neo.VoteInternal(ic, h, candidates[i]))
|
|
|
|
}
|
|
|
|
|
2020-08-10 14:51:46 +00:00
|
|
|
// We still haven't voted enough validators in.
|
2020-09-22 09:53:44 +00:00
|
|
|
pubs, err = neo.ComputeNextBlockValidators(bc, ic.DAO)
|
2020-08-03 13:24:22 +00:00
|
|
|
require.NoError(t, err)
|
2020-08-10 16:16:54 +00:00
|
|
|
require.Equal(t, standBySorted, pubs)
|
2020-08-03 13:24:22 +00:00
|
|
|
|
2020-09-22 10:03:34 +00:00
|
|
|
advanceChain(t)
|
2020-08-28 07:24:54 +00:00
|
|
|
pubs = neo.GetNextBlockValidatorsInternal()
|
2020-08-20 15:49:01 +00:00
|
|
|
require.EqualValues(t, standBySorted, pubs)
|
|
|
|
|
2020-08-03 13:24:22 +00:00
|
|
|
// Register and give some value to the last validator.
|
|
|
|
require.NoError(t, neo.RegisterCandidateInternal(ic, candidates[0]))
|
2020-11-03 15:08:58 +00:00
|
|
|
priv := accs[0].PrivateKey()
|
2020-08-10 06:20:21 +00:00
|
|
|
h := priv.GetScriptHash()
|
2020-08-03 13:24:22 +00:00
|
|
|
setSigner(tx, h)
|
2020-08-10 06:20:21 +00:00
|
|
|
ic.VM.Load(priv.PublicKey().GetVerificationScript())
|
2020-08-03 13:24:22 +00:00
|
|
|
require.NoError(t, neo.VoteInternal(ic, h, candidates[0]))
|
|
|
|
|
2021-05-12 18:34:07 +00:00
|
|
|
_, err = ic.DAO.Persist()
|
|
|
|
require.NoError(t, err)
|
2020-09-22 10:03:34 +00:00
|
|
|
advanceChain(t)
|
2020-09-22 09:53:44 +00:00
|
|
|
pubs, err = neo.ComputeNextBlockValidators(bc, ic.DAO)
|
2020-08-03 13:24:22 +00:00
|
|
|
require.NoError(t, err)
|
2020-11-03 15:08:58 +00:00
|
|
|
sortedCandidates := candidates.Copy()[:testchain.Size()]
|
2020-08-10 16:16:54 +00:00
|
|
|
sort.Sort(sortedCandidates)
|
|
|
|
require.EqualValues(t, sortedCandidates, pubs)
|
2020-08-06 11:57:10 +00:00
|
|
|
|
2020-08-28 07:24:54 +00:00
|
|
|
pubs = neo.GetNextBlockValidatorsInternal()
|
2020-08-20 15:49:01 +00:00
|
|
|
require.EqualValues(t, sortedCandidates, pubs)
|
|
|
|
|
2020-11-03 15:08:58 +00:00
|
|
|
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)
|
2020-12-29 10:44:07 +00:00
|
|
|
emit.AppCall(w.BinWriter, bc.contracts.NEO.Hash, "transfer", callflag.All,
|
2020-11-19 15:01:42 +00:00
|
|
|
h.BytesBE(), h.BytesBE(), int64(1), nil)
|
2020-11-03 15:08:58 +00:00
|
|
|
emit.Opcodes(w.BinWriter, opcode.ASSERT)
|
|
|
|
require.NoError(t, w.Err)
|
2021-03-25 16:18:01 +00:00
|
|
|
tx := transaction.New(w.Bytes(), 0)
|
2020-11-03 15:08:58 +00:00
|
|
|
tx.ValidUntilBlock = bc.BlockHeight() + 1
|
|
|
|
tx.NetworkFee = 2_000_000
|
2020-12-15 09:04:08 +00:00
|
|
|
tx.SystemFee = 11_000_000
|
2020-11-03 15:08:58 +00:00
|
|
|
setSigner(tx, h)
|
2021-03-25 16:18:01 +00:00
|
|
|
require.NoError(t, accs[i].SignTx(netmode.UnitTestNet, tx))
|
2020-11-03 15:08:58 +00:00
|
|
|
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])
|
|
|
|
|
2020-11-06 09:27:05 +00:00
|
|
|
gasForHold, err := bc.contracts.NEO.CalculateNEOHolderReward(bc.dao, neoBalance[i], transferBlock, bc.BlockHeight())
|
|
|
|
require.NoError(t, err)
|
2020-11-03 15:08:58 +00:00
|
|
|
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]))
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2020-08-06 11:57:10 +00:00
|
|
|
require.NoError(t, neo.UnregisterCandidateInternal(ic, candidates[0]))
|
|
|
|
require.Error(t, neo.VoteInternal(ic, h, candidates[0]))
|
2020-09-22 10:03:34 +00:00
|
|
|
advanceChain(t)
|
2020-08-06 11:57:10 +00:00
|
|
|
|
2020-09-22 09:53:44 +00:00
|
|
|
pubs, err = neo.ComputeNextBlockValidators(bc, ic.DAO)
|
2020-08-06 11:57:10 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
for i := range pubs {
|
|
|
|
require.NotEqual(t, candidates[0], pubs[i])
|
|
|
|
}
|
2020-08-03 13:24:22 +00:00
|
|
|
}
|
2020-08-26 09:07:30 +00:00
|
|
|
|
2021-09-16 14:09:42 +00:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2020-08-26 10:06:19 +00:00
|
|
|
func TestNEO_SetGasPerBlock(t *testing.T) {
|
|
|
|
bc := newTestChain(t)
|
|
|
|
|
2021-01-28 15:01:30 +00:00
|
|
|
testGetSet(t, bc, bc.contracts.NEO.Hash, "GasPerBlock",
|
|
|
|
5*native.GASFactor, 0, 10*native.GASFactor)
|
2020-08-26 10:06:19 +00:00
|
|
|
}
|
|
|
|
|
2020-08-26 09:07:30 +00:00
|
|
|
func TestNEO_CalculateBonus(t *testing.T) {
|
|
|
|
bc := newTestChain(t)
|
|
|
|
|
|
|
|
neo := bc.contracts.NEO
|
2021-07-14 12:45:30 +00:00
|
|
|
tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
|
2020-10-29 16:14:49 +00:00
|
|
|
ic := bc.newInteropContext(trigger.Application, bc.dao, nil, tx)
|
2020-09-29 06:56:19 +00:00
|
|
|
ic.SpawnVM()
|
|
|
|
ic.VM.LoadScript([]byte{byte(opcode.RET)})
|
2020-08-26 09:07:30 +00:00
|
|
|
t.Run("Invalid", func(t *testing.T) {
|
2020-11-06 09:27:05 +00:00
|
|
|
_, err := neo.CalculateNEOHolderReward(ic.DAO, new(big.Int).SetInt64(-1), 0, 1)
|
2020-08-26 09:07:30 +00:00
|
|
|
require.Error(t, err)
|
|
|
|
})
|
|
|
|
t.Run("Zero", func(t *testing.T) {
|
2020-11-06 09:27:05 +00:00
|
|
|
res, err := neo.CalculateNEOHolderReward(ic.DAO, big.NewInt(0), 0, 100)
|
2020-08-26 09:07:30 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.EqualValues(t, 0, res.Int64())
|
|
|
|
})
|
2020-08-26 10:06:19 +00:00
|
|
|
t.Run("ManyBlocks", func(t *testing.T) {
|
2020-09-24 12:36:14 +00:00
|
|
|
setSigner(tx, neo.GetCommitteeAddress())
|
2021-01-28 15:01:30 +00:00
|
|
|
err := neo.SetGASPerBlock(ic, 10, big.NewInt(1*native.GASFactor))
|
2020-08-26 10:06:19 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2020-11-06 09:27:05 +00:00
|
|
|
res, err := neo.CalculateNEOHolderReward(ic.DAO, big.NewInt(100), 5, 15)
|
2020-08-26 10:06:19 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.EqualValues(t, (100*5*5/10)+(100*5*1/10), res.Int64())
|
|
|
|
})
|
2020-08-26 09:07:30 +00:00
|
|
|
}
|
2020-08-26 13:16:57 +00:00
|
|
|
|
2021-05-25 14:54:57 +00:00
|
|
|
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)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-08-26 13:16:57 +00:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
|
2020-11-06 07:50:45 +00:00
|
|
|
const singleBounty = 50000000
|
2020-09-23 08:48:31 +00:00
|
|
|
bs := map[int]int64{0: singleBounty}
|
2020-08-26 13:16:57 +00:00
|
|
|
checkBalances := func() {
|
|
|
|
for i := 0; i < testchain.CommitteeSize(); i++ {
|
2020-09-23 08:48:31 +00:00
|
|
|
require.EqualValues(t, bs[i], bc.GetUtilityTokenBalance(hs[i]).Int64(), i)
|
2020-08-26 13:16:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
for i := 0; i < testchain.CommitteeSize()*2; i++ {
|
|
|
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
2020-09-23 08:48:31 +00:00
|
|
|
bs[(i+1)%testchain.CommitteeSize()] += singleBounty
|
2020-08-26 13:16:57 +00:00
|
|
|
checkBalances()
|
|
|
|
}
|
|
|
|
}
|
2020-11-19 15:01:42 +00:00
|
|
|
|
|
|
|
func TestNEO_TransferOnPayment(t *testing.T) {
|
|
|
|
bc := newTestChain(t)
|
|
|
|
|
2020-12-13 15:26:35 +00:00
|
|
|
cs, _ := getTestContractState(bc)
|
|
|
|
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
|
2020-11-19 15:01:42 +00:00
|
|
|
|
|
|
|
const amount = 2
|
2020-11-18 20:10:48 +00:00
|
|
|
tx := transferTokenFromMultisigAccount(t, bc, cs.Hash, bc.contracts.NEO.Hash, amount)
|
2020-11-19 15:01:42 +00:00
|
|
|
aer, err := bc.GetAppExecResults(tx.Hash(), trigger.Application)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, vm.HaltState, aer[0].VMState)
|
2020-12-10 13:52:16 +00:00
|
|
|
require.Len(t, aer[0].Events, 3) // transfer + GAS claim for sender + onPayment
|
2020-11-19 15:01:42 +00:00
|
|
|
|
|
|
|
e := aer[0].Events[2]
|
|
|
|
require.Equal(t, "LastPayment", e.Name)
|
|
|
|
arr := e.Item.Value().([]stackitem.Item)
|
2020-12-10 13:52:16 +00:00
|
|
|
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())
|
2020-11-19 15:01:42 +00:00
|
|
|
}
|
2021-03-05 11:17:58 +00:00
|
|
|
|
|
|
|
func TestRegisterPrice(t *testing.T) {
|
|
|
|
bc := newTestChain(t)
|
|
|
|
testGetSet(t, bc, bc.contracts.NEO.Hash, "RegisterPrice",
|
|
|
|
native.DefaultRegisterPrice, 1, math.MaxInt64)
|
|
|
|
}
|
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
|
|
|
|
|
|
|
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)
|
|
|
|
})
|
|
|
|
}
|
core: allow transfer 0 GAS/NEO with zero balance
This commit fixes states diff at 131795 block of mainnet.
Transaction:
```
NEO-GO-VM > loadbase64 DAAQDBSPsxdYh6cITC3gUKI4oWmYxJs49gwUj7MXWIenCEwt4FCiOKFpmMSbOPYUwB8MCHRyYW5zZmVyDBT1Y+pAvCg9TQ4FxI6jBbPyoHNA70FifVtSOQwAEQwUj7MXWIenCEwt4FCiOKFpmMSbOPYMFL1Mb4Fqp6gHiEwzM6xSc8fLS+RpFMAfDAh0cmFuc2ZlcgwU9WPqQLwoPU0OBcSOowWz8qBzQO9BYn1bUjk=
READY: loaded 176 instructions
NEO-GO-VM 0 > ops
INDEX OPCODE PARAMETER
0 PUSHDATA1 ("") <<
2 PUSH0
3 PUSHDATA1 8fb3175887a7084c2de050a238a16998c49b38f6
25 PUSHDATA1 8fb3175887a7084c2de050a238a16998c49b38f6
47 PUSH4
48 PACK
49 PUSH15
50 PUSHDATA1 7472616e73666572 ("transfer")
60 PUSHDATA1 f563ea40bc283d4d0e05c48ea305b3f2a07340ef // NEO token
82 SYSCALL System.Contract.Call (627d5b52)
87 ASSERT
88 PUSHDATA1 ("")
90 PUSH1
91 PUSHDATA1 8fb3175887a7084c2de050a238a16998c49b38f6
113 PUSHDATA1 bd4c6f816aa7a807884c3333ac5273c7cb4be469
135 PUSH4
136 PACK
137 PUSH15
138 PUSHDATA1 7472616e73666572 ("transfer")
148 PUSHDATA1 f563ea40bc283d4d0e05c48ea305b3f2a07340ef // NEO token
170 SYSCALL System.Contract.Call (627d5b52)
175 ASSERT
```
Go's applog:
```
{
"id" : 1,
"result" : {
"txid" : "0x97d2ccb01467b22c73a2cb95f7af298f3a5bd8c849d7044371898b8efecdaabd",
"executions" : [
{
"exception" : "at instruction 87 (ASSERT): ASSERT failed",
"stack" : [],
"gasconsumed" : "4988995",
"notifications" : [],
"trigger" : "Application",
"vmstate" : "FAULT"
}
]
},
"jsonrpc" : "2.0"
}
```
C#'s applog:
```
{
"jsonrpc" : "2.0",
"result" : {
"executions" : [
{
"stack" : [],
"notifications" : [
{
"contract" : "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5",
"state" : {
"type" : "Array",
"value" : [
{
"type" : "ByteString",
"value" : "j7MXWIenCEwt4FCiOKFpmMSbOPY="
},
{
"type" : "ByteString",
"value" : "j7MXWIenCEwt4FCiOKFpmMSbOPY="
},
{
"value" : "0",
"type" : "Integer"
}
]
},
"eventname" : "Transfer"
},
{
"contract" : "0xd2a4cff31913016155e38e474a2c06d08be276cf",
"state" : {
"value" : [
{
"type" : "Any"
},
{
"type" : "ByteString",
"value" : "vUxvgWqnqAeITDMzrFJzx8tL5Gk="
},
{
"value" : "2490",
"type" : "Integer"
}
],
"type" : "Array"
},
"eventname" : "Transfer"
},
{
"contract" : "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5",
"state" : {
"value" : [
{
"value" : "vUxvgWqnqAeITDMzrFJzx8tL5Gk=",
"type" : "ByteString"
},
{
"value" : "j7MXWIenCEwt4FCiOKFpmMSbOPY=",
"type" : "ByteString"
},
{
"value" : "1",
"type" : "Integer"
}
],
"type" : "Array"
},
"eventname" : "Transfer"
}
],
"vmstate" : "HALT",
"gasconsumed" : "9977990",
"trigger" : "Application",
"exception" : null
}
],
"txid" : "0x97d2ccb01467b22c73a2cb95f7af298f3a5bd8c849d7044371898b8efecdaabd"
},
"id" : 1
}
```
2021-09-10 14:18:09 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
})
|
|
|
|
}
|
2021-10-23 11:28:25 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|