From 76700f31cf004c40961870e8bc01418a9d22b49e Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 25 Mar 2020 13:00:11 +0300 Subject: [PATCH] core: implement skeletons for NEO/GAS native contracts --- pkg/core/blockchain.go | 140 +++--------- pkg/core/dao/dao.go | 48 +++++ pkg/core/native/contract.go | 14 ++ pkg/core/native/native_gas.go | 134 ++++++++++++ pkg/core/native/native_neo.go | 380 +++++++++++++++++++++++++++++++++ pkg/core/native/native_nep5.go | 229 ++++++++++++++++++++ pkg/core/state/account.go | 6 + pkg/core/state/native_state.go | 45 ++++ pkg/core/storage/store.go | 2 + 9 files changed, 893 insertions(+), 105 deletions(-) create mode 100644 pkg/core/native/native_gas.go create mode 100644 pkg/core/native/native_neo.go create mode 100644 pkg/core/native/native_nep5.go create mode 100644 pkg/core/state/native_state.go diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index d8a9509ea..441c0f909 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -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: diff --git a/pkg/core/dao/dao.go b/pkg/core/dao/dao.go index 88a0b9981..c5a13a338 100644 --- a/pkg/core/dao/dao.go +++ b/pkg/core/dao/dao.go @@ -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) diff --git a/pkg/core/native/contract.go b/pkg/core/native/contract.go index f68556ded..150fb4ee8 100644 --- a/pkg/core/native/contract.go +++ b/pkg/core/native/contract.go @@ -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{ diff --git a/pkg/core/native/native_gas.go b/pkg/core/native/native_gas.go new file mode 100644 index 000000000..87736f0de --- /dev/null +++ b/pkg/core/native/native_gas.go @@ -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, , 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 + } +} diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go new file mode 100644 index 000000000..71498a187 --- /dev/null +++ b/pkg/core/native/native_neo.go @@ -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 +} diff --git a/pkg/core/native/native_nep5.go b/pkg/core/native/native_nep5.go new file mode 100644 index 000000000..e4476be14 --- /dev/null +++ b/pkg/core/native/native_nep5.go @@ -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 +} diff --git a/pkg/core/state/account.go b/pkg/core/state/account.go index 4d163b03c..f18a25d15 100644 --- a/pkg/core/state/account.go +++ b/pkg/core/state/account.go @@ -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 { diff --git a/pkg/core/state/native_state.go b/pkg/core/state/native_state.go new file mode 100644 index 000000000..9744cc754 --- /dev/null +++ b/pkg/core/state/native_state.go @@ -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() +} diff --git a/pkg/core/storage/store.go b/pkg/core/storage/store.go index 73b3866b1..b9fb6874d 100644 --- a/pkg/core/storage/store.go +++ b/pkg/core/storage/store.go @@ -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