core: implement skeletons for NEO/GAS native contracts

This commit is contained in:
Evgenii Stratonikov 2020-03-25 13:00:11 +03:00
parent 25354c44f9
commit 76700f31cf
9 changed files with 893 additions and 105 deletions

View file

@ -198,6 +198,9 @@ func (bc *Blockchain) init() error {
if err != nil {
return err
}
if err := bc.initNative(); err != nil {
return err
}
return bc.storeBlock(genesisBlock)
}
if ver != version {
@ -270,6 +273,27 @@ func (bc *Blockchain) init() error {
return nil
}
func (bc *Blockchain) initNative() error {
ic := bc.newInteropContext(trigger.Application, bc.dao, nil, nil)
gas := native.NewGAS()
neo := native.NewNEO()
neo.GAS = gas
gas.NEO = neo
if err := gas.Initialize(ic); err != nil {
return fmt.Errorf("can't initialize GAS native contract: %v", err)
}
if err := neo.Initialize(ic); err != nil {
return fmt.Errorf("can't initialize NEO native contract: %v", err)
}
bc.contracts.SetGAS(gas)
bc.contracts.SetNEO(neo)
return nil
}
// Run runs chain loop.
func (bc *Blockchain) Run() {
persistTimer := time.NewTimer(persistInterval)
@ -639,7 +663,7 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
return err
}
case *transaction.StateTX:
if err := processStateTX(cache, t); err != nil {
if err := bc.processStateTX(cache, tx, t); err != nil {
return err
}
case *transaction.PublishTX:
@ -887,21 +911,8 @@ func processTXWithValidatorsSubtract(output *transaction.Output, account *state.
// modAccountVotes adds given value to given account voted validators.
func modAccountVotes(account *state.Account, dao *dao.Cached, value util.Fixed8) error {
for _, vote := range account.Votes {
validator, err := dao.GetValidatorStateOrNew(vote)
if err != nil {
return err
}
validator.Votes += value
if validator.UnregisteredAndHasNoVotes() {
if err := dao.DeleteValidatorState(validator); err != nil {
return err
}
} else {
if err := dao.PutValidatorState(validator); err != nil {
return err
}
}
if err := native.ModifyAccountVotes(account, dao, value); err != nil {
return err
}
if len(account.Votes) > 0 {
vc, err := dao.GetValidatorsCount()
@ -937,55 +948,19 @@ func processValidatorStateDescriptor(descriptor *transaction.StateDescriptor, da
return nil
}
func processAccountStateDescriptor(descriptor *transaction.StateDescriptor, dao *dao.Cached) error {
func (bc *Blockchain) processAccountStateDescriptor(descriptor *transaction.StateDescriptor, t *transaction.Transaction, dao *dao.Cached) error {
hash, err := util.Uint160DecodeBytesBE(descriptor.Key)
if err != nil {
return err
}
account, err := dao.GetAccountStateOrNew(hash)
if err != nil {
return err
}
if descriptor.Field == "Votes" {
balance := account.GetBalanceValues()[GoverningTokenID()]
if err = modAccountVotes(account, dao, -balance); err != nil {
return err
}
votes := keys.PublicKeys{}
err := votes.DecodeBytes(descriptor.Value)
if err != nil {
if err := votes.DecodeBytes(descriptor.Value); err != nil {
return err
}
if len(votes) > state.MaxValidatorsVoted {
return errors.New("voting candidate limit exceeded")
}
if len(votes) > 0 {
account.Votes = votes
for _, vote := range account.Votes {
validatorState, err := dao.GetValidatorStateOrNew(vote)
if err != nil {
return err
}
validatorState.Votes += balance
if err = dao.PutValidatorState(validatorState); err != nil {
return err
}
}
vc, err := dao.GetValidatorsCount()
if err != nil {
return err
}
vc[len(account.Votes)-1] += balance
err = dao.PutValidatorsCount(vc)
if err != nil {
return err
}
} else {
account.Votes = nil
}
return dao.PutAccountState(account)
ic := bc.newInteropContext(trigger.Application, dao, nil, t)
return bc.contracts.NEO.VoteInternal(ic, hash, votes)
}
return nil
}
@ -1807,59 +1782,14 @@ func (bc *Blockchain) GetValidators(txes ...*transaction.Transaction) ([]*keys.P
return nil, err
}
case *transaction.StateTX:
if err := processStateTX(cache, t); err != nil {
if err := bc.processStateTX(cache, tx, t); err != nil {
return nil, err
}
}
}
}
validators := cache.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
})
validatorsCount, err := cache.GetValidatorsCount()
if err != nil {
return nil, err
}
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
return bc.contracts.NEO.GetValidatorsInternal(bc, cache)
}
// GetEnrollments returns all registered validators and non-registered SB validators
@ -1896,11 +1826,11 @@ func (bc *Blockchain) GetEnrollments() ([]*state.Validator, error) {
return result, nil
}
func processStateTX(dao *dao.Cached, tx *transaction.StateTX) error {
func (bc *Blockchain) processStateTX(dao *dao.Cached, t *transaction.Transaction, tx *transaction.StateTX) error {
for _, desc := range tx.Descriptors {
switch desc.Type {
case transaction.Account:
if err := processAccountStateDescriptor(desc, dao); err != nil {
if err := bc.processAccountStateDescriptor(desc, t, dao); err != nil {
return err
}
case transaction.Validator:

View file

@ -32,8 +32,10 @@ type DAO interface {
GetCurrentBlockHeight() (uint32, error)
GetCurrentHeaderHeight() (i uint32, h util.Uint256, err error)
GetHeaderHashes() ([]util.Uint256, error)
GetNativeContractState(h util.Uint160) ([]byte, error)
GetNEP5Balances(acc util.Uint160) (*state.NEP5Balances, error)
GetNEP5TransferLog(acc util.Uint160, index uint32) (*state.NEP5TransferLog, error)
GetNextBlockValidators() (keys.PublicKeys, error)
GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem
GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error)
GetTransaction(hash util.Uint256) (*transaction.Transaction, uint32, error)
@ -53,8 +55,10 @@ type DAO interface {
PutAssetState(as *state.Asset) error
PutContractState(cs *state.Contract) error
PutCurrentHeader(hashAndIndex []byte) error
PutNativeContractState(h util.Uint160, value []byte) error
PutNEP5Balances(acc util.Uint160, bs *state.NEP5Balances) error
PutNEP5TransferLog(acc util.Uint160, index uint32, lg *state.NEP5TransferLog) error
PutNextBlockValidators(keys.PublicKeys) error
PutStorageItem(scripthash util.Uint160, key []byte, si *state.StorageItem) error
PutUnspentCoinState(hash util.Uint256, ucs *state.UnspentCoin) error
PutValidatorState(vs *state.Validator) error
@ -207,6 +211,18 @@ func (dao *Simple) DeleteContractState(hash util.Uint160) error {
return dao.Store.Delete(key)
}
// GetNativeContractState retrieves native contract state from the store.
func (dao *Simple) GetNativeContractState(h util.Uint160) ([]byte, error) {
key := storage.AppendPrefix(storage.STNativeContract, h.BytesBE())
return dao.Store.Get(key)
}
// PutNativeContractState puts native contract state into the store.
func (dao *Simple) PutNativeContractState(h util.Uint160, value []byte) error {
key := storage.AppendPrefix(storage.STNativeContract, h.BytesBE())
return dao.Store.Put(key, value)
}
// -- end contracts.
// -- start nep5 balances.
@ -310,6 +326,38 @@ func (dao *Simple) putUnspentCoinState(hash util.Uint256, ucs *state.UnspentCoin
// -- start validator.
// GetNextBlockValidators retrieves next block validators from store or nil if they are missing.
func (dao *Simple) GetNextBlockValidators() (keys.PublicKeys, error) {
key := []byte{byte(storage.STNextValidators)}
buf, err := dao.Store.Get(key)
if err != nil {
if err == storage.ErrKeyNotFound {
return nil, nil
}
return nil, err
}
var pubs keys.PublicKeys
r := io.NewBinReaderFromBuf(buf)
r.ReadArray(&pubs)
if r.Err != nil {
return nil, r.Err
}
return pubs, nil
}
// PutNextBlockValidators puts next block validators to store.
func (dao *Simple) PutNextBlockValidators(pubs keys.PublicKeys) error {
w := io.NewBufBinWriter()
w.WriteArray(pubs)
if w.Err != nil {
return w.Err
}
key := []byte{byte(storage.STNextValidators)}
return dao.Store.Put(key, w.Bytes())
}
// GetValidatorStateOrNew gets validator from store or created new one in case of error.
func (dao *Simple) GetValidatorStateOrNew(publicKey *keys.PublicKey) (*state.Validator, error) {
validatorState, err := dao.GetValidatorState(publicKey)

View file

@ -42,9 +42,23 @@ type ContractMD struct {
// Contracts is a set of registered native contracts.
type Contracts struct {
NEO *NEO
GAS *GAS
Contracts []Contract
}
// SetGAS sets GAS native contract.
func (cs *Contracts) SetGAS(g *GAS) {
cs.GAS = g
cs.Contracts = append(cs.Contracts, g)
}
// SetNEO sets NEO native contract.
func (cs *Contracts) SetNEO(n *NEO) {
cs.NEO = n
cs.Contracts = append(cs.Contracts, n)
}
// NewContractMD returns Contract with the specified list of methods.
func NewContractMD(name string) *ContractMD {
c := &ContractMD{

View file

@ -0,0 +1,134 @@
package native
import (
"errors"
"math/big"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"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/hash"
"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"
)
// GAS represents GAS native contract.
type GAS struct {
nep5TokenNative
NEO *NEO
}
const gasSyscallName = "Neo.Native.Tokens.GAS"
// NewGAS returns GAS native contract.
func NewGAS() *GAS {
nep5 := newNEP5Native(gasSyscallName)
nep5.name = "GAS"
nep5.symbol = "gas"
nep5.decimals = 8
nep5.factor = 100000000
g := &GAS{nep5TokenNative: *nep5}
desc := newDescriptor("getSysFeeAmount", smartcontract.IntegerType,
manifest.NewParameter("index", smartcontract.IntegerType))
md := newMethodAndPrice(g.getSysFeeAmount, 1, smartcontract.NoneFlag)
g.AddMethod(md, desc, true)
g.onPersist = chainOnPersist(g.onPersist, g.OnPersist)
g.incBalance = g.increaseBalance
return g
}
// initFromStore initializes variable contract parameters from the store.
func (g *GAS) initFromStore(data []byte) error {
g.totalSupply = *emit.BytesToInt(data)
return nil
}
func (g *GAS) serializeState() []byte {
return emit.IntToBytes(&g.totalSupply)
}
func (g *GAS) increaseBalance(_ *interop.Context, acc *state.Account, amount *big.Int) error {
if sign := amount.Sign(); sign == 0 {
return nil
} else if sign == -1 && acc.GAS.Balance.Cmp(new(big.Int).Neg(amount)) == -1 {
return errors.New("insufficient funds")
}
acc.GAS.Balance.Add(&acc.GAS.Balance, amount)
return nil
}
// Initialize initializes GAS contract.
func (g *GAS) Initialize(ic *interop.Context) error {
data, err := ic.DAO.GetNativeContractState(g.Hash)
if err == nil {
return g.initFromStore(data)
} else if err != storage.ErrKeyNotFound {
return err
}
if err := g.nep5TokenNative.Initialize(); err != nil {
return err
}
h, _, err := getStandbyValidatorsHash(ic)
if err != nil {
return err
}
g.mint(ic, h, big.NewInt(30000000*g.factor))
return ic.DAO.PutNativeContractState(g.Hash, g.serializeState())
}
// OnPersist implements Contract interface.
func (g *GAS) OnPersist(ic *interop.Context) error {
//for _ ,tx := range ic.block.Transactions {
// g.burn(ic, tx.Sender, tx.SystemFee + tx.NetworkFee)
//}
//validators := g.NEO.getNextBlockValidators(ic)
//var netFee util.Fixed8
//for _, tx := range ic.block.Transactions {
// netFee += tx.NetworkFee
//}
//g.mint(ic, <primary>, netFee)
return ic.DAO.PutNativeContractState(g.Hash, g.serializeState())
}
func (g *GAS) getSysFeeAmount(ic *interop.Context, args []vm.StackItem) vm.StackItem {
index := toBigInt(args[0])
h := ic.Chain.GetHeaderHash(int(index.Int64()))
_, sf, err := ic.DAO.GetBlock(h)
if err != nil {
panic(err)
}
return vm.NewBigIntegerItem(big.NewInt(int64(sf)))
}
func getStandbyValidatorsHash(ic *interop.Context) (util.Uint160, []*keys.PublicKey, error) {
vs, err := ic.Chain.GetStandByValidators()
if err != nil {
return util.Uint160{}, nil, err
}
s, err := smartcontract.CreateMultiSigRedeemScript(len(vs)/2+1, vs)
if err != nil {
return util.Uint160{}, nil, err
}
return hash.Hash160(s), vs, nil
}
func chainOnPersist(fs ...func(*interop.Context) error) func(*interop.Context) error {
return func(ic *interop.Context) error {
for i := range fs {
if fs[i] != nil {
if err := fs[i](ic); err != nil {
return err
}
}
}
return nil
}
}

View file

@ -0,0 +1,380 @@
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
}

View file

@ -0,0 +1,229 @@
package native
import (
"errors"
"math/big"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"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"
)
// nep5TokenNative represents NEP-5 token contract.
type nep5TokenNative struct {
ContractMD
name string
symbol string
decimals int64
factor int64
totalSupply big.Int
onPersist func(*interop.Context) error
incBalance func(*interop.Context, *state.Account, *big.Int) error
}
func (c *nep5TokenNative) Metadata() *ContractMD {
return &c.ContractMD
}
var _ Contract = (*nep5TokenNative)(nil)
func newNEP5Native(name string) *nep5TokenNative {
n := &nep5TokenNative{ContractMD: *NewContractMD(name)}
desc := newDescriptor("name", smartcontract.StringType)
md := newMethodAndPrice(n.Name, 1, smartcontract.NoneFlag)
n.AddMethod(md, desc, true)
desc = newDescriptor("symbol", smartcontract.StringType)
md = newMethodAndPrice(n.Symbol, 1, smartcontract.NoneFlag)
n.AddMethod(md, desc, true)
desc = newDescriptor("decimals", smartcontract.IntegerType)
md = newMethodAndPrice(n.Decimals, 1, smartcontract.NoneFlag)
n.AddMethod(md, desc, true)
desc = newDescriptor("balanceOf", smartcontract.IntegerType,
manifest.NewParameter("account", smartcontract.Hash160Type))
md = newMethodAndPrice(n.balanceOf, 1, smartcontract.NoneFlag)
n.AddMethod(md, desc, true)
desc = newDescriptor("transfer", smartcontract.BoolType,
manifest.NewParameter("from", smartcontract.Hash160Type),
manifest.NewParameter("to", smartcontract.Hash160Type),
manifest.NewParameter("amount", smartcontract.IntegerType),
)
md = newMethodAndPrice(n.Transfer, 1, smartcontract.NoneFlag)
n.AddMethod(md, desc, false)
n.AddEvent("Transfer", desc.Parameters...)
return n
}
func (c *nep5TokenNative) Initialize() error {
return nil
}
func (c *nep5TokenNative) Name(_ *interop.Context, _ []vm.StackItem) vm.StackItem {
return vm.NewByteArrayItem([]byte(c.name))
}
func (c *nep5TokenNative) Symbol(_ *interop.Context, _ []vm.StackItem) vm.StackItem {
return vm.NewByteArrayItem([]byte(c.symbol))
}
func (c *nep5TokenNative) Decimals(_ *interop.Context, _ []vm.StackItem) vm.StackItem {
return vm.NewBigIntegerItem(big.NewInt(c.decimals))
}
func (c *nep5TokenNative) Transfer(ic *interop.Context, args []vm.StackItem) vm.StackItem {
from := toUint160(args[0])
to := toUint160(args[1])
amount := toBigInt(args[2])
err := c.transfer(ic, from, to, amount)
return vm.NewBoolItem(err == nil)
}
func addrToStackItem(u *util.Uint160) vm.StackItem {
if u == nil {
return nil
}
return vm.NewByteArrayItem(u.BytesBE())
}
func (c *nep5TokenNative) emitTransfer(ic *interop.Context, from, to *util.Uint160, amount *big.Int) {
ne := state.NotificationEvent{
ScriptHash: c.Hash,
Item: vm.NewArrayItem([]vm.StackItem{
vm.NewByteArrayItem([]byte("Transfer")),
addrToStackItem(from),
addrToStackItem(to),
vm.NewBigIntegerItem(amount),
}),
}
ic.Notifications = append(ic.Notifications, ne)
}
func (c *nep5TokenNative) transfer(ic *interop.Context, from, to util.Uint160, amount *big.Int) error {
if amount.Sign() == -1 {
return errors.New("negative amount")
}
accFrom, err := ic.DAO.GetAccountStateOrNew(from)
if err != nil {
return err
}
isEmpty := from.Equals(to) || amount.Sign() == 0
inc := amount
if isEmpty {
inc = big.NewInt(0)
}
if err := c.incBalance(ic, accFrom, inc); err != nil {
return err
}
if err := ic.DAO.PutAccountState(accFrom); err != nil {
return err
}
if !isEmpty {
accTo, err := ic.DAO.GetAccountStateOrNew(to)
if err != nil {
return err
}
if err := c.incBalance(ic, accTo, amount); err != nil {
return err
}
if err := ic.DAO.PutAccountState(accTo); err != nil {
return err
}
}
c.emitTransfer(ic, &from, &to, amount)
return nil
}
func (c *nep5TokenNative) balanceOf(ic *interop.Context, args []vm.StackItem) vm.StackItem {
h := toUint160(args[0])
bs, err := ic.DAO.GetNEP5Balances(h)
if err != nil {
panic(err)
}
balance := bs.Trackers[c.Hash].Balance
return vm.NewBigIntegerItem(big.NewInt(balance))
}
func (c *nep5TokenNative) mint(ic *interop.Context, h util.Uint160, amount *big.Int) {
c.addTokens(ic, h, amount)
c.emitTransfer(ic, nil, &h, amount)
}
func (c *nep5TokenNative) burn(ic *interop.Context, h util.Uint160, amount *big.Int) {
amount = new(big.Int).Neg(amount)
c.addTokens(ic, h, amount)
c.emitTransfer(ic, &h, nil, amount)
}
func (c *nep5TokenNative) addTokens(ic *interop.Context, h util.Uint160, amount *big.Int) {
if sign := amount.Sign(); sign == -1 {
panic("negative amount")
} else if sign == 0 {
return
}
acc, err := ic.DAO.GetAccountStateOrNew(h)
if err != nil {
panic(err)
}
if err := c.incBalance(ic, acc, amount); err != nil {
panic(err)
}
if err := ic.DAO.PutAccountState(acc); err != nil {
panic(err)
}
c.totalSupply.Add(&c.totalSupply, amount)
}
func (c *nep5TokenNative) OnPersist(ic *interop.Context) error {
return c.onPersist(ic)
}
func newDescriptor(name string, ret smartcontract.ParamType, ps ...manifest.Parameter) *manifest.Method {
return &manifest.Method{
Name: name,
Parameters: ps,
ReturnType: ret,
}
}
func newMethodAndPrice(f Method, price int64, flags smartcontract.CallFlag) *MethodAndPrice {
return &MethodAndPrice{
Func: f,
Price: price,
RequiredFlags: flags,
}
}
func toBigInt(s vm.StackItem) *big.Int {
bi, err := s.TryInteger()
if err != nil {
panic(err)
}
return bi
}
func toUint160(s vm.StackItem) util.Uint160 {
buf, err := s.TryBytes()
if err != nil {
panic(err)
}
u, err := util.Uint160DecodeBytesBE(buf)
if err != nil {
panic(err)
}
return u
}

View file

@ -33,6 +33,8 @@ type Account struct {
ScriptHash util.Uint160
IsFrozen bool
Votes []*keys.PublicKey
GAS NEP5BalanceState
NEO NEOBalanceState
Balances map[util.Uint256][]UnspentBalance
Unclaimed UnclaimedBalances
}
@ -55,6 +57,8 @@ func (s *Account) DecodeBinary(br *io.BinReader) {
br.ReadBytes(s.ScriptHash[:])
s.IsFrozen = br.ReadBool()
br.ReadArray(&s.Votes)
s.GAS.DecodeBinary(br)
s.NEO.DecodeBinary(br)
s.Balances = make(map[util.Uint256][]UnspentBalance)
lenBalances := br.ReadVarUint()
@ -80,6 +84,8 @@ func (s *Account) EncodeBinary(bw *io.BinWriter) {
bw.WriteBytes(s.ScriptHash[:])
bw.WriteBool(s.IsFrozen)
bw.WriteArray(s.Votes)
s.GAS.EncodeBinary(bw)
s.NEO.EncodeBinary(bw)
bw.WriteVarUint(uint64(len(s.Balances)))
for k, v := range s.Balances {

View file

@ -0,0 +1,45 @@
package state
import (
"math/big"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
)
// NEP5BalanceState represents balance state of a NEP5-token.
type NEP5BalanceState struct {
Balance big.Int
}
// NEOBalanceState represents balance state of a NEO-token.
type NEOBalanceState struct {
NEP5BalanceState
BalanceHeight uint32
}
// EncodeBinary implements io.Serializable interface.
func (s *NEP5BalanceState) EncodeBinary(w *io.BinWriter) {
w.WriteVarBytes(emit.IntToBytes(&s.Balance))
}
// DecodeBinary implements io.Serializable interface.
func (s *NEP5BalanceState) DecodeBinary(r *io.BinReader) {
buf := r.ReadVarBytes()
if r.Err != nil {
return
}
s.Balance = *emit.BytesToInt(buf)
}
// EncodeBinary implements io.Serializable interface.
func (s *NEOBalanceState) EncodeBinary(w *io.BinWriter) {
s.NEP5BalanceState.EncodeBinary(w)
w.WriteU32LE(s.BalanceHeight)
}
// DecodeBinary implements io.Serializable interface.
func (s *NEOBalanceState) DecodeBinary(r *io.BinReader) {
s.NEP5BalanceState.DecodeBinary(r)
s.BalanceHeight = r.ReadU32LE()
}

View file

@ -12,10 +12,12 @@ const (
STAccount KeyPrefix = 0x40
STCoin KeyPrefix = 0x44
STSpentCoin KeyPrefix = 0x45
STNextValidators KeyPrefix = 0x47
STValidator KeyPrefix = 0x48
STAsset KeyPrefix = 0x4c
STNotification KeyPrefix = 0x4d
STContract KeyPrefix = 0x50
STNativeContract KeyPrefix = 0x51
STStorage KeyPrefix = 0x70
STNEP5Transfers KeyPrefix = 0x72
STNEP5Balances KeyPrefix = 0x73