forked from TrueCloudLab/neoneo-go
c7a040ff81
`NewNEO()` and `NewGAS()` methods are trying to initialise both `onPersist` and `incBalance` methods of NEO and GAS AFTER nep5TokenNative is set to the VALUE of created nep5 token. In this situation an attemmpt to call the corresponding native contracts methods (e.g. transfer native GAS) leads to contract invocation failure, as far as `nep5TokenNative.incBalance` method is nil. Fixed this by initializing both `onPersist` and `incBalance` methods before getting the value of nep5 contract.
502 lines
14 KiB
Go
502 lines
14 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/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/pkg/errors"
|
|
)
|
|
|
|
// NEO represents NEO native contract.
|
|
type NEO struct {
|
|
nep5TokenNative
|
|
GAS *GAS
|
|
}
|
|
|
|
// keyWithVotes is a serialized key with votes balance. It's not deserialized
|
|
// because some uses of it imply serialized-only usage and converting to
|
|
// PublicKey is quite expensive.
|
|
type keyWithVotes struct {
|
|
Key string
|
|
Votes *big.Int
|
|
}
|
|
|
|
const (
|
|
neoSyscallName = "Neo.Native.Tokens.NEO"
|
|
// NEOTotalSupply is the total amount of NEO in the system.
|
|
NEOTotalSupply = 100000000
|
|
// prefixValidator is a prefix used to store validator's data.
|
|
prefixValidator = 33
|
|
)
|
|
|
|
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}
|
|
)
|
|
|
|
// makeValidatorKey creates a key from account script hash.
|
|
func makeValidatorKey(key *keys.PublicKey) []byte {
|
|
b := key.Bytes()
|
|
// Don't create a new buffer.
|
|
b = append(b, 0)
|
|
copy(b[1:], b[0:])
|
|
b[0] = prefixValidator
|
|
return b
|
|
}
|
|
|
|
// NewNEO returns NEO native contract.
|
|
func NewNEO() *NEO {
|
|
n := &NEO{}
|
|
nep5 := newNEP5Native(neoSyscallName)
|
|
nep5.name = "NEO"
|
|
nep5.symbol = "neo"
|
|
nep5.decimals = 0
|
|
nep5.factor = 1
|
|
nep5.onPersist = chainOnPersist(n.onPersist, n.OnPersist)
|
|
nep5.incBalance = n.increaseBalance
|
|
|
|
n.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.getRegisteredValidatorsCall, 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)
|
|
|
|
return n
|
|
}
|
|
|
|
// Initialize initializes NEO contract.
|
|
func (n *NEO) Initialize(ic *interop.Context) error {
|
|
var si state.StorageItem
|
|
|
|
if err := n.nep5TokenNative.Initialize(ic); err != nil {
|
|
return err
|
|
}
|
|
|
|
if n.nep5TokenNative.getTotalSupply(ic).Sign() != 0 {
|
|
return errors.New("already initialized")
|
|
}
|
|
|
|
vc := new(ValidatorsCount)
|
|
si.Value = vc.Bytes()
|
|
if err := ic.DAO.PutStorageItem(n.Hash, validatorsCountKey, &si); err != nil {
|
|
return err
|
|
}
|
|
h, vs, err := getStandbyValidatorsHash(ic)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
n.mint(ic, h, big.NewInt(NEOTotalSupply))
|
|
|
|
for i := range vs {
|
|
if err := n.registerValidatorInternal(ic, vs[i]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// 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
|
|
}
|
|
si := new(state.StorageItem)
|
|
si.Value = pubs.Bytes()
|
|
return ic.DAO.PutStorageItem(n.Hash, nextValidatorsKey, si)
|
|
}
|
|
|
|
func (n *NEO) increaseBalance(ic *interop.Context, h util.Uint160, si *state.StorageItem, amount *big.Int) error {
|
|
acc, err := state.NEOBalanceStateFromBytes(si.Value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if amount.Sign() == -1 && acc.Balance.Cmp(new(big.Int).Neg(amount)) == -1 {
|
|
return errors.New("insufficient funds")
|
|
}
|
|
if err := n.distributeGas(ic, h, acc); err != nil {
|
|
return err
|
|
}
|
|
if amount.Sign() == 0 {
|
|
return nil
|
|
}
|
|
if len(acc.Votes) > 0 {
|
|
if err := n.ModifyAccountVotes(acc, ic.DAO, new(big.Int).Neg(&acc.Balance)); err != nil {
|
|
return err
|
|
}
|
|
siVC := ic.DAO.GetStorageItem(n.Hash, 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.Hash, validatorsCountKey, siVC); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
acc.Balance.Add(&acc.Balance, amount)
|
|
si.Value = acc.Bytes()
|
|
return nil
|
|
}
|
|
|
|
func (n *NEO) distributeGas(ic *interop.Context, h util.Uint160, acc *state.NEOBalanceState) error {
|
|
if ic.Block == nil || ic.Block.Index == 0 {
|
|
return nil
|
|
}
|
|
sys, net, err := ic.Chain.CalculateClaimable(util.Fixed8(acc.Balance.Int64()), acc.BalanceHeight, ic.Block.Index)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
acc.BalanceHeight = ic.Block.Index
|
|
n.GAS.mint(ic, h, 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 {
|
|
key := makeValidatorKey(pub)
|
|
si := ic.DAO.GetStorageItem(n.Hash, key)
|
|
if si != nil {
|
|
return errors.New("already registered")
|
|
}
|
|
si = new(state.StorageItem)
|
|
// It's the same simple counter, calling it `Votes` instead of `Balance`
|
|
// doesn't help a lot.
|
|
votes := state.NEP5BalanceState{}
|
|
si.Value = votes.Bytes()
|
|
return ic.DAO.PutStorageItem(n.Hash, key, si)
|
|
}
|
|
|
|
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, neoScriptHash{hash: n.Hash}, h)
|
|
if err != nil {
|
|
return err
|
|
} else if !ok {
|
|
return errors.New("invalid signature")
|
|
}
|
|
key := makeAccountKey(h)
|
|
si := ic.DAO.GetStorageItem(n.Hash, key)
|
|
if si == nil {
|
|
return errors.New("invalid account")
|
|
}
|
|
acc, err := state.NEOBalanceStateFromBytes(si.Value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := n.ModifyAccountVotes(acc, ic.DAO, new(big.Int).Neg(&acc.Balance)); err != nil {
|
|
return err
|
|
}
|
|
pubs = pubs.Unique()
|
|
// Check validators registration.
|
|
var newPubs keys.PublicKeys
|
|
for _, pub := range pubs {
|
|
if ic.DAO.GetStorageItem(n.Hash, makeValidatorKey(pub)) == nil {
|
|
continue
|
|
}
|
|
newPubs = append(newPubs, pub)
|
|
}
|
|
if lp, lv := len(newPubs), len(acc.Votes); lp != lv {
|
|
si := ic.DAO.GetStorageItem(n.Hash, validatorsCountKey)
|
|
if si == nil {
|
|
return errors.New("validators count uninitialized")
|
|
}
|
|
vc, err := ValidatorsCountFromBytes(si.Value)
|
|
if 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.Hash, validatorsCountKey, si); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
acc.Votes = newPubs
|
|
if err := n.ModifyAccountVotes(acc, ic.DAO, &acc.Balance); err != nil {
|
|
return err
|
|
}
|
|
si.Value = acc.Bytes()
|
|
return ic.DAO.PutStorageItem(n.Hash, key, si)
|
|
}
|
|
|
|
// 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)
|
|
si := d.GetStorageItem(n.Hash, key)
|
|
if si == nil {
|
|
return errors.New("invalid validator")
|
|
}
|
|
votes, err := state.NEP5BalanceStateFromBytes(si.Value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
votes.Balance.Add(&votes.Balance, value)
|
|
si.Value = votes.Bytes()
|
|
if err := d.PutStorageItem(n.Hash, key, si); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (n *NEO) getRegisteredValidators(d dao.DAO) ([]keyWithVotes, error) {
|
|
siMap, err := d.GetStorageItemsWithPrefix(n.Hash, []byte{prefixValidator})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
arr := make([]keyWithVotes, 0, len(siMap))
|
|
for key, si := range siMap {
|
|
votes, err := state.NEP5BalanceStateFromBytes(si.Value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
arr = append(arr, keyWithVotes{key, &votes.Balance})
|
|
}
|
|
return arr, nil
|
|
}
|
|
|
|
// GetRegisteredValidators returns current registered validators list with keys
|
|
// and votes.
|
|
func (n *NEO) GetRegisteredValidators(d dao.DAO) ([]state.Validator, error) {
|
|
kvs, err := n.getRegisteredValidators(d)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
arr := make([]state.Validator, len(kvs))
|
|
for i := range kvs {
|
|
arr[i].Key, err = keys.NewPublicKeyFromBytes([]byte(kvs[i].Key))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
arr[i].Votes = kvs[i].Votes
|
|
}
|
|
return arr, nil
|
|
}
|
|
|
|
func (n *NEO) getRegisteredValidatorsCall(ic *interop.Context, _ []vm.StackItem) vm.StackItem {
|
|
validators, err := n.getRegisteredValidators(ic.DAO)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
arr := make([]vm.StackItem, len(validators))
|
|
for i := range validators {
|
|
arr[i] = vm.NewStructItem([]vm.StackItem{
|
|
vm.NewByteArrayItem([]byte(validators[i].Key)),
|
|
vm.NewBigIntegerItem(validators[i].Votes),
|
|
})
|
|
}
|
|
return vm.NewArrayItem(arr)
|
|
}
|
|
|
|
// GetValidatorsInternal returns a list of current validators.
|
|
func (n *NEO) GetValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) {
|
|
si := d.GetStorageItem(n.Hash, validatorsCountKey)
|
|
if si == nil {
|
|
return nil, errors.New("validators count uninitialized")
|
|
}
|
|
validatorsCount, err := ValidatorsCountFromBytes(si.Value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
validators, err := n.GetRegisteredValidators(d)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
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()
|
|
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.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)
|
|
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.PublicKeys, error) {
|
|
si := d.GetStorageItem(n.Hash, nextValidatorsKey)
|
|
if si == nil {
|
|
return n.GetValidatorsInternal(bc, d)
|
|
}
|
|
pubs := keys.PublicKeys{}
|
|
err := pubs.DecodeBytes(si.Value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return pubs, 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
|
|
}
|
|
|
|
// scriptHash is an auxiliary structure which implements ScriptHashGetter
|
|
// interface over NEO native contract and is used for runtime.CheckHashedWitness
|
|
type neoScriptHash struct {
|
|
hash util.Uint160
|
|
}
|
|
|
|
// GetCallingScriptHash implements ScriptHashGetter interface
|
|
func (s neoScriptHash) GetCallingScriptHash() util.Uint160 {
|
|
return util.Uint160{}
|
|
}
|
|
|
|
// GetEntryScriptHash implements ScriptHashGetter interface
|
|
func (s neoScriptHash) GetEntryScriptHash() util.Uint160 {
|
|
return s.hash
|
|
}
|
|
|
|
// GetCurrentScriptHash implements ScriptHashGetter interface
|
|
func (s neoScriptHash) GetCurrentScriptHash() util.Uint160 {
|
|
return s.hash
|
|
}
|