mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-12-23 13:41:37 +00:00
parent
a3c7130ab2
commit
af583c14ea
4 changed files with 253 additions and 62 deletions
|
@ -1158,7 +1158,7 @@ func (bc *Blockchain) UnsubscribeFromExecutions(ch chan<- *state.AppExecResult)
|
|||
// amount of NEO between specified blocks.
|
||||
func (bc *Blockchain) CalculateClaimable(value *big.Int, startHeight, endHeight uint32) *big.Int {
|
||||
ic := bc.newInteropContext(trigger.Application, bc.dao, nil, nil)
|
||||
res, _ := bc.contracts.NEO.CalculateBonus(ic, value, startHeight, endHeight)
|
||||
res, _ := bc.contracts.NEO.CalculateNEOHolderReward(ic, value, startHeight, endHeight)
|
||||
return res
|
||||
}
|
||||
|
||||
|
|
|
@ -61,6 +61,7 @@ type DAO interface {
|
|||
PutNEP5TransferLog(acc util.Uint160, index uint32, lg *state.NEP5TransferLog) error
|
||||
PutStorageItem(id int32, key []byte, si *state.StorageItem) error
|
||||
PutVersion(v string) error
|
||||
Seek(id int32, prefix []byte, f func(k, v []byte))
|
||||
StoreAsBlock(block *block.Block, buf *io.BufBinWriter) error
|
||||
StoreAsCurrentBlock(block *block.Block, buf *io.BufBinWriter) error
|
||||
StoreAsTransaction(tx *transaction.Transaction, index uint32, buf *io.BufBinWriter) error
|
||||
|
@ -406,10 +407,6 @@ func (dao *Simple) GetStorageItemsWithPrefix(id int32, prefix []byte) (map[strin
|
|||
var siMap = make(map[string]*state.StorageItem)
|
||||
var err error
|
||||
|
||||
lookupKey := makeStorageItemKey(id, nil)
|
||||
if prefix != nil {
|
||||
lookupKey = append(lookupKey, prefix...)
|
||||
}
|
||||
saveToMap := func(k, v []byte) {
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -421,20 +418,28 @@ func (dao *Simple) GetStorageItemsWithPrefix(id int32, prefix []byte) (map[strin
|
|||
err = r.Err
|
||||
return
|
||||
}
|
||||
|
||||
// Cut prefix and hash.
|
||||
// Must copy here, #1468.
|
||||
key := make([]byte, len(k[len(lookupKey):]))
|
||||
copy(key, k[len(lookupKey):])
|
||||
key := make([]byte, len(k))
|
||||
copy(key, k)
|
||||
siMap[string(key)] = si
|
||||
}
|
||||
dao.Store.Seek(lookupKey, saveToMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dao.Seek(id, prefix, saveToMap)
|
||||
return siMap, nil
|
||||
}
|
||||
|
||||
// Seek executes f for all items with a given prefix.
|
||||
// If key is to be used outside of f, they must be copied.
|
||||
func (dao *Simple) Seek(id int32, prefix []byte, f func(k, v []byte)) {
|
||||
lookupKey := makeStorageItemKey(id, nil)
|
||||
if prefix != nil {
|
||||
lookupKey = append(lookupKey, prefix...)
|
||||
}
|
||||
dao.Store.Seek(lookupKey, func(k, v []byte) {
|
||||
f(k[len(lookupKey):], v)
|
||||
})
|
||||
}
|
||||
|
||||
// makeStorageItemKey returns a key used to store StorageItem in the DB.
|
||||
func makeStorageItemKey(id int32, key []byte) []byte {
|
||||
// 1 for prefix + 4 for Uint32 + len(key) for key
|
||||
|
|
|
@ -55,6 +55,11 @@ const (
|
|||
prefixCandidate = 33
|
||||
// prefixVotersCount is a prefix for storing total amount of NEO of voters.
|
||||
prefixVotersCount = 1
|
||||
// prefixVoterRewardPerCommittee is a prefix for storing committee GAS reward.
|
||||
prefixVoterRewardPerCommittee = 23
|
||||
// voterRewardFactor is a factor by which voter reward per committee is multiplied
|
||||
// to make calculations more precise.
|
||||
voterRewardFactor = 100_000_000
|
||||
// prefixGasPerBlock is a prefix for storing amount of GAS generated per block.
|
||||
prefixGASPerBlock = 29
|
||||
// effectiveVoterTurnout represents minimal ratio of total supply to total amount voted value
|
||||
|
@ -284,13 +289,66 @@ func (n *NEO) OnPersist(ic *interop.Context) error {
|
|||
func (n *NEO) PostPersist(ic *interop.Context) error {
|
||||
gas := n.GetGASPerBlock(ic.DAO, ic.Block.Index)
|
||||
pubs := n.GetCommitteeMembers()
|
||||
index := int(ic.Block.Index) % len(ic.Chain.GetConfig().StandbyCommittee)
|
||||
gas.Mul(gas, big.NewInt(committeeRewardRatio))
|
||||
n.GAS.mint(ic, pubs[index].GetScriptHash(), gas.Div(gas, big.NewInt(100)))
|
||||
committeeSize := len(ic.Chain.GetConfig().StandbyCommittee)
|
||||
index := int(ic.Block.Index) % committeeSize
|
||||
committeeReward := new(big.Int).Mul(gas, big.NewInt(committeeRewardRatio))
|
||||
n.GAS.mint(ic, pubs[index].GetScriptHash(), committeeReward.Div(committeeReward, big.NewInt(100)))
|
||||
|
||||
if ShouldUpdateCommittee(ic.Block.Index, ic.Chain) {
|
||||
var voterReward = big.NewInt(voterRewardRatio)
|
||||
voterReward.Mul(voterReward, gas)
|
||||
voterReward.Mul(voterReward, big.NewInt(voterRewardFactor*int64(committeeSize)))
|
||||
var validatorsCount = ic.Chain.GetConfig().ValidatorsCount
|
||||
voterReward.Div(voterReward, big.NewInt(int64(committeeSize+validatorsCount)))
|
||||
voterReward.Div(voterReward, big.NewInt(100))
|
||||
|
||||
var cs = n.committee.Load().(keysWithVotes)
|
||||
var si = new(state.StorageItem)
|
||||
var key = make([]byte, 38)
|
||||
for i := range cs {
|
||||
if cs[i].Votes.Sign() > 0 {
|
||||
tmp := big.NewInt(1)
|
||||
if i < validatorsCount {
|
||||
tmp = big.NewInt(2)
|
||||
}
|
||||
tmp.Mul(tmp, voterReward)
|
||||
tmp.Div(tmp, cs[i].Votes)
|
||||
|
||||
key = makeVoterKey([]byte(cs[i].Key), key)
|
||||
var reward = n.getGASPerVote(ic.DAO, key[:34], ic.Block.Index+1)
|
||||
tmp.Add(tmp, &reward[0])
|
||||
|
||||
binary.BigEndian.PutUint32(key[34:], ic.Block.Index+1)
|
||||
|
||||
si.Value = bigint.ToBytes(tmp)
|
||||
if err := ic.DAO.PutStorageItem(n.ContractID, key, si); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
n.OnPersistEnd(ic.DAO)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *NEO) getGASPerVote(d dao.DAO, key []byte, index ...uint32) []big.Int {
|
||||
var max = make([]uint32, len(index))
|
||||
var reward = make([]big.Int, len(index))
|
||||
d.Seek(n.ContractID, key, func(k, v []byte) {
|
||||
if len(k) == 4 {
|
||||
num := binary.BigEndian.Uint32(k)
|
||||
for i, ind := range index {
|
||||
if max[i] < num && num <= ind {
|
||||
max[i] = num
|
||||
reward[i] = *bigint.FromBytes(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return reward
|
||||
}
|
||||
|
||||
// OnPersistEnd updates cached values if they've been changed.
|
||||
func (n *NEO) OnPersistEnd(d dao.DAO) {
|
||||
if n.gasPerBlockChanged.Load().(bool) {
|
||||
|
@ -339,7 +397,7 @@ func (n *NEO) distributeGas(ic *interop.Context, h util.Uint160, acc *state.NEOB
|
|||
if ic.Block == nil || ic.Block.Index == 0 {
|
||||
return nil
|
||||
}
|
||||
gen, err := n.CalculateBonus(ic, &acc.Balance, acc.BalanceHeight, ic.Block.Index)
|
||||
gen, err := n.CalculateBonus(ic, acc.VoteTo, &acc.Balance, acc.BalanceHeight, ic.Block.Index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -351,13 +409,13 @@ func (n *NEO) distributeGas(ic *interop.Context, h util.Uint160, acc *state.NEOB
|
|||
func (n *NEO) unclaimedGas(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||
u := toUint160(args[0])
|
||||
end := uint32(toBigInt(args[1]).Int64())
|
||||
bs, err := ic.DAO.GetNEP5Balances(u)
|
||||
key := makeAccountKey(u)
|
||||
si := ic.DAO.GetStorageItem(n.ContractID, key)
|
||||
st, err := state.NEOBalanceStateFromBytes(si.Value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tr := bs.Trackers[n.ContractID]
|
||||
|
||||
gen, err := n.CalculateBonus(ic, &tr.Balance, tr.LastUpdatedBlock, end)
|
||||
gen, err := n.CalculateBonus(ic, st.VoteTo, &st.Balance, st.BalanceHeight, end)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -442,8 +500,57 @@ func (n *NEO) SetGASPerBlock(ic *interop.Context, index uint32, gas *big.Int) (b
|
|||
return true, n.putGASRecord(ic.DAO, index, gas)
|
||||
}
|
||||
|
||||
// CalculateBonus calculates amount of gas generated for holding `value` NEO from start to end block.
|
||||
func (n *NEO) CalculateBonus(ic *interop.Context, value *big.Int, start, end uint32) (*big.Int, error) {
|
||||
func (n *NEO) dropCandidateIfZero(d dao.DAO, pub *keys.PublicKey, c *candidate) (bool, error) {
|
||||
if c.Registered || c.Votes.Sign() != 0 {
|
||||
return false, nil
|
||||
}
|
||||
if err := d.DeleteStorageItem(n.ContractID, makeValidatorKey(pub)); err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
var toRemove []string
|
||||
d.Seek(n.ContractID, makeVoterKey(pub.Bytes()), func(k, v []byte) {
|
||||
toRemove = append(toRemove, string(k))
|
||||
})
|
||||
for i := range toRemove {
|
||||
if err := d.DeleteStorageItem(n.ContractID, []byte(toRemove[i])); err != nil {
|
||||
return true, err
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func makeVoterKey(pub []byte, prealloc ...[]byte) []byte {
|
||||
var key []byte
|
||||
if len(prealloc) != 0 {
|
||||
key = prealloc[0]
|
||||
} else {
|
||||
key = make([]byte, 34, 38)
|
||||
}
|
||||
key[0] = prefixVoterRewardPerCommittee
|
||||
copy(key[1:], pub)
|
||||
return key
|
||||
}
|
||||
|
||||
// CalculateBonus calculates amount of gas generated for holding value NEO from start to end block
|
||||
// and having voted for active committee member.
|
||||
func (n *NEO) CalculateBonus(ic *interop.Context, vote *keys.PublicKey, value *big.Int, start, end uint32) (*big.Int, error) {
|
||||
r, err := n.CalculateNEOHolderReward(ic, value, start, end)
|
||||
if err != nil || vote == nil {
|
||||
return r, err
|
||||
}
|
||||
|
||||
var key = makeVoterKey(vote.Bytes())
|
||||
var reward = n.getGASPerVote(ic.DAO, key, start, end)
|
||||
var tmp = new(big.Int).Sub(&reward[1], &reward[0])
|
||||
tmp.Mul(tmp, value)
|
||||
tmp.Div(tmp, big.NewInt(voterRewardFactor))
|
||||
tmp.Add(tmp, r)
|
||||
return tmp, nil
|
||||
}
|
||||
|
||||
// CalculateNEOHolderReward return GAS reward for holding `value` of NEO from start to end block.
|
||||
func (n *NEO) CalculateNEOHolderReward(ic *interop.Context, value *big.Int, start, end uint32) (*big.Int, error) {
|
||||
if value.Sign() == 0 || start >= end {
|
||||
return big.NewInt(0), nil
|
||||
} else if value.Sign() < 0 {
|
||||
|
@ -530,10 +637,11 @@ func (n *NEO) UnregisterCandidateInternal(ic *interop.Context, pub *keys.PublicK
|
|||
}
|
||||
n.validators.Store(keys.PublicKeys(nil))
|
||||
c := new(candidate).FromBytes(si.Value)
|
||||
if c.Votes.Sign() == 0 {
|
||||
return ic.DAO.DeleteStorageItem(n.ContractID, key)
|
||||
}
|
||||
c.Registered = false
|
||||
ok, err := n.dropCandidateIfZero(ic.DAO, pub, c)
|
||||
if ok {
|
||||
return err
|
||||
}
|
||||
si.Value = c.Bytes()
|
||||
return ic.DAO.PutStorageItem(n.ContractID, key, si)
|
||||
}
|
||||
|
@ -574,6 +682,9 @@ func (n *NEO) VoteInternal(ic *interop.Context, h util.Uint160, pub *keys.Public
|
|||
return err
|
||||
}
|
||||
}
|
||||
if err := n.distributeGas(ic, h, acc); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := n.ModifyAccountVotes(acc, ic.DAO, new(big.Int).Neg(&acc.Balance), false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -598,8 +709,9 @@ func (n *NEO) ModifyAccountVotes(acc *state.NEOBalanceState, d dao.DAO, value *b
|
|||
cd := new(candidate).FromBytes(si.Value)
|
||||
cd.Votes.Add(&cd.Votes, value)
|
||||
if !isNewVote {
|
||||
if !cd.Registered && cd.Votes.Sign() == 0 {
|
||||
return d.DeleteStorageItem(n.ContractID, key)
|
||||
ok, err := n.dropCandidateIfZero(d, acc.VoteTo, cd)
|
||||
if ok {
|
||||
return err
|
||||
}
|
||||
} else if !cd.Registered {
|
||||
return errors.New("validator must be registered")
|
||||
|
@ -611,7 +723,7 @@ func (n *NEO) ModifyAccountVotes(acc *state.NEOBalanceState, d dao.DAO, value *b
|
|||
return nil
|
||||
}
|
||||
|
||||
func (n *NEO) getCandidates(d dao.DAO) ([]keyWithVotes, error) {
|
||||
func (n *NEO) getCandidates(d dao.DAO, sortByKey bool) ([]keyWithVotes, error) {
|
||||
siMap, err := d.GetStorageItemsWithPrefix(n.ContractID, []byte{prefixCandidate})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -623,14 +735,26 @@ func (n *NEO) getCandidates(d dao.DAO) ([]keyWithVotes, error) {
|
|||
arr = append(arr, keyWithVotes{Key: key, Votes: &c.Votes})
|
||||
}
|
||||
}
|
||||
sort.Slice(arr, func(i, j int) bool { return strings.Compare(arr[i].Key, arr[j].Key) == -1 })
|
||||
if sortByKey {
|
||||
sort.Slice(arr, func(i, j int) bool { return strings.Compare(arr[i].Key, arr[j].Key) == -1 })
|
||||
} else {
|
||||
sort.Slice(arr, func(i, j int) bool {
|
||||
// The most-voted validators should end up in the front of the list.
|
||||
cmp := arr[i].Votes.Cmp(arr[j].Votes)
|
||||
if cmp != 0 {
|
||||
return cmp > 0
|
||||
}
|
||||
// Ties are broken with public keys.
|
||||
return strings.Compare(arr[i].Key, arr[j].Key) == -1
|
||||
})
|
||||
}
|
||||
return arr, nil
|
||||
}
|
||||
|
||||
// GetCandidates returns current registered validators list with keys
|
||||
// and votes.
|
||||
func (n *NEO) GetCandidates(d dao.DAO) ([]state.Validator, error) {
|
||||
kvs, err := n.getCandidates(d)
|
||||
kvs, err := n.getCandidates(d, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -646,7 +770,7 @@ func (n *NEO) GetCandidates(d dao.DAO) ([]state.Validator, error) {
|
|||
}
|
||||
|
||||
func (n *NEO) getCandidatesCall(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
||||
validators, err := n.getCandidates(ic.DAO)
|
||||
validators, err := n.getCandidates(ic.DAO, true)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -732,7 +856,7 @@ func (n *NEO) computeCommitteeMembers(bc blockchainer.Blockchainer, d dao.DAO) (
|
|||
pubs := bc.GetStandByCommittee()
|
||||
return pubs, nil, nil
|
||||
}
|
||||
cs, err := n.getCandidates(d)
|
||||
cs, err := n.getCandidates(d, false)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -741,15 +865,6 @@ func (n *NEO) computeCommitteeMembers(bc blockchainer.Blockchainer, d dao.DAO) (
|
|||
if len(cs) < count {
|
||||
return sbVals, nil, nil
|
||||
}
|
||||
sort.Slice(cs, func(i, j int) bool {
|
||||
// The most-voted validators should end up in the front of the list.
|
||||
cmp := cs[i].Votes.Cmp(cs[j].Votes)
|
||||
if cmp != 0 {
|
||||
return cmp > 0
|
||||
}
|
||||
// Ties are broken with public keys.
|
||||
return strings.Compare(cs[i].Key, cs[j].Key) == -1
|
||||
})
|
||||
pubs := make(keys.PublicKeys, count)
|
||||
for i := range pubs {
|
||||
pubs[i], err = cs[i].PublicKey()
|
||||
|
|
|
@ -10,10 +10,13 @@ import (
|
|||
"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/internal/testchain"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"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/wallet"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -24,6 +27,12 @@ func setSigner(tx *transaction.Transaction, h util.Uint160) {
|
|||
}}
|
||||
}
|
||||
|
||||
func checkTxHalt(t *testing.T, bc *Blockchain, h util.Uint256) {
|
||||
aer, err := bc.GetAppExecResult(h)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, vm.HaltState, aer.VMState, aer.FaultException)
|
||||
}
|
||||
|
||||
func TestNEO_Vote(t *testing.T) {
|
||||
bc := newTestChain(t)
|
||||
defer bc.Close()
|
||||
|
@ -37,7 +46,7 @@ func TestNEO_Vote(t *testing.T) {
|
|||
freq := testchain.ValidatorsCount + testchain.CommitteeSize()
|
||||
advanceChain := func(t *testing.T) {
|
||||
for i := 0; i < freq; i++ {
|
||||
require.NoError(t, neo.OnPersist(ic))
|
||||
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||
ic.Block.Index++
|
||||
}
|
||||
}
|
||||
|
@ -48,27 +57,45 @@ func TestNEO_Vote(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
require.Equal(t, standBySorted, pubs)
|
||||
|
||||
sz := testchain.Size()
|
||||
|
||||
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]))
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < sz; i++ {
|
||||
to := testchain.PrivateKeyByID(i).GetScriptHash()
|
||||
ic.VM.Load(testchain.MultisigVerificationScript())
|
||||
ic.VM.LoadScriptWithHash(testchain.MultisigVerificationScript(), testchain.MultisigScriptHash(), smartcontract.All)
|
||||
require.NoError(t, neo.TransferInternal(ic, testchain.MultisigScriptHash(), to, big.NewInt(int64(sz-i)*10000000)))
|
||||
to := accs[i].Contract.ScriptHash()
|
||||
w := io.NewBufBinWriter()
|
||||
emit.AppCallWithOperationAndArgs(w.BinWriter, bc.contracts.NEO.Hash, "transfer",
|
||||
neoOwner.BytesBE(), to.BytesBE(),
|
||||
big.NewInt(int64(sz-i)*1000000).Int64())
|
||||
emit.Opcodes(w.BinWriter, opcode.ASSERT)
|
||||
emit.AppCallWithOperationAndArgs(w.BinWriter, bc.contracts.GAS.Hash, "transfer",
|
||||
neoOwner.BytesBE(), to.BytesBE(),
|
||||
int64(1_000_000_000))
|
||||
emit.Opcodes(w.BinWriter, opcode.ASSERT)
|
||||
require.NoError(t, w.Err)
|
||||
tx := transaction.New(netmode.UnitTestNet, w.Bytes(), 1000_000_000)
|
||||
tx.ValidUntilBlock = bc.BlockHeight() + 1
|
||||
setSigner(tx, testchain.MultisigScriptHash())
|
||||
require.NoError(t, 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 := testchain.PrivateKeyByID(i)
|
||||
priv := accs[i].PrivateKey()
|
||||
h := priv.GetScriptHash()
|
||||
setSigner(tx, h)
|
||||
ic.VM.Load(priv.PublicKey().GetVerificationScript())
|
||||
|
@ -86,27 +113,71 @@ func TestNEO_Vote(t *testing.T) {
|
|||
|
||||
// Register and give some value to the last validator.
|
||||
require.NoError(t, neo.RegisterCandidateInternal(ic, candidates[0]))
|
||||
priv := testchain.PrivateKeyByID(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]))
|
||||
|
||||
for i := testchain.ValidatorsCount; i < testchain.CommitteeSize(); i++ {
|
||||
priv := testchain.PrivateKey(i)
|
||||
require.NoError(t, neo.RegisterCandidateInternal(ic, priv.PublicKey()))
|
||||
}
|
||||
|
||||
ic.DAO.Persist()
|
||||
advanceChain(t)
|
||||
pubs, err = neo.ComputeNextBlockValidators(bc, ic.DAO)
|
||||
require.NoError(t, err)
|
||||
sortedCandidates := candidates.Copy()
|
||||
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.AppCallWithOperationAndArgs(w.BinWriter, bc.contracts.NEO.Hash, "transfer",
|
||||
h.BytesBE(), h.BytesBE(), int64(1))
|
||||
emit.Opcodes(w.BinWriter, opcode.ASSERT)
|
||||
require.NoError(t, w.Err)
|
||||
tx := transaction.New(netmode.UnitTestNet, w.Bytes(), 0)
|
||||
tx.ValidUntilBlock = bc.BlockHeight() + 1
|
||||
tx.NetworkFee = 2_000_000
|
||||
tx.SystemFee = 10_000_000
|
||||
setSigner(tx, h)
|
||||
require.NoError(t, accs[i].SignTx(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 := bc.CalculateClaimable(neoBalance[i], transferBlock, bc.BlockHeight())
|
||||
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)
|
||||
|
@ -187,11 +258,11 @@ func TestNEO_CalculateBonus(t *testing.T) {
|
|||
ic.SpawnVM()
|
||||
ic.VM.LoadScript([]byte{byte(opcode.RET)})
|
||||
t.Run("Invalid", func(t *testing.T) {
|
||||
_, err := neo.CalculateBonus(ic, new(big.Int).SetInt64(-1), 0, 1)
|
||||
_, err := neo.CalculateNEOHolderReward(ic, new(big.Int).SetInt64(-1), 0, 1)
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("Zero", func(t *testing.T) {
|
||||
res, err := neo.CalculateBonus(ic, big.NewInt(0), 0, 100)
|
||||
res, err := neo.CalculateNEOHolderReward(ic, big.NewInt(0), 0, 100)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 0, res.Int64())
|
||||
})
|
||||
|
@ -201,7 +272,7 @@ func TestNEO_CalculateBonus(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
require.True(t, ok)
|
||||
|
||||
res, err := neo.CalculateBonus(ic, big.NewInt(100), 5, 15)
|
||||
res, err := neo.CalculateNEOHolderReward(ic, big.NewInt(100), 5, 15)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, (100*5*5/10)+(100*5*1/10), res.Int64())
|
||||
|
||||
|
|
Loading…
Reference in a new issue