parent
f0b62cdaa6
commit
9bc731b3b1
8 changed files with 223 additions and 181 deletions
|
@ -7,6 +7,7 @@ ProtocolConfiguration:
|
|||
- 02103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e
|
||||
- 03d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699
|
||||
- 02a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd62
|
||||
ValidatorsCount: 4
|
||||
SeedList:
|
||||
- 127.0.0.1:20334
|
||||
- 127.0.0.1:20335
|
||||
|
|
|
@ -14,6 +14,7 @@ type (
|
|||
SecondsPerBlock int `yaml:"SecondsPerBlock"`
|
||||
SeedList []string `yaml:"SeedList"`
|
||||
StandbyValidators []string `yaml:"StandbyValidators"`
|
||||
ValidatorsCount int `yaml:"ValidatorsCount"`
|
||||
// Whether to verify received blocks.
|
||||
VerifyBlocks bool `yaml:"VerifyBlocks"`
|
||||
// Whether to verify transactions in received blocks.
|
||||
|
|
|
@ -44,12 +44,14 @@ const (
|
|||
NEOTotalSupply = 100000000
|
||||
// prefixCandidate is a prefix used to store validator's data.
|
||||
prefixCandidate = 33
|
||||
// prefixVotersCount is a prefix for storing total amount of NEO of voters.
|
||||
prefixVotersCount = 1
|
||||
// effectiveVoterTurnout represents minimal ratio of total supply to total amount voted value
|
||||
// which is require to use non-standby validators.
|
||||
effectiveVoterTurnout = 5
|
||||
)
|
||||
|
||||
var (
|
||||
// validatorsCountKey is a key used to store validators count
|
||||
// used to determine the real number of validators.
|
||||
validatorsCountKey = []byte{15}
|
||||
// nextValidatorsKey is a key used to store validators for the
|
||||
// next block.
|
||||
nextValidatorsKey = []byte{14}
|
||||
|
@ -96,7 +98,7 @@ func NewNEO() *NEO {
|
|||
|
||||
desc = newDescriptor("vote", smartcontract.BoolType,
|
||||
manifest.NewParameter("account", smartcontract.Hash160Type),
|
||||
manifest.NewParameter("pubkeys", smartcontract.ArrayType))
|
||||
manifest.NewParameter("pubkey", smartcontract.PublicKeyType))
|
||||
md = newMethodAndPrice(n.vote, 500000000, smartcontract.AllowModifyStates)
|
||||
n.AddMethod(md, desc, false)
|
||||
|
||||
|
@ -104,6 +106,10 @@ func NewNEO() *NEO {
|
|||
md = newMethodAndPrice(n.getCandidatesCall, 100000000, smartcontract.AllowStates)
|
||||
n.AddMethod(md, desc, true)
|
||||
|
||||
desc = newDescriptor("getСommittee", smartcontract.ArrayType)
|
||||
md = newMethodAndPrice(n.getCommittee, 100000000, smartcontract.AllowStates)
|
||||
n.AddMethod(md, desc, true)
|
||||
|
||||
desc = newDescriptor("getValidators", smartcontract.ArrayType)
|
||||
md = newMethodAndPrice(n.getValidators, 100000000, smartcontract.AllowStates)
|
||||
n.AddMethod(md, desc, true)
|
||||
|
@ -131,6 +137,11 @@ func (n *NEO) Initialize(ic *interop.Context) error {
|
|||
}
|
||||
n.mint(ic, h, big.NewInt(NEOTotalSupply))
|
||||
|
||||
err = ic.DAO.PutStorageItem(n.ContractID, []byte{prefixVotersCount}, &state.StorageItem{Value: []byte{0}})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range vs {
|
||||
if err := n.registerCandidateInternal(ic, vs[i]); err != nil {
|
||||
return err
|
||||
|
@ -166,21 +177,11 @@ func (n *NEO) increaseBalance(ic *interop.Context, h util.Uint160, si *state.Sto
|
|||
si.Value = acc.Bytes()
|
||||
return nil
|
||||
}
|
||||
if len(acc.Votes) > 0 {
|
||||
if err := n.ModifyAccountVotes(acc, ic.DAO, amount); err != nil {
|
||||
if err := n.ModifyAccountVotes(acc, ic.DAO, amount, modifyVoteTransfer); err != nil {
|
||||
return err
|
||||
}
|
||||
siVC := ic.DAO.GetStorageItem(n.ContractID, validatorsCountKey)
|
||||
if siVC == nil {
|
||||
return errors.New("validators count uninitialized")
|
||||
}
|
||||
vc, err := ValidatorsCountFromBytes(siVC.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vc[len(acc.Votes)-1].Add(&vc[len(acc.Votes)-1], amount)
|
||||
siVC.Value = vc.Bytes()
|
||||
if err := ic.DAO.PutStorageItem(n.ContractID, validatorsCountKey, siVC); err != nil {
|
||||
if acc.VoteTo != nil {
|
||||
if err := n.modifyVoterTurnout(ic.DAO, amount); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -224,35 +225,26 @@ func (n *NEO) registerCandidate(ic *interop.Context, args []stackitem.Item) stac
|
|||
func (n *NEO) registerCandidateInternal(ic *interop.Context, pub *keys.PublicKey) error {
|
||||
key := makeValidatorKey(pub)
|
||||
si := ic.DAO.GetStorageItem(n.ContractID, key)
|
||||
if si != nil {
|
||||
return errors.New("already registered")
|
||||
}
|
||||
if si == nil {
|
||||
si = new(state.StorageItem)
|
||||
// Zero value.
|
||||
si.Value = []byte{}
|
||||
}
|
||||
c := &candidate{Registered: true}
|
||||
si.Value = c.Bytes()
|
||||
return ic.DAO.PutStorageItem(n.ContractID, key, si)
|
||||
}
|
||||
|
||||
func (n *NEO) vote(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||
acc := toUint160(args[0])
|
||||
arr := args[1].Value().([]stackitem.Item)
|
||||
var pubs keys.PublicKeys
|
||||
for i := range arr {
|
||||
pub := new(keys.PublicKey)
|
||||
bs, err := arr[i].TryBytes()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
} else if err := pub.DecodeBytes(bs); err != nil {
|
||||
panic(err)
|
||||
var pub *keys.PublicKey
|
||||
if _, ok := args[1].(stackitem.Null); !ok {
|
||||
pub = toPublicKey(args[1])
|
||||
}
|
||||
pubs = append(pubs, pub)
|
||||
}
|
||||
err := n.VoteInternal(ic, acc, pubs)
|
||||
err := n.VoteInternal(ic, acc, pub)
|
||||
return stackitem.NewBool(err == nil)
|
||||
}
|
||||
|
||||
// VoteInternal votes from account h for validarors specified in pubs.
|
||||
func (n *NEO) VoteInternal(ic *interop.Context, h util.Uint160, pubs keys.PublicKeys) error {
|
||||
func (n *NEO) VoteInternal(ic *interop.Context, h util.Uint160, pub *keys.PublicKey) error {
|
||||
ok, err := runtime.CheckHashedWitness(ic, h)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -268,69 +260,57 @@ func (n *NEO) VoteInternal(ic *interop.Context, h util.Uint160, pubs keys.Public
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := n.ModifyAccountVotes(acc, ic.DAO, new(big.Int).Neg(&acc.Balance)); err != nil {
|
||||
return err
|
||||
if (acc.VoteTo == nil) != (pub == nil) {
|
||||
val := &acc.Balance
|
||||
if pub == nil {
|
||||
val = new(big.Int).Neg(val)
|
||||
}
|
||||
pubs = pubs.Unique()
|
||||
// Check validators registration.
|
||||
var newPubs keys.PublicKeys
|
||||
for _, pub := range pubs {
|
||||
if ic.DAO.GetStorageItem(n.ContractID, makeValidatorKey(pub)) == nil {
|
||||
continue
|
||||
}
|
||||
newPubs = append(newPubs, pub)
|
||||
}
|
||||
if lp, lv := len(newPubs), len(acc.Votes); lp != lv {
|
||||
var si *state.StorageItem
|
||||
var vc *ValidatorsCount
|
||||
var err error
|
||||
|
||||
si = ic.DAO.GetStorageItem(n.ContractID, validatorsCountKey)
|
||||
if si == nil {
|
||||
// The first voter.
|
||||
si = new(state.StorageItem)
|
||||
vc = new(ValidatorsCount)
|
||||
} else {
|
||||
vc, err = ValidatorsCountFromBytes(si.Value)
|
||||
if err != nil {
|
||||
if err := n.modifyVoterTurnout(ic.DAO, val); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if lv > 0 {
|
||||
vc[lv-1].Sub(&vc[lv-1], &acc.Balance)
|
||||
}
|
||||
if len(newPubs) > 0 {
|
||||
vc[lp-1].Add(&vc[lp-1], &acc.Balance)
|
||||
}
|
||||
si.Value = vc.Bytes()
|
||||
if err := ic.DAO.PutStorageItem(n.ContractID, validatorsCountKey, si); err != nil {
|
||||
if err := n.ModifyAccountVotes(acc, ic.DAO, new(big.Int).Neg(&acc.Balance), modifyVoteOld); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
acc.Votes = newPubs
|
||||
if err := n.ModifyAccountVotes(acc, ic.DAO, &acc.Balance); err != nil {
|
||||
acc.VoteTo = pub
|
||||
if err := n.ModifyAccountVotes(acc, ic.DAO, &acc.Balance, modifyVoteNew); err != nil {
|
||||
return err
|
||||
}
|
||||
si.Value = acc.Bytes()
|
||||
return ic.DAO.PutStorageItem(n.ContractID, key, si)
|
||||
}
|
||||
|
||||
const (
|
||||
modifyVoteTransfer = iota
|
||||
modifyVoteOld
|
||||
modifyVoteNew
|
||||
)
|
||||
|
||||
// ModifyAccountVotes modifies votes of the specified account by value (can be negative).
|
||||
func (n *NEO) ModifyAccountVotes(acc *state.NEOBalanceState, d dao.DAO, value *big.Int) error {
|
||||
for _, vote := range acc.Votes {
|
||||
key := makeValidatorKey(vote)
|
||||
// typ specifies if this modify is occuring during transfer or vote (with old or new validator).
|
||||
func (n *NEO) ModifyAccountVotes(acc *state.NEOBalanceState, d dao.DAO, value *big.Int, typ int) error {
|
||||
if acc.VoteTo != nil {
|
||||
key := makeValidatorKey(acc.VoteTo)
|
||||
si := d.GetStorageItem(n.ContractID, key)
|
||||
if si == nil {
|
||||
return errors.New("invalid validator")
|
||||
}
|
||||
votes := bigint.FromBytes(si.Value)
|
||||
votes.Add(votes, value)
|
||||
si.Value = bigint.ToPreallocatedBytes(votes, si.Value[:0])
|
||||
if err := d.PutStorageItem(n.ContractID, key, si); err != nil {
|
||||
return err
|
||||
cd := new(candidate).FromBytes(si.Value)
|
||||
cd.Votes.Add(&cd.Votes, value)
|
||||
switch typ {
|
||||
case modifyVoteOld:
|
||||
if !cd.Registered && cd.Votes.Sign() == 0 {
|
||||
return d.DeleteStorageItem(n.ContractID, key)
|
||||
}
|
||||
case modifyVoteNew:
|
||||
if !cd.Registered {
|
||||
return errors.New("validator must be registered")
|
||||
}
|
||||
}
|
||||
n.validators.Store(keys.PublicKeys(nil))
|
||||
si.Value = cd.Bytes()
|
||||
return d.PutStorageItem(n.ContractID, key, si)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -341,8 +321,10 @@ func (n *NEO) getCandidates(d dao.DAO) ([]keyWithVotes, error) {
|
|||
}
|
||||
arr := make([]keyWithVotes, 0, len(siMap))
|
||||
for key, si := range siMap {
|
||||
votes := bigint.FromBytes(si.Value)
|
||||
arr = append(arr, keyWithVotes{key, votes})
|
||||
c := new(candidate).FromBytes(si.Value)
|
||||
if c.Registered {
|
||||
arr = append(arr, keyWithVotes{key, &c.Votes})
|
||||
}
|
||||
}
|
||||
sort.Slice(arr, func(i, j int) bool { return strings.Compare(arr[i].Key, arr[j].Key) == -1 })
|
||||
return arr, nil
|
||||
|
@ -386,53 +368,15 @@ func (n *NEO) GetValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) (ke
|
|||
if vals := n.validators.Load().(keys.PublicKeys); vals != nil {
|
||||
return vals.Copy(), nil
|
||||
}
|
||||
standByValidators := bc.GetStandByValidators()
|
||||
si := d.GetStorageItem(n.ContractID, validatorsCountKey)
|
||||
if si == nil {
|
||||
n.validators.Store(standByValidators)
|
||||
return standByValidators.Copy(), nil
|
||||
}
|
||||
validatorsCount, err := ValidatorsCountFromBytes(si.Value)
|
||||
result, err := n.getCommitteeMembers(bc, d)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
validators, err := n.GetCandidates(d)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
count := bc.GetConfig().ValidatorsCount
|
||||
if len(result) < count {
|
||||
count = len(result)
|
||||
}
|
||||
sort.Slice(validators, func(i, j int) bool {
|
||||
// The most-voted validators should end up in the front of the list.
|
||||
cmp := validators[i].Votes.Cmp(validators[j].Votes)
|
||||
if cmp != 0 {
|
||||
return cmp > 0
|
||||
}
|
||||
// Ties are broken with public keys.
|
||||
return validators[i].Key.Cmp(validators[j].Key) == -1
|
||||
})
|
||||
|
||||
count := validatorsCount.GetWeightedAverage()
|
||||
if count < len(standByValidators) {
|
||||
count = len(standByValidators)
|
||||
}
|
||||
|
||||
uniqueSBValidators := standByValidators.Unique()
|
||||
result := keys.PublicKeys{}
|
||||
for _, validator := range validators {
|
||||
if validator.Votes.Sign() > 0 || uniqueSBValidators.Contains(validator.Key) {
|
||||
result = append(result, validator.Key)
|
||||
}
|
||||
}
|
||||
|
||||
if result.Len() >= count {
|
||||
result = result[:count]
|
||||
} else {
|
||||
for i := 0; i < uniqueSBValidators.Len() && result.Len() < count; i++ {
|
||||
if !result.Contains(uniqueSBValidators[i]) {
|
||||
result = append(result, uniqueSBValidators[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Sort(result)
|
||||
n.validators.Store(result)
|
||||
return result, nil
|
||||
}
|
||||
|
@ -445,6 +389,68 @@ func (n *NEO) getValidators(ic *interop.Context, _ []stackitem.Item) stackitem.I
|
|||
return pubsToArray(result)
|
||||
}
|
||||
|
||||
func (n *NEO) getCommittee(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
||||
pubs, err := n.getCommitteeMembers(ic.Chain, ic.DAO)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
sort.Sort(pubs)
|
||||
return pubsToArray(pubs)
|
||||
}
|
||||
|
||||
func (n *NEO) modifyVoterTurnout(d dao.DAO, amount *big.Int) error {
|
||||
key := []byte{prefixVotersCount}
|
||||
si := d.GetStorageItem(n.ContractID, key)
|
||||
if si == nil {
|
||||
return errors.New("voters count not found")
|
||||
}
|
||||
votersCount := bigint.FromBytes(si.Value)
|
||||
votersCount.Add(votersCount, amount)
|
||||
si.Value = bigint.ToBytes(votersCount)
|
||||
return d.PutStorageItem(n.ContractID, key, si)
|
||||
}
|
||||
|
||||
func (n *NEO) getCommitteeMembers(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) {
|
||||
key := []byte{prefixVotersCount}
|
||||
si := d.GetStorageItem(n.ContractID, key)
|
||||
if si == nil {
|
||||
return nil, errors.New("voters count not found")
|
||||
}
|
||||
votersCount := bigint.FromBytes(si.Value)
|
||||
// votersCount / totalSupply must be >= 0.2
|
||||
votersCount.Mul(votersCount, big.NewInt(effectiveVoterTurnout))
|
||||
voterTurnout := votersCount.Div(votersCount, n.getTotalSupply(d))
|
||||
if voterTurnout.Sign() != 1 {
|
||||
return bc.GetStandByValidators(), nil
|
||||
}
|
||||
cs, err := n.getCandidates(d)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sbVals := bc.GetStandByValidators()
|
||||
count := len(sbVals)
|
||||
if len(cs) < count {
|
||||
return sbVals, 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 = keys.NewPublicKeyFromBytes([]byte(cs[i].Key), elliptic.P256())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return pubs, nil
|
||||
}
|
||||
|
||||
func (n *NEO) getNextBlockValidators(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
||||
result, err := n.getNextBlockValidatorsInternal(ic.Chain, ic.DAO)
|
||||
if err != nil {
|
||||
|
|
48
pkg/core/native/native_neo_candidate.go
Normal file
48
pkg/core/native/native_neo_candidate.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
package native
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
)
|
||||
|
||||
type candidate struct {
|
||||
Registered bool
|
||||
Votes big.Int
|
||||
}
|
||||
|
||||
// Bytes marshals c to byte array.
|
||||
func (c *candidate) Bytes() []byte {
|
||||
w := io.NewBufBinWriter()
|
||||
stackitem.EncodeBinaryStackItem(c.toStackItem(), w.BinWriter)
|
||||
return w.Bytes()
|
||||
}
|
||||
|
||||
// FromBytes unmarshals candidate from byte array.
|
||||
func (c *candidate) FromBytes(data []byte) *candidate {
|
||||
r := io.NewBinReaderFromBuf(data)
|
||||
item := stackitem.DecodeBinaryStackItem(r)
|
||||
if r.Err != nil {
|
||||
panic(r.Err)
|
||||
}
|
||||
return c.fromStackItem(item)
|
||||
}
|
||||
|
||||
func (c *candidate) toStackItem() stackitem.Item {
|
||||
return stackitem.NewStruct([]stackitem.Item{
|
||||
stackitem.NewBool(c.Registered),
|
||||
stackitem.NewBigInteger(&c.Votes),
|
||||
})
|
||||
}
|
||||
|
||||
func (c *candidate) fromStackItem(item stackitem.Item) *candidate {
|
||||
arr := item.(*stackitem.Struct).Value().([]stackitem.Item)
|
||||
vs, err := arr[1].TryInteger()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
c.Registered = arr[0].Bool()
|
||||
c.Votes = *vs
|
||||
return c
|
||||
}
|
18
pkg/core/native/native_neo_test.go
Normal file
18
pkg/core/native/native_neo_test.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
package native
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCandidate_Bytes(t *testing.T) {
|
||||
expected := &candidate{
|
||||
Registered: true,
|
||||
Votes: *big.NewInt(0x0F),
|
||||
}
|
||||
data := expected.Bytes()
|
||||
actual := new(candidate).FromBytes(data)
|
||||
require.Equal(t, expected, actual)
|
||||
}
|
|
@ -63,51 +63,3 @@ func (vc *ValidatorsCount) DecodeBinary(r *io.BinReader) {
|
|||
vc[i] = *bigint.FromBytes(buf)
|
||||
}
|
||||
}
|
||||
|
||||
// GetWeightedAverage returns an average count of validators that's been voted
|
||||
// for not counting 1/4 of minimum and maximum numbers.
|
||||
func (vc *ValidatorsCount) GetWeightedAverage() int {
|
||||
const (
|
||||
lowerThreshold = 0.25
|
||||
upperThreshold = 0.75
|
||||
)
|
||||
var (
|
||||
sumWeight, sumValue, overallSum, slidingSum int64
|
||||
slidingRatio float64
|
||||
)
|
||||
|
||||
for i := range vc {
|
||||
overallSum += vc[i].Int64()
|
||||
}
|
||||
|
||||
for i := range vc {
|
||||
if slidingRatio >= upperThreshold {
|
||||
break
|
||||
}
|
||||
weight := vc[i].Int64()
|
||||
slidingSum += weight
|
||||
previousRatio := slidingRatio
|
||||
slidingRatio = float64(slidingSum) / float64(overallSum)
|
||||
|
||||
if slidingRatio <= lowerThreshold {
|
||||
continue
|
||||
}
|
||||
|
||||
if previousRatio < lowerThreshold {
|
||||
if slidingRatio > upperThreshold {
|
||||
weight = int64((upperThreshold - lowerThreshold) * float64(overallSum))
|
||||
} else {
|
||||
weight = int64((slidingRatio - lowerThreshold) * float64(overallSum))
|
||||
}
|
||||
} else if slidingRatio > upperThreshold {
|
||||
weight = int64((upperThreshold - previousRatio) * float64(overallSum))
|
||||
}
|
||||
sumWeight += weight
|
||||
// Votes with N values get stored with N-1 index, thus +1 here.
|
||||
sumValue += (int64(i) + 1) * weight
|
||||
}
|
||||
if sumValue == 0 || sumWeight == 0 {
|
||||
return 0
|
||||
}
|
||||
return int(sumValue / sumWeight)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"math/big"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
|
@ -17,7 +18,7 @@ type NEP5BalanceState struct {
|
|||
type NEOBalanceState struct {
|
||||
NEP5BalanceState
|
||||
BalanceHeight uint32
|
||||
Votes keys.PublicKeys
|
||||
VoteTo *keys.PublicKey
|
||||
}
|
||||
|
||||
// NEP5BalanceStateFromBytes converts serialized NEP5BalanceState to structure.
|
||||
|
@ -110,7 +111,11 @@ func (s *NEOBalanceState) DecodeBinary(r *io.BinReader) {
|
|||
func (s *NEOBalanceState) toStackItem() stackitem.Item {
|
||||
result := s.NEP5BalanceState.toStackItem().(*stackitem.Struct)
|
||||
result.Append(stackitem.NewBigInteger(big.NewInt(int64(s.BalanceHeight))))
|
||||
result.Append(stackitem.NewByteArray(s.Votes.Bytes()))
|
||||
if s.VoteTo != nil {
|
||||
result.Append(stackitem.NewByteArray(s.VoteTo.Bytes()))
|
||||
} else {
|
||||
result.Append(stackitem.Null{})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
@ -118,6 +123,18 @@ func (s *NEOBalanceState) fromStackItem(item stackitem.Item) error {
|
|||
structItem := item.Value().([]stackitem.Item)
|
||||
s.Balance = *structItem[0].Value().(*big.Int)
|
||||
s.BalanceHeight = uint32(structItem[1].Value().(*big.Int).Int64())
|
||||
s.Votes = make(keys.PublicKeys, 0)
|
||||
return s.Votes.DecodeBytes(structItem[2].Value().([]byte))
|
||||
if _, ok := structItem[2].(stackitem.Null); ok {
|
||||
s.VoteTo = nil
|
||||
return nil
|
||||
}
|
||||
bs, err := structItem[2].TryBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pub, err := keys.NewPublicKeyFromBytes(bs, elliptic.P256())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.VoteTo = pub
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1125,7 +1125,6 @@ func checkNep5Transfers(t *testing.T, e *executor, acc interface{}) {
|
|||
TxHash: b.Hash(),
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
require.Equal(t, expected.Address, res.Address)
|
||||
require.ElementsMatch(t, expected.Sent, res.Sent)
|
||||
|
|
Loading…
Reference in a new issue