mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-12-23 03:41:34 +00:00
Merge pull request #2093 from nspcc-dev/states-exchange/drop-nep17-balance-state
core: implement dynamic NEP17 balances tracking
This commit is contained in:
commit
06c3dda5d1
20 changed files with 303 additions and 144 deletions
19
docs/rpc.md
19
docs/rpc.md
|
@ -97,6 +97,25 @@ it only works for native contracts.
|
|||
This method doesn't work for the Ledger contract, you can get data via regular
|
||||
`getblock` and `getrawtransaction` calls.
|
||||
|
||||
#### `getnep17balances`
|
||||
|
||||
neo-go's implementation of `getnep17balances` does not perform tracking of NEP17
|
||||
balances for each account as it is done in the C# node. Instead, neo-go node
|
||||
maintains the list of NEP17-compliant contracts, i.e. those contracts that have
|
||||
`NEP-17` declared in the supported standards section of the manifest. Each time
|
||||
`getnep17balances` is queried, neo-go node asks every NEP17 contract for the
|
||||
account balance by invoking `balanceOf` method with the corresponding args.
|
||||
Invocation GAS limit is set to be 3 GAS. All non-zero NEP17 balances are included
|
||||
in the RPC call result.
|
||||
|
||||
Thus, if NEP17 token contract doesn't have `NEP-17` standard declared in the list
|
||||
of supported standards but emits proper NEP17 `Transfer` notifications, the token
|
||||
balance won't be shown in the list of NEP17 balances returned by the neo-go node
|
||||
(unlike the C# node behavior). However, transfer logs of such token are still
|
||||
available via `getnep17transfers` RPC call.
|
||||
|
||||
The behaviour of the `LastUpdatedBlock` tracking matches the C# node's one.
|
||||
|
||||
### Unsupported methods
|
||||
|
||||
Methods listed down below are not going to be supported for various reasons
|
||||
|
|
|
@ -254,13 +254,18 @@ func (chain *FakeChain) GetNextBlockValidators() ([]*keys.PublicKey, error) {
|
|||
panic("TODO")
|
||||
}
|
||||
|
||||
// ForEachNEP17Transfer implements Blockchainer interface.
|
||||
func (chain *FakeChain) ForEachNEP17Transfer(util.Uint160, func(*state.NEP17Transfer) (bool, error)) error {
|
||||
// GetNEP17Contracts implements Blockchainer interface.
|
||||
func (chain *FakeChain) GetNEP17Contracts() []util.Uint160 {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
// GetNEP17Balances implements Blockchainer interface.
|
||||
func (chain *FakeChain) GetNEP17Balances(util.Uint160) *state.NEP17Balances {
|
||||
// GetNEP17LastUpdated implements Blockchainer interface.
|
||||
func (chain *FakeChain) GetNEP17LastUpdated(acc util.Uint160) (map[int32]uint32, error) {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
// ForEachNEP17Transfer implements Blockchainer interface.
|
||||
func (chain *FakeChain) ForEachNEP17Transfer(util.Uint160, func(*state.NEP17Transfer) (bool, error)) error {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ import (
|
|||
// Tuning parameters.
|
||||
const (
|
||||
headerBatchCount = 2000
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
|
||||
defaultInitialGAS = 52000000_00000000
|
||||
defaultMemPoolSize = 50000
|
||||
|
@ -52,7 +52,8 @@ const (
|
|||
defaultMaxBlockSystemFee = 900000000000
|
||||
defaultMaxTraceableBlocks = 2102400 // 1 year of 15s blocks
|
||||
defaultMaxTransactionsPerBlock = 512
|
||||
headerVerificationGasLimit = 3_00000000 // 3 GAS
|
||||
// HeaderVerificationGasLimit is the maximum amount of GAS for block header verification.
|
||||
HeaderVerificationGasLimit = 3_00000000 // 3 GAS
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -989,14 +990,11 @@ func (bc *Blockchain) processNEP17Transfer(cache *dao.Cached, h util.Uint256, b
|
|||
Tx: h,
|
||||
}
|
||||
if !fromAddr.Equals(util.Uint160{}) {
|
||||
balances, err := cache.GetNEP17Balances(fromAddr)
|
||||
balances, err := cache.GetNEP17TransferInfo(fromAddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
bs := balances.Trackers[id]
|
||||
bs.Balance = *new(big.Int).Sub(&bs.Balance, amount)
|
||||
bs.LastUpdatedBlock = b.Index
|
||||
balances.Trackers[id] = bs
|
||||
balances.LastUpdated[id] = b.Index
|
||||
transfer.Amount = *new(big.Int).Sub(&transfer.Amount, amount)
|
||||
balances.NewBatch, err = cache.AppendNEP17Transfer(fromAddr,
|
||||
balances.NextTransferBatch, balances.NewBatch, transfer)
|
||||
|
@ -1006,19 +1004,16 @@ func (bc *Blockchain) processNEP17Transfer(cache *dao.Cached, h util.Uint256, b
|
|||
if balances.NewBatch {
|
||||
balances.NextTransferBatch++
|
||||
}
|
||||
if err := cache.PutNEP17Balances(fromAddr, balances); err != nil {
|
||||
if err := cache.PutNEP17TransferInfo(fromAddr, balances); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if !toAddr.Equals(util.Uint160{}) {
|
||||
balances, err := cache.GetNEP17Balances(toAddr)
|
||||
balances, err := cache.GetNEP17TransferInfo(toAddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
bs := balances.Trackers[id]
|
||||
bs.Balance = *new(big.Int).Add(&bs.Balance, amount)
|
||||
bs.LastUpdatedBlock = b.Index
|
||||
balances.Trackers[id] = bs
|
||||
balances.LastUpdated[id] = b.Index
|
||||
|
||||
transfer.Amount = *amount
|
||||
balances.NewBatch, err = cache.AppendNEP17Transfer(toAddr,
|
||||
|
@ -1029,7 +1024,7 @@ func (bc *Blockchain) processNEP17Transfer(cache *dao.Cached, h util.Uint256, b
|
|||
if balances.NewBatch {
|
||||
balances.NextTransferBatch++
|
||||
}
|
||||
if err := cache.PutNEP17Balances(toAddr, balances); err != nil {
|
||||
if err := cache.PutNEP17TransferInfo(toAddr, balances); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -1037,7 +1032,7 @@ func (bc *Blockchain) processNEP17Transfer(cache *dao.Cached, h util.Uint256, b
|
|||
|
||||
// ForEachNEP17Transfer executes f for each nep17 transfer in log.
|
||||
func (bc *Blockchain) ForEachNEP17Transfer(acc util.Uint160, f func(*state.NEP17Transfer) (bool, error)) error {
|
||||
balances, err := bc.dao.GetNEP17Balances(acc)
|
||||
balances, err := bc.dao.GetNEP17TransferInfo(acc)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -1057,34 +1052,34 @@ func (bc *Blockchain) ForEachNEP17Transfer(acc util.Uint160, f func(*state.NEP17
|
|||
return nil
|
||||
}
|
||||
|
||||
// GetNEP17Balances returns NEP17 balances for the acc.
|
||||
func (bc *Blockchain) GetNEP17Balances(acc util.Uint160) *state.NEP17Balances {
|
||||
bs, err := bc.dao.GetNEP17Balances(acc)
|
||||
if err != nil {
|
||||
return nil
|
||||
// GetNEP17Contracts returns the list of deployed NEP17 contracts.
|
||||
func (bc *Blockchain) GetNEP17Contracts() []util.Uint160 {
|
||||
return bc.contracts.Management.GetNEP17Contracts()
|
||||
}
|
||||
return bs
|
||||
|
||||
// GetNEP17LastUpdated returns a set of contract ids with the corresponding last updated
|
||||
// block indexes.
|
||||
func (bc *Blockchain) GetNEP17LastUpdated(acc util.Uint160) (map[int32]uint32, error) {
|
||||
info, err := bc.dao.GetNEP17TransferInfo(acc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return info.LastUpdated, nil
|
||||
}
|
||||
|
||||
// GetUtilityTokenBalance returns utility token (GAS) balance for the acc.
|
||||
func (bc *Blockchain) GetUtilityTokenBalance(acc util.Uint160) *big.Int {
|
||||
bs, err := bc.dao.GetNEP17Balances(acc)
|
||||
if err != nil {
|
||||
bs := bc.contracts.GAS.BalanceOf(bc.dao, acc)
|
||||
if bs == nil {
|
||||
return big.NewInt(0)
|
||||
}
|
||||
balance := bs.Trackers[bc.contracts.GAS.ID].Balance
|
||||
return &balance
|
||||
return bs
|
||||
}
|
||||
|
||||
// GetGoverningTokenBalance returns governing token (NEO) balance and the height
|
||||
// of the last balance change for the account.
|
||||
func (bc *Blockchain) GetGoverningTokenBalance(acc util.Uint160) (*big.Int, uint32) {
|
||||
bs, err := bc.dao.GetNEP17Balances(acc)
|
||||
if err != nil {
|
||||
return big.NewInt(0), 0
|
||||
}
|
||||
neo := bs.Trackers[bc.contracts.NEO.ID]
|
||||
return &neo.Balance, neo.LastUpdatedBlock
|
||||
return bc.contracts.NEO.BalanceOf(bc.dao, acc)
|
||||
}
|
||||
|
||||
// GetNotaryBalance returns Notary deposit amount for the specified account.
|
||||
|
@ -1873,7 +1868,7 @@ func (bc *Blockchain) verifyHeaderWitnesses(currHeader, prevHeader *block.Header
|
|||
} else {
|
||||
hash = prevHeader.NextConsensus
|
||||
}
|
||||
return bc.VerifyWitness(hash, currHeader, &currHeader.Script, headerVerificationGasLimit)
|
||||
return bc.VerifyWitness(hash, currHeader, &currHeader.Script, HeaderVerificationGasLimit)
|
||||
}
|
||||
|
||||
// GoverningTokenHash returns the governing token (NEO) native contract hash.
|
||||
|
|
|
@ -47,7 +47,8 @@ type Blockchainer interface {
|
|||
GetNativeContractScriptHash(string) (util.Uint160, error)
|
||||
GetNatives() []state.NativeContract
|
||||
GetNextBlockValidators() ([]*keys.PublicKey, error)
|
||||
GetNEP17Balances(util.Uint160) *state.NEP17Balances
|
||||
GetNEP17Contracts() []util.Uint160
|
||||
GetNEP17LastUpdated(acc util.Uint160) (map[int32]uint32, error)
|
||||
GetNotaryContractScriptHash() util.Uint160
|
||||
GetNotaryBalance(acc util.Uint160) *big.Int
|
||||
GetPolicer() Policer
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
// objects in the storeBlock().
|
||||
type Cached struct {
|
||||
DAO
|
||||
balances map[util.Uint160]*state.NEP17Balances
|
||||
balances map[util.Uint160]*state.NEP17TransferInfo
|
||||
transfers map[util.Uint160]map[uint32]*state.NEP17TransferLog
|
||||
|
||||
dropNEP17Cache bool
|
||||
|
@ -21,21 +21,21 @@ type Cached struct {
|
|||
|
||||
// NewCached returns new Cached wrapping around given backing store.
|
||||
func NewCached(d DAO) *Cached {
|
||||
balances := make(map[util.Uint160]*state.NEP17Balances)
|
||||
balances := make(map[util.Uint160]*state.NEP17TransferInfo)
|
||||
transfers := make(map[util.Uint160]map[uint32]*state.NEP17TransferLog)
|
||||
return &Cached{d.GetWrapped(), balances, transfers, false}
|
||||
}
|
||||
|
||||
// GetNEP17Balances retrieves NEP17Balances for the acc.
|
||||
func (cd *Cached) GetNEP17Balances(acc util.Uint160) (*state.NEP17Balances, error) {
|
||||
// GetNEP17TransferInfo retrieves NEP17TransferInfo for the acc.
|
||||
func (cd *Cached) GetNEP17TransferInfo(acc util.Uint160) (*state.NEP17TransferInfo, error) {
|
||||
if bs := cd.balances[acc]; bs != nil {
|
||||
return bs, nil
|
||||
}
|
||||
return cd.DAO.GetNEP17Balances(acc)
|
||||
return cd.DAO.GetNEP17TransferInfo(acc)
|
||||
}
|
||||
|
||||
// PutNEP17Balances saves NEP17Balances for the acc.
|
||||
func (cd *Cached) PutNEP17Balances(acc util.Uint160, bs *state.NEP17Balances) error {
|
||||
// PutNEP17TransferInfo saves NEP17TransferInfo for the acc.
|
||||
func (cd *Cached) PutNEP17TransferInfo(acc util.Uint160, bs *state.NEP17TransferInfo) error {
|
||||
cd.balances[acc] = bs
|
||||
return nil
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ func (cd *Cached) Persist() (int, error) {
|
|||
// caches (accounts/transfer data) in any way.
|
||||
if ok {
|
||||
if cd.dropNEP17Cache {
|
||||
lowerCache.balances = make(map[util.Uint160]*state.NEP17Balances)
|
||||
lowerCache.balances = make(map[util.Uint160]*state.NEP17TransferInfo)
|
||||
}
|
||||
var simpleCache *Simple
|
||||
for simpleCache == nil {
|
||||
|
@ -105,7 +105,7 @@ func (cd *Cached) Persist() (int, error) {
|
|||
buf := io.NewBufBinWriter()
|
||||
|
||||
for acc, bs := range cd.balances {
|
||||
err := cd.DAO.putNEP17Balances(acc, bs, buf)
|
||||
err := cd.DAO.putNEP17TransferInfo(acc, bs, buf)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ type DAO interface {
|
|||
GetCurrentBlockHeight() (uint32, error)
|
||||
GetCurrentHeaderHeight() (i uint32, h util.Uint256, err error)
|
||||
GetHeaderHashes() ([]util.Uint256, error)
|
||||
GetNEP17Balances(acc util.Uint160) (*state.NEP17Balances, error)
|
||||
GetNEP17TransferInfo(acc util.Uint160) (*state.NEP17TransferInfo, error)
|
||||
GetNEP17TransferLog(acc util.Uint160, index uint32) (*state.NEP17TransferLog, error)
|
||||
GetStorageItem(id int32, key []byte) state.StorageItem
|
||||
GetStorageItems(id int32) (map[string]state.StorageItem, error)
|
||||
|
@ -55,7 +55,7 @@ type DAO interface {
|
|||
PutAppExecResult(aer *state.AppExecResult, buf *io.BufBinWriter) error
|
||||
PutContractID(id int32, hash util.Uint160) error
|
||||
PutCurrentHeader(hashAndIndex []byte) error
|
||||
PutNEP17Balances(acc util.Uint160, bs *state.NEP17Balances) error
|
||||
PutNEP17TransferInfo(acc util.Uint160, bs *state.NEP17TransferInfo) error
|
||||
PutNEP17TransferLog(acc util.Uint160, index uint32, lg *state.NEP17TransferLog) error
|
||||
PutStorageItem(id int32, key []byte, si state.StorageItem) error
|
||||
PutVersion(v string) error
|
||||
|
@ -63,7 +63,7 @@ type DAO interface {
|
|||
StoreAsBlock(block *block.Block, buf *io.BufBinWriter) error
|
||||
StoreAsCurrentBlock(block *block.Block, buf *io.BufBinWriter) error
|
||||
StoreAsTransaction(tx *transaction.Transaction, index uint32, buf *io.BufBinWriter) error
|
||||
putNEP17Balances(acc util.Uint160, bs *state.NEP17Balances, buf *io.BufBinWriter) error
|
||||
putNEP17TransferInfo(acc util.Uint160, bs *state.NEP17TransferInfo, buf *io.BufBinWriter) error
|
||||
}
|
||||
|
||||
// Simple is memCached wrapper around DB, simple DAO implementation.
|
||||
|
@ -142,12 +142,12 @@ func (dao *Simple) GetContractScriptHash(id int32) (util.Uint160, error) {
|
|||
return *data, nil
|
||||
}
|
||||
|
||||
// -- start nep17 balances.
|
||||
// -- start nep17 transfer info.
|
||||
|
||||
// GetNEP17Balances retrieves nep17 balances from the cache.
|
||||
func (dao *Simple) GetNEP17Balances(acc util.Uint160) (*state.NEP17Balances, error) {
|
||||
key := storage.AppendPrefix(storage.STNEP17Balances, acc.BytesBE())
|
||||
bs := state.NewNEP17Balances()
|
||||
// GetNEP17TransferInfo retrieves nep17 transfer info from the cache.
|
||||
func (dao *Simple) GetNEP17TransferInfo(acc util.Uint160) (*state.NEP17TransferInfo, error) {
|
||||
key := storage.AppendPrefix(storage.STNEP17TransferInfo, acc.BytesBE())
|
||||
bs := state.NewNEP17TransferInfo()
|
||||
err := dao.GetAndDecode(bs, key)
|
||||
if err != nil && err != storage.ErrKeyNotFound {
|
||||
return nil, err
|
||||
|
@ -155,17 +155,17 @@ func (dao *Simple) GetNEP17Balances(acc util.Uint160) (*state.NEP17Balances, err
|
|||
return bs, nil
|
||||
}
|
||||
|
||||
// PutNEP17Balances saves nep17 balances from the cache.
|
||||
func (dao *Simple) PutNEP17Balances(acc util.Uint160, bs *state.NEP17Balances) error {
|
||||
return dao.putNEP17Balances(acc, bs, io.NewBufBinWriter())
|
||||
// PutNEP17TransferInfo saves nep17 transfer info in the cache.
|
||||
func (dao *Simple) PutNEP17TransferInfo(acc util.Uint160, bs *state.NEP17TransferInfo) error {
|
||||
return dao.putNEP17TransferInfo(acc, bs, io.NewBufBinWriter())
|
||||
}
|
||||
|
||||
func (dao *Simple) putNEP17Balances(acc util.Uint160, bs *state.NEP17Balances, buf *io.BufBinWriter) error {
|
||||
key := storage.AppendPrefix(storage.STNEP17Balances, acc.BytesBE())
|
||||
func (dao *Simple) putNEP17TransferInfo(acc util.Uint160, bs *state.NEP17TransferInfo, buf *io.BufBinWriter) error {
|
||||
key := storage.AppendPrefix(storage.STNEP17TransferInfo, acc.BytesBE())
|
||||
return dao.putWithBuffer(bs, key, buf)
|
||||
}
|
||||
|
||||
// -- end nep17 balances.
|
||||
// -- end nep17 transfer info.
|
||||
|
||||
// -- start transfer log.
|
||||
|
||||
|
|
|
@ -713,8 +713,8 @@ func checkFAULTState(t *testing.T, result *state.AppExecResult) {
|
|||
}
|
||||
|
||||
func checkBalanceOf(t *testing.T, chain *Blockchain, addr util.Uint160, expected int) {
|
||||
balance := chain.GetNEP17Balances(addr).Trackers[chain.contracts.GAS.ID]
|
||||
require.Equal(t, int64(expected), balance.Balance.Int64())
|
||||
balance := chain.GetUtilityTokenBalance(addr)
|
||||
require.Equal(t, int64(expected), balance.Int64())
|
||||
}
|
||||
|
||||
type NotaryFeerStub struct {
|
||||
|
|
|
@ -33,6 +33,8 @@ type Management struct {
|
|||
|
||||
mtx sync.RWMutex
|
||||
contracts map[util.Uint160]*state.Contract
|
||||
// nep17 is a map of NEP17-compliant contracts which is updated with every PostPersist.
|
||||
nep17 map[util.Uint160]struct{}
|
||||
}
|
||||
|
||||
const (
|
||||
|
@ -63,6 +65,7 @@ func newManagement() *Management {
|
|||
var m = &Management{
|
||||
ContractMD: *interop.NewContractMD(nativenames.Management, managementContractID),
|
||||
contracts: make(map[util.Uint160]*state.Contract),
|
||||
nep17: make(map[util.Uint160]struct{}),
|
||||
}
|
||||
defer m.UpdateHash()
|
||||
|
||||
|
@ -471,6 +474,9 @@ func (m *Management) OnPersist(ic *interop.Context) error {
|
|||
}
|
||||
m.mtx.Lock()
|
||||
m.contracts[md.Hash] = cs
|
||||
if md.Manifest.IsStandardSupported(manifest.NEP17StandardName) {
|
||||
m.nep17[md.Hash] = struct{}{}
|
||||
}
|
||||
m.mtx.Unlock()
|
||||
}
|
||||
|
||||
|
@ -492,6 +498,9 @@ func (m *Management) InitializeCache(d dao.DAO) error {
|
|||
return
|
||||
}
|
||||
m.contracts[cs.Hash] = cs
|
||||
if cs.Manifest.IsStandardSupported(manifest.NEP17StandardName) {
|
||||
m.nep17[cs.Hash] = struct{}{}
|
||||
}
|
||||
})
|
||||
return initErr
|
||||
}
|
||||
|
@ -507,14 +516,33 @@ func (m *Management) PostPersist(ic *interop.Context) error {
|
|||
if err != nil {
|
||||
// Contract was destroyed.
|
||||
delete(m.contracts, h)
|
||||
delete(m.nep17, h)
|
||||
continue
|
||||
}
|
||||
m.contracts[h] = newCs
|
||||
if newCs.Manifest.IsStandardSupported(manifest.NEP17StandardName) {
|
||||
m.nep17[h] = struct{}{}
|
||||
} else {
|
||||
delete(m.nep17, h)
|
||||
}
|
||||
}
|
||||
m.mtx.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetNEP17Contracts returns hashes of all deployed contracts that support NEP17 standard. The list
|
||||
// is updated every PostPersist, so until PostPersist is called, the result for the previous block
|
||||
// is returned.
|
||||
func (m *Management) GetNEP17Contracts() []util.Uint160 {
|
||||
m.mtx.RLock()
|
||||
result := make([]util.Uint160, 0, len(m.nep17))
|
||||
for h := range m.nep17 {
|
||||
result = append(result, h)
|
||||
}
|
||||
m.mtx.RUnlock()
|
||||
return result
|
||||
}
|
||||
|
||||
// Initialize implements Contract interface.
|
||||
func (m *Management) Initialize(ic *interop.Context) error {
|
||||
if err := setIntWithKey(m.ID, ic.DAO, keyMinimumDeploymentFee, defaultMinimumDeploymentFee); err != nil {
|
||||
|
|
|
@ -83,3 +83,50 @@ func TestManagement_Initialize(t *testing.T) {
|
|||
require.Error(t, mgmt.InitializeCache(d))
|
||||
})
|
||||
}
|
||||
|
||||
func TestManagement_GetNEP17Contracts(t *testing.T) {
|
||||
mgmt := newManagement()
|
||||
d := dao.NewCached(dao.NewSimple(storage.NewMemoryStore(), false))
|
||||
err := mgmt.Initialize(&interop.Context{DAO: d})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Empty(t, mgmt.GetNEP17Contracts())
|
||||
|
||||
// Deploy NEP17 contract
|
||||
script := []byte{byte(opcode.RET)}
|
||||
sender := util.Uint160{1, 2, 3}
|
||||
ne, err := nef.NewFile(script)
|
||||
require.NoError(t, err)
|
||||
manif := manifest.NewManifest("Test")
|
||||
manif.ABI.Methods = append(manif.ABI.Methods, manifest.Method{
|
||||
Name: "dummy",
|
||||
ReturnType: smartcontract.VoidType,
|
||||
Parameters: []manifest.Parameter{},
|
||||
})
|
||||
manif.SupportedStandards = []string{manifest.NEP17StandardName}
|
||||
c1, err := mgmt.Deploy(d, sender, ne, manif)
|
||||
require.NoError(t, err)
|
||||
|
||||
// PostPersist is not yet called, thus no NEP17 contracts are expected
|
||||
require.Empty(t, mgmt.GetNEP17Contracts())
|
||||
|
||||
// Call PostPersist, check c1 contract hash is returned
|
||||
require.NoError(t, mgmt.PostPersist(&interop.Context{DAO: d}))
|
||||
require.Equal(t, []util.Uint160{c1.Hash}, mgmt.GetNEP17Contracts())
|
||||
|
||||
// Update contract
|
||||
manif.ABI.Methods = append(manif.ABI.Methods, manifest.Method{
|
||||
Name: "dummy2",
|
||||
ReturnType: smartcontract.VoidType,
|
||||
Parameters: []manifest.Parameter{},
|
||||
})
|
||||
c2, err := mgmt.Update(d, c1.Hash, ne, manif)
|
||||
require.NoError(t, err)
|
||||
|
||||
// No changes expected before PostPersist call.
|
||||
require.Equal(t, []util.Uint160{c1.Hash}, mgmt.GetNEP17Contracts())
|
||||
|
||||
// Call PostPersist, check c2 contract hash is returned
|
||||
require.NoError(t, mgmt.PostPersist(&interop.Context{DAO: d}))
|
||||
require.Equal(t, []util.Uint160{c2.Hash}, mgmt.GetNEP17Contracts())
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"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/native/nativenames"
|
||||
|
@ -138,6 +139,11 @@ func (g *GAS) PostPersist(ic *interop.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// BalanceOf returns native GAS token balance for the acc.
|
||||
func (g *GAS) BalanceOf(d dao.DAO, acc util.Uint160) *big.Int {
|
||||
return g.balanceOfInternal(d, acc)
|
||||
}
|
||||
|
||||
func getStandbyValidatorsHash(ic *interop.Context) (util.Uint160, error) {
|
||||
s, err := smartcontract.CreateDefaultMultiSigRedeemScript(ic.Chain.GetStandByValidators())
|
||||
if err != nil {
|
||||
|
|
|
@ -1020,6 +1020,20 @@ func (n *NEO) GetNextBlockValidatorsInternal() keys.PublicKeys {
|
|||
return n.nextValidators.Load().(keys.PublicKeys).Copy()
|
||||
}
|
||||
|
||||
// BalanceOf returns native NEO token balance for the acc.
|
||||
func (n *NEO) BalanceOf(d dao.DAO, acc util.Uint160) (*big.Int, uint32) {
|
||||
key := makeAccountKey(acc)
|
||||
si := d.GetStorageItem(n.ID, key)
|
||||
if si == nil {
|
||||
return big.NewInt(0), 0
|
||||
}
|
||||
st, err := state.NEOBalanceFromBytes(si)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to decode NEO balance state: %w", err))
|
||||
}
|
||||
return &st.Balance, st.BalanceHeight
|
||||
}
|
||||
|
||||
func pubsToArray(pubs keys.PublicKeys) stackitem.Item {
|
||||
arr := make([]stackitem.Item, len(pubs))
|
||||
for i := range pubs {
|
||||
|
|
|
@ -246,16 +246,20 @@ func (c *nep17TokenNative) TransferInternal(ic *interop.Context, from, to util.U
|
|||
|
||||
func (c *nep17TokenNative) balanceOf(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||
h := toUint160(args[0])
|
||||
return stackitem.NewBigInteger(c.balanceOfInternal(ic.DAO, h))
|
||||
}
|
||||
|
||||
func (c *nep17TokenNative) balanceOfInternal(d dao.DAO, h util.Uint160) *big.Int {
|
||||
key := makeAccountKey(h)
|
||||
si := ic.DAO.GetStorageItem(c.ID, key)
|
||||
si := d.GetStorageItem(c.ID, key)
|
||||
if si == nil {
|
||||
return stackitem.NewBigInteger(big.NewInt(0))
|
||||
return big.NewInt(0)
|
||||
}
|
||||
balance, err := c.balFromBytes(&si)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("can not deserialize balance state: %w", err))
|
||||
}
|
||||
return stackitem.NewBigInteger(balance)
|
||||
return balance
|
||||
}
|
||||
|
||||
func (c *nep17TokenNative) mint(ic *interop.Context, h util.Uint160, amount *big.Int, callOnPayment bool) {
|
||||
|
|
|
@ -76,12 +76,9 @@ func TestGAS_Roundtrip(t *testing.T) {
|
|||
bc := newTestChain(t)
|
||||
|
||||
getUtilityTokenBalance := func(bc *Blockchain, acc util.Uint160) (*big.Int, uint32) {
|
||||
bs, err := bc.dao.GetNEP17Balances(acc)
|
||||
if err != nil {
|
||||
return big.NewInt(0), 0
|
||||
}
|
||||
balance := bs.Trackers[bc.contracts.GAS.ID]
|
||||
return &balance.Balance, balance.LastUpdatedBlock
|
||||
lub, err := bc.GetNEP17LastUpdated(acc)
|
||||
require.NoError(t, err)
|
||||
return bc.GetUtilityTokenBalance(acc), lub[bc.contracts.GAS.ID]
|
||||
}
|
||||
|
||||
initialBalance, _ := getUtilityTokenBalance(bc, neoOwner)
|
||||
|
|
|
@ -607,3 +607,18 @@ func TestMinimumDeploymentFee(t *testing.T) {
|
|||
|
||||
testGetSet(t, chain, chain.contracts.Management.Hash, "MinimumDeploymentFee", 10_00000000, 0, 0)
|
||||
}
|
||||
|
||||
func TestManagement_GetNEP17Contracts(t *testing.T) {
|
||||
t.Run("empty chain", func(t *testing.T) {
|
||||
chain := newTestChain(t)
|
||||
require.ElementsMatch(t, []util.Uint160{chain.contracts.NEO.Hash, chain.contracts.GAS.Hash}, chain.contracts.Management.GetNEP17Contracts())
|
||||
})
|
||||
|
||||
t.Run("test chain", func(t *testing.T) {
|
||||
chain := newTestChain(t)
|
||||
initBasicChain(t, chain)
|
||||
rublesHash, err := chain.GetContractScriptHash(1)
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t, []util.Uint160{chain.contracts.NEO.Hash, chain.contracts.GAS.Hash, rublesHash}, chain.contracts.Management.GetNEP17Contracts())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -11,15 +11,6 @@ import (
|
|||
// NEP17TransferBatchSize is the maximum number of entries for NEP17TransferLog.
|
||||
const NEP17TransferBatchSize = 128
|
||||
|
||||
// NEP17Tracker contains info about a single account in a NEP17 contract.
|
||||
type NEP17Tracker struct {
|
||||
// Balance is the current balance of the account.
|
||||
Balance big.Int
|
||||
// LastUpdatedBlock is a number of block when last `transfer` to or from the
|
||||
// account occurred.
|
||||
LastUpdatedBlock uint32
|
||||
}
|
||||
|
||||
// NEP17TransferLog is a log of NEP17 token transfers for the specific command.
|
||||
type NEP17TransferLog struct {
|
||||
Raw []byte
|
||||
|
@ -44,46 +35,44 @@ type NEP17Transfer struct {
|
|||
Tx util.Uint256
|
||||
}
|
||||
|
||||
// NEP17Balances is a map of the NEP17 contract IDs
|
||||
// to the corresponding structures.
|
||||
type NEP17Balances struct {
|
||||
Trackers map[int32]NEP17Tracker
|
||||
// NEP17TransferInfo stores map of the NEP17 contract IDs to the balance's last updated
|
||||
// block trackers along with information about NEP17 transfer batch.
|
||||
type NEP17TransferInfo struct {
|
||||
LastUpdated map[int32]uint32
|
||||
// NextTransferBatch stores an index of the next transfer batch.
|
||||
NextTransferBatch uint32
|
||||
// NewBatch is true if batch with the `NextTransferBatch` index should be created.
|
||||
NewBatch bool
|
||||
}
|
||||
|
||||
// NewNEP17Balances returns new NEP17Balances.
|
||||
func NewNEP17Balances() *NEP17Balances {
|
||||
return &NEP17Balances{
|
||||
Trackers: make(map[int32]NEP17Tracker),
|
||||
// NewNEP17TransferInfo returns new NEP17TransferInfo.
|
||||
func NewNEP17TransferInfo() *NEP17TransferInfo {
|
||||
return &NEP17TransferInfo{
|
||||
LastUpdated: make(map[int32]uint32),
|
||||
}
|
||||
}
|
||||
|
||||
// DecodeBinary implements io.Serializable interface.
|
||||
func (bs *NEP17Balances) DecodeBinary(r *io.BinReader) {
|
||||
func (bs *NEP17TransferInfo) DecodeBinary(r *io.BinReader) {
|
||||
bs.NextTransferBatch = r.ReadU32LE()
|
||||
bs.NewBatch = r.ReadBool()
|
||||
lenBalances := r.ReadVarUint()
|
||||
m := make(map[int32]NEP17Tracker, lenBalances)
|
||||
m := make(map[int32]uint32, lenBalances)
|
||||
for i := 0; i < int(lenBalances); i++ {
|
||||
key := int32(r.ReadU32LE())
|
||||
var tr NEP17Tracker
|
||||
tr.DecodeBinary(r)
|
||||
m[key] = tr
|
||||
m[key] = r.ReadU32LE()
|
||||
}
|
||||
bs.Trackers = m
|
||||
bs.LastUpdated = m
|
||||
}
|
||||
|
||||
// EncodeBinary implements io.Serializable interface.
|
||||
func (bs *NEP17Balances) EncodeBinary(w *io.BinWriter) {
|
||||
func (bs *NEP17TransferInfo) EncodeBinary(w *io.BinWriter) {
|
||||
w.WriteU32LE(bs.NextTransferBatch)
|
||||
w.WriteBool(bs.NewBatch)
|
||||
w.WriteVarUint(uint64(len(bs.Trackers)))
|
||||
for k, v := range bs.Trackers {
|
||||
w.WriteVarUint(uint64(len(bs.LastUpdated)))
|
||||
for k, v := range bs.LastUpdated {
|
||||
w.WriteU32LE(uint32(k))
|
||||
v.EncodeBinary(w)
|
||||
w.WriteU32LE(v)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,18 +127,6 @@ func (lg *NEP17TransferLog) Size() int {
|
|||
return int(lg.Raw[0])
|
||||
}
|
||||
|
||||
// EncodeBinary implements io.Serializable interface.
|
||||
func (t *NEP17Tracker) EncodeBinary(w *io.BinWriter) {
|
||||
w.WriteVarBytes(bigint.ToBytes(&t.Balance))
|
||||
w.WriteU32LE(t.LastUpdatedBlock)
|
||||
}
|
||||
|
||||
// DecodeBinary implements io.Serializable interface.
|
||||
func (t *NEP17Tracker) DecodeBinary(r *io.BinReader) {
|
||||
t.Balance = *bigint.FromBytes(r.ReadVarBytes())
|
||||
t.LastUpdatedBlock = r.ReadU32LE()
|
||||
}
|
||||
|
||||
// EncodeBinary implements io.Serializable interface.
|
||||
func (t *NEP17Transfer) EncodeBinary(w *io.BinWriter) {
|
||||
w.WriteU32LE(uint32(t.Asset))
|
||||
|
|
|
@ -38,15 +38,6 @@ func TestNEP17TransferLog_Append(t *testing.T) {
|
|||
require.True(t, cont)
|
||||
}
|
||||
|
||||
func TestNEP17Tracker_EncodeBinary(t *testing.T) {
|
||||
expected := &NEP17Tracker{
|
||||
Balance: *big.NewInt(int64(rand.Uint64())),
|
||||
LastUpdatedBlock: rand.Uint32(),
|
||||
}
|
||||
|
||||
testserdes.EncodeDecodeBinary(t, expected, new(NEP17Tracker))
|
||||
}
|
||||
|
||||
func TestNEP17Transfer_DecodeBinary(t *testing.T) {
|
||||
expected := &NEP17Transfer{
|
||||
Asset: 123,
|
||||
|
|
|
@ -16,7 +16,7 @@ const (
|
|||
STContractID KeyPrefix = 0x51
|
||||
STStorage KeyPrefix = 0x70
|
||||
STNEP17Transfers KeyPrefix = 0x72
|
||||
STNEP17Balances KeyPrefix = 0x73
|
||||
STNEP17TransferInfo KeyPrefix = 0x73
|
||||
IXHeaderHashList KeyPrefix = 0x80
|
||||
SYSCurrentBlock KeyPrefix = 0xc0
|
||||
SYSCurrentHeader KeyPrefix = 0xc1
|
||||
|
|
|
@ -40,6 +40,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
@ -669,28 +670,65 @@ func (s *Server) getNEP17Balances(ps request.Params) (interface{}, *response.Err
|
|||
return nil, response.ErrInvalidParams
|
||||
}
|
||||
|
||||
as := s.chain.GetNEP17Balances(u)
|
||||
bs := &result.NEP17Balances{
|
||||
Address: address.Uint160ToString(u),
|
||||
Balances: []result.NEP17Balance{},
|
||||
}
|
||||
if as != nil {
|
||||
cache := make(map[int32]util.Uint160)
|
||||
for id, bal := range as.Trackers {
|
||||
h, err := s.getHash(id, cache)
|
||||
lastUpdated, err := s.chain.GetNEP17LastUpdated(u)
|
||||
if err != nil {
|
||||
return nil, response.NewRPCError("Failed to get NEP17 last updated block", err.Error(), err)
|
||||
}
|
||||
bw := io.NewBufBinWriter()
|
||||
for _, h := range s.chain.GetNEP17Contracts() {
|
||||
balance, err := s.getNEP17Balance(h, u, bw)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if balance.Sign() == 0 {
|
||||
continue
|
||||
}
|
||||
cs := s.chain.GetContractState(h)
|
||||
if cs == nil {
|
||||
continue
|
||||
}
|
||||
bs.Balances = append(bs.Balances, result.NEP17Balance{
|
||||
Asset: h,
|
||||
Amount: bal.Balance.String(),
|
||||
LastUpdated: bal.LastUpdatedBlock,
|
||||
Amount: balance.String(),
|
||||
LastUpdated: lastUpdated[cs.ID],
|
||||
})
|
||||
}
|
||||
}
|
||||
return bs, nil
|
||||
}
|
||||
|
||||
func (s *Server) getNEP17Balance(h util.Uint160, acc util.Uint160, bw *io.BufBinWriter) (*big.Int, error) {
|
||||
if bw == nil {
|
||||
bw = io.NewBufBinWriter()
|
||||
} else {
|
||||
bw.Reset()
|
||||
}
|
||||
emit.AppCall(bw.BinWriter, h, "balanceOf", callflag.ReadStates, acc)
|
||||
if bw.Err != nil {
|
||||
return nil, fmt.Errorf("failed to create `balanceOf` invocation script: %w", bw.Err)
|
||||
}
|
||||
script := bw.Bytes()
|
||||
tx := &transaction.Transaction{Script: script}
|
||||
v := s.chain.GetTestVM(trigger.Application, tx, nil)
|
||||
v.GasLimit = core.HeaderVerificationGasLimit
|
||||
v.LoadScriptWithFlags(script, callflag.All)
|
||||
err := v.Run()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to run `balanceOf` for %s: %w", h.StringLE(), err)
|
||||
}
|
||||
if v.Estack().Len() != 1 {
|
||||
return nil, fmt.Errorf("invalid `balanceOf` return values count: expected 1, got %d", v.Estack().Len())
|
||||
}
|
||||
res, err := v.Estack().Pop().Item().TryInteger()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unexpected `balanceOf` result type: %w", err)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func getTimestampsAndLimit(ps request.Params, index int) (uint64, uint64, int, int, error) {
|
||||
var start, end uint64
|
||||
var limit, page int
|
||||
|
|
|
@ -119,6 +119,16 @@ func (m *Manifest) IsValid(hash util.Uint160) error {
|
|||
return Permissions(m.Permissions).AreValid()
|
||||
}
|
||||
|
||||
// IsStandardSupported denotes whether the specified standard supported by the contract.
|
||||
func (m *Manifest) IsStandardSupported(standard string) bool {
|
||||
for _, st := range m.SupportedStandards {
|
||||
if st == standard {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ToStackItem converts Manifest to stackitem.Item.
|
||||
func (m *Manifest) ToStackItem() (stackitem.Item, error) {
|
||||
groups := make([]stackitem.Item, len(m.Groups))
|
||||
|
|
|
@ -429,3 +429,15 @@ func TestExtraToStackItem(t *testing.T) {
|
|||
require.Equal(t, tc.expected, string(actual))
|
||||
}
|
||||
}
|
||||
|
||||
func TestManifest_IsStandardSupported(t *testing.T) {
|
||||
m := &Manifest{
|
||||
SupportedStandards: []string{NEP17StandardName, NEP17Payable, NEP11Payable},
|
||||
}
|
||||
for _, st := range m.SupportedStandards {
|
||||
require.True(t, m.IsStandardSupported(st))
|
||||
}
|
||||
require.False(t, m.IsStandardSupported(NEP11StandardName))
|
||||
require.False(t, m.IsStandardSupported(""))
|
||||
require.False(t, m.IsStandardSupported("unknown standard"))
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue