380 lines
10 KiB
Go
380 lines
10 KiB
Go
package native
|
|
|
|
import (
|
|
"math/big"
|
|
"sort"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/runtime"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
|
"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/pkg/errors"
|
|
)
|
|
|
|
// NEO represents NEO native contract.
|
|
type NEO struct {
|
|
nep5TokenNative
|
|
GAS *GAS
|
|
}
|
|
|
|
const neoSyscallName = "Neo.Native.Tokens.NEO"
|
|
|
|
// NewNEO returns NEO native contract.
|
|
func NewNEO() *NEO {
|
|
nep5 := newNEP5Native(neoSyscallName)
|
|
nep5.name = "NEO"
|
|
nep5.symbol = "neo"
|
|
nep5.decimals = 0
|
|
nep5.factor = 1
|
|
|
|
n := &NEO{nep5TokenNative: *nep5}
|
|
|
|
desc := newDescriptor("unclaimedGas", smartcontract.IntegerType,
|
|
manifest.NewParameter("account", smartcontract.Hash160Type),
|
|
manifest.NewParameter("end", smartcontract.IntegerType))
|
|
md := newMethodAndPrice(n.unclaimedGas, 1, smartcontract.NoneFlag)
|
|
n.AddMethod(md, desc, true)
|
|
|
|
desc = newDescriptor("registerValidator", smartcontract.BoolType,
|
|
manifest.NewParameter("pubkey", smartcontract.PublicKeyType))
|
|
md = newMethodAndPrice(n.registerValidator, 1, smartcontract.NoneFlag)
|
|
n.AddMethod(md, desc, false)
|
|
|
|
desc = newDescriptor("vote", smartcontract.BoolType,
|
|
manifest.NewParameter("account", smartcontract.Hash160Type),
|
|
manifest.NewParameter("pubkeys", smartcontract.ArrayType))
|
|
md = newMethodAndPrice(n.vote, 1, smartcontract.NoneFlag)
|
|
n.AddMethod(md, desc, false)
|
|
|
|
desc = newDescriptor("getRegisteredValidators", smartcontract.ArrayType)
|
|
md = newMethodAndPrice(n.getRegisteredValidators, 1, smartcontract.NoneFlag)
|
|
n.AddMethod(md, desc, true)
|
|
|
|
desc = newDescriptor("getValidators", smartcontract.ArrayType)
|
|
md = newMethodAndPrice(n.getValidators, 1, smartcontract.NoneFlag)
|
|
n.AddMethod(md, desc, true)
|
|
|
|
desc = newDescriptor("getNextBlockValidators", smartcontract.ArrayType)
|
|
md = newMethodAndPrice(n.getNextBlockValidators, 1, smartcontract.NoneFlag)
|
|
n.AddMethod(md, desc, true)
|
|
|
|
n.onPersist = chainOnPersist(n.onPersist, n.OnPersist)
|
|
n.incBalance = n.increaseBalance
|
|
|
|
return n
|
|
}
|
|
|
|
// Initialize initializes NEO contract.
|
|
func (n *NEO) Initialize(ic *interop.Context) error {
|
|
data, err := ic.DAO.GetNativeContractState(n.Hash)
|
|
if err == nil {
|
|
return n.initFromStore(data)
|
|
} else if err != storage.ErrKeyNotFound {
|
|
return err
|
|
}
|
|
|
|
if err := n.nep5TokenNative.Initialize(); err != nil {
|
|
return err
|
|
}
|
|
|
|
h, vs, err := getStandbyValidatorsHash(ic)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
n.mint(ic, h, big.NewInt(100000000*n.factor))
|
|
|
|
for i := range vs {
|
|
if err := n.registerValidatorInternal(ic, vs[i]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return ic.DAO.PutNativeContractState(n.Hash, n.serializeState())
|
|
}
|
|
|
|
// initFromStore initializes variable contract parameters from the store.
|
|
func (n *NEO) initFromStore(data []byte) error {
|
|
n.totalSupply = *emit.BytesToInt(data)
|
|
return nil
|
|
}
|
|
|
|
func (n *NEO) serializeState() []byte {
|
|
return emit.IntToBytes(&n.totalSupply)
|
|
}
|
|
|
|
// OnPersist implements Contract interface.
|
|
func (n *NEO) OnPersist(ic *interop.Context) error {
|
|
pubs, err := n.GetValidatorsInternal(ic.Chain, ic.DAO)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := ic.DAO.PutNextBlockValidators(pubs); err != nil {
|
|
return err
|
|
}
|
|
return ic.DAO.PutNativeContractState(n.Hash, n.serializeState())
|
|
}
|
|
|
|
func (n *NEO) increaseBalance(ic *interop.Context, acc *state.Account, amount *big.Int) error {
|
|
if sign := amount.Sign(); sign == 0 {
|
|
return nil
|
|
} else if sign == -1 && acc.NEO.Balance.Cmp(new(big.Int).Neg(amount)) == -1 {
|
|
return errors.New("insufficient funds")
|
|
}
|
|
if err := n.distributeGas(ic, acc); err != nil {
|
|
return err
|
|
}
|
|
acc.NEO.Balance.Add(&acc.NEO.Balance, amount)
|
|
return nil
|
|
}
|
|
|
|
func (n *NEO) distributeGas(ic *interop.Context, acc *state.Account) error {
|
|
if ic.Block == nil {
|
|
return nil
|
|
}
|
|
sys, net, err := ic.Chain.CalculateClaimable(util.Fixed8(acc.NEO.Balance.Int64()), acc.NEO.BalanceHeight, ic.Block.Index)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
acc.NEO.BalanceHeight = ic.Block.Index
|
|
n.GAS.mint(ic, acc.ScriptHash, big.NewInt(int64(sys+net)))
|
|
return nil
|
|
}
|
|
|
|
func (n *NEO) unclaimedGas(ic *interop.Context, args []vm.StackItem) vm.StackItem {
|
|
u := toUint160(args[0])
|
|
end := uint32(toBigInt(args[1]).Int64())
|
|
bs, err := ic.DAO.GetNEP5Balances(u)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
tr := bs.Trackers[n.Hash]
|
|
|
|
sys, net, err := ic.Chain.CalculateClaimable(util.Fixed8(tr.Balance), tr.LastUpdatedBlock, end)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return vm.NewBigIntegerItem(big.NewInt(int64(sys.Add(net))))
|
|
}
|
|
|
|
func (n *NEO) registerValidator(ic *interop.Context, args []vm.StackItem) vm.StackItem {
|
|
err := n.registerValidatorInternal(ic, toPublicKey(args[0]))
|
|
return vm.NewBoolItem(err == nil)
|
|
}
|
|
|
|
func (n *NEO) registerValidatorInternal(ic *interop.Context, pub *keys.PublicKey) error {
|
|
_, err := ic.DAO.GetValidatorState(pub)
|
|
if err == nil {
|
|
return err
|
|
}
|
|
return ic.DAO.PutValidatorState(&state.Validator{PublicKey: pub})
|
|
}
|
|
|
|
func (n *NEO) vote(ic *interop.Context, args []vm.StackItem) vm.StackItem {
|
|
acc := toUint160(args[0])
|
|
arr := args[1].Value().([]vm.StackItem)
|
|
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)
|
|
}
|
|
pubs = append(pubs, pub)
|
|
}
|
|
err := n.VoteInternal(ic, acc, pubs)
|
|
return vm.NewBoolItem(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 {
|
|
ok, err := runtime.CheckHashedWitness(ic, h)
|
|
if err != nil {
|
|
return err
|
|
} else if !ok {
|
|
return errors.New("invalid signature")
|
|
}
|
|
acc, err := ic.DAO.GetAccountState(h)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
balance := util.Fixed8(acc.NEO.Balance.Int64())
|
|
if err := ModifyAccountVotes(acc, ic.DAO, -balance); err != nil {
|
|
return err
|
|
}
|
|
pubs = pubs.Unique()
|
|
var newPubs keys.PublicKeys
|
|
for _, pub := range pubs {
|
|
_, err := ic.DAO.GetValidatorState(pub)
|
|
if err != nil {
|
|
if err == storage.ErrKeyNotFound {
|
|
continue
|
|
}
|
|
return err
|
|
}
|
|
newPubs = append(newPubs, pub)
|
|
}
|
|
if lp, lv := len(newPubs), len(acc.Votes); lp != lv {
|
|
vc, err := ic.DAO.GetValidatorsCount()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if lv > 0 {
|
|
vc[lv-1] -= balance
|
|
}
|
|
if len(newPubs) > 0 {
|
|
vc[lp-1] += balance
|
|
}
|
|
if err := ic.DAO.PutValidatorsCount(vc); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
acc.Votes = newPubs
|
|
return ModifyAccountVotes(acc, ic.DAO, balance)
|
|
}
|
|
|
|
// ModifyAccountVotes modifies votes of the specified account by value (can be negative).
|
|
func ModifyAccountVotes(acc *state.Account, d dao.DAO, value util.Fixed8) error {
|
|
for _, vote := range acc.Votes {
|
|
validator, err := d.GetValidatorStateOrNew(vote)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
validator.Votes += value
|
|
if validator.UnregisteredAndHasNoVotes() {
|
|
if err := d.DeleteValidatorState(validator); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if err := d.PutValidatorState(validator); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (n *NEO) getRegisteredValidators(ic *interop.Context, _ []vm.StackItem) vm.StackItem {
|
|
vs := ic.DAO.GetValidators()
|
|
arr := make([]vm.StackItem, len(vs))
|
|
for i := range vs {
|
|
arr[i] = vm.NewStructItem([]vm.StackItem{
|
|
vm.NewByteArrayItem(vs[i].PublicKey.Bytes()),
|
|
vm.NewBigIntegerItem(big.NewInt(int64(vs[i].Votes))),
|
|
})
|
|
}
|
|
return vm.NewArrayItem(arr)
|
|
}
|
|
|
|
// GetValidatorsInternal returns a list of current validators.
|
|
func (n *NEO) GetValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) ([]*keys.PublicKey, error) {
|
|
validatorsCount, err := d.GetValidatorsCount()
|
|
if err != nil {
|
|
return nil, err
|
|
} else if len(validatorsCount) == 0 {
|
|
sb, err := bc.GetStandByValidators()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return sb, nil
|
|
}
|
|
|
|
validators := d.GetValidators()
|
|
sort.Slice(validators, func(i, j int) bool {
|
|
// Unregistered validators go to the end of the list.
|
|
if validators[i].Registered != validators[j].Registered {
|
|
return validators[i].Registered
|
|
}
|
|
// The most-voted validators should end up in the front of the list.
|
|
if validators[i].Votes != validators[j].Votes {
|
|
return validators[i].Votes > validators[j].Votes
|
|
}
|
|
// Ties are broken with public keys.
|
|
return validators[i].PublicKey.Cmp(validators[j].PublicKey) == -1
|
|
})
|
|
|
|
count := validatorsCount.GetWeightedAverage()
|
|
standByValidators, err := bc.GetStandByValidators()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if count < len(standByValidators) {
|
|
count = len(standByValidators)
|
|
}
|
|
|
|
uniqueSBValidators := standByValidators.Unique()
|
|
result := keys.PublicKeys{}
|
|
for _, validator := range validators {
|
|
if validator.RegisteredAndHasVotes() || uniqueSBValidators.Contains(validator.PublicKey) {
|
|
result = append(result, validator.PublicKey)
|
|
}
|
|
}
|
|
|
|
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)
|
|
return result, nil
|
|
}
|
|
|
|
func (n *NEO) getValidators(ic *interop.Context, _ []vm.StackItem) vm.StackItem {
|
|
result, err := n.GetValidatorsInternal(ic.Chain, ic.DAO)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return pubsToArray(result)
|
|
}
|
|
|
|
func (n *NEO) getNextBlockValidators(ic *interop.Context, _ []vm.StackItem) vm.StackItem {
|
|
result, err := n.GetNextBlockValidatorsInternal(ic.Chain, ic.DAO)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return pubsToArray(result)
|
|
}
|
|
|
|
// GetNextBlockValidatorsInternal returns next block validators.
|
|
func (n *NEO) GetNextBlockValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) ([]*keys.PublicKey, error) {
|
|
result, err := d.GetNextBlockValidators()
|
|
if err != nil {
|
|
return nil, err
|
|
} else if result == nil {
|
|
return bc.GetStandByValidators()
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func pubsToArray(pubs keys.PublicKeys) vm.StackItem {
|
|
arr := make([]vm.StackItem, len(pubs))
|
|
for i := range pubs {
|
|
arr[i] = vm.NewByteArrayItem(pubs[i].Bytes())
|
|
}
|
|
return vm.NewArrayItem(arr)
|
|
}
|
|
|
|
func toPublicKey(s vm.StackItem) *keys.PublicKey {
|
|
buf, err := s.TryBytes()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
pub := new(keys.PublicKey)
|
|
if err := pub.DecodeBytes(buf); err != nil {
|
|
panic(err)
|
|
}
|
|
return pub
|
|
}
|