Merge pull request #1239 from nspcc-dev/store_nep5_with_id

core: store contract IDs instead of hashes for NEP5Balances and Transfers
This commit is contained in:
fyrchik 2020-07-30 12:56:19 +03:00 committed by GitHub
commit e4fc655115
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 106 additions and 111 deletions

View file

@ -742,8 +742,19 @@ func parseUint160(addr []byte) util.Uint160 {
func (bc *Blockchain) processNEP5Transfer(cache *dao.Cached, h util.Uint256, b *block.Block, sc util.Uint160, from, to []byte, amount *big.Int) { func (bc *Blockchain) processNEP5Transfer(cache *dao.Cached, h util.Uint256, b *block.Block, sc util.Uint160, from, to []byte, amount *big.Int) {
toAddr := parseUint160(to) toAddr := parseUint160(to)
fromAddr := parseUint160(from) fromAddr := parseUint160(from)
var id int32
nativeContract := bc.contracts.ByHash(sc)
if nativeContract != nil {
id = nativeContract.Metadata().ContractID
} else {
assetContract := bc.GetContractState(sc)
if assetContract == nil {
return
}
id = assetContract.ID
}
transfer := &state.NEP5Transfer{ transfer := &state.NEP5Transfer{
Asset: sc, Asset: id,
From: fromAddr, From: fromAddr,
To: toAddr, To: toAddr,
Block: b.Index, Block: b.Index,
@ -755,10 +766,10 @@ func (bc *Blockchain) processNEP5Transfer(cache *dao.Cached, h util.Uint256, b *
if err != nil { if err != nil {
return return
} }
bs := balances.Trackers[sc] bs := balances.Trackers[id]
bs.Balance = *new(big.Int).Sub(&bs.Balance, amount) bs.Balance = *new(big.Int).Sub(&bs.Balance, amount)
bs.LastUpdatedBlock = b.Index bs.LastUpdatedBlock = b.Index
balances.Trackers[sc] = bs balances.Trackers[id] = bs
transfer.Amount = *new(big.Int).Sub(&transfer.Amount, amount) transfer.Amount = *new(big.Int).Sub(&transfer.Amount, amount)
isBig, err := cache.AppendNEP5Transfer(fromAddr, balances.NextTransferBatch, transfer) isBig, err := cache.AppendNEP5Transfer(fromAddr, balances.NextTransferBatch, transfer)
if err != nil { if err != nil {
@ -776,10 +787,10 @@ func (bc *Blockchain) processNEP5Transfer(cache *dao.Cached, h util.Uint256, b *
if err != nil { if err != nil {
return return
} }
bs := balances.Trackers[sc] bs := balances.Trackers[id]
bs.Balance = *new(big.Int).Add(&bs.Balance, amount) bs.Balance = *new(big.Int).Add(&bs.Balance, amount)
bs.LastUpdatedBlock = b.Index bs.LastUpdatedBlock = b.Index
balances.Trackers[sc] = bs balances.Trackers[id] = bs
transfer.Amount = *amount transfer.Amount = *amount
isBig, err := cache.AppendNEP5Transfer(toAddr, balances.NextTransferBatch, transfer) isBig, err := cache.AppendNEP5Transfer(toAddr, balances.NextTransferBatch, transfer)
@ -827,7 +838,7 @@ func (bc *Blockchain) GetUtilityTokenBalance(acc util.Uint160) *big.Int {
if err != nil { if err != nil {
return big.NewInt(0) return big.NewInt(0)
} }
balance := bs.Trackers[bc.contracts.GAS.Hash].Balance balance := bs.Trackers[bc.contracts.GAS.ContractID].Balance
return &balance return &balance
} }
@ -838,7 +849,7 @@ func (bc *Blockchain) GetGoverningTokenBalance(acc util.Uint160) (*big.Int, uint
if err != nil { if err != nil {
return big.NewInt(0), 0 return big.NewInt(0), 0
} }
neo := bs.Trackers[bc.contracts.NEO.Hash] neo := bs.Trackers[bc.contracts.NEO.ContractID]
return &neo.Balance, neo.LastUpdatedBlock return &neo.Balance, neo.LastUpdatedBlock
} }
@ -1017,6 +1028,11 @@ func (bc *Blockchain) GetContractState(hash util.Uint160) *state.Contract {
return contract return contract
} }
// GetContractScriptHash returns contract script hash by its ID.
func (bc *Blockchain) GetContractScriptHash(id int32) (util.Uint160, error) {
return bc.dao.GetContractScriptHash(id)
}
// GetAccountState returns the account state from its script hash. // GetAccountState returns the account state from its script hash.
func (bc *Blockchain) GetAccountState(scriptHash util.Uint160) *state.Account { func (bc *Blockchain) GetAccountState(scriptHash util.Uint160) *state.Account {
as, err := bc.dao.GetAccountState(scriptHash) as, err := bc.dao.GetAccountState(scriptHash)

View file

@ -27,6 +27,7 @@ type Blockchainer interface {
HeaderHeight() uint32 HeaderHeight() uint32
GetBlock(hash util.Uint256) (*block.Block, error) GetBlock(hash util.Uint256) (*block.Block, error)
GetContractState(hash util.Uint160) *state.Contract GetContractState(hash util.Uint160) *state.Contract
GetContractScriptHash(id int32) (util.Uint160, error)
GetEnrollments() ([]state.Validator, error) GetEnrollments() ([]state.Validator, error)
GetGoverningTokenBalance(acc util.Uint160) (*big.Int, uint32) GetGoverningTokenBalance(acc util.Uint160) (*big.Int, uint32)
GetHeaderHash(int) util.Uint256 GetHeaderHash(int) util.Uint256

View file

@ -1,11 +1,9 @@
package dao package dao
import ( import (
"bytes"
"errors" "errors"
"github.com/nspcc-dev/neo-go/pkg/core/state" "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/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
) )
@ -125,65 +123,6 @@ func (cd *Cached) AppendNEP5Transfer(acc util.Uint160, index uint32, tr *state.N
return lg.Size() >= nep5TransferBatchSize, cd.PutNEP5TransferLog(acc, index, lg) return lg.Size() >= nep5TransferBatchSize, cd.PutNEP5TransferLog(acc, index, lg)
} }
// MigrateNEP5Balances migrates NEP5 balances from old contract to the new one.
func (cd *Cached) MigrateNEP5Balances(from, to util.Uint160) error {
var (
simpleDAO *Simple
cachedDAO = cd
ok bool
w = io.NewBufBinWriter()
)
for simpleDAO == nil {
simpleDAO, ok = cachedDAO.DAO.(*Simple)
if !ok {
cachedDAO, ok = cachedDAO.DAO.(*Cached)
if !ok {
panic("uknown DAO")
}
}
}
for acc, bs := range cd.balances {
err := simpleDAO.putNEP5Balances(acc, bs, w)
if err != nil {
return err
}
w.Reset()
}
cd.dropNEP5Cache = true
var store = simpleDAO.Store
// Create another layer of cache because we can't change original storage
// while seeking.
var upStore = storage.NewMemCachedStore(store)
store.Seek([]byte{byte(storage.STNEP5Balances)}, func(k, v []byte) {
if !bytes.Contains(v, from[:]) {
return
}
bs := state.NewNEP5Balances()
reader := io.NewBinReaderFromBuf(v)
bs.DecodeBinary(reader)
if reader.Err != nil {
panic("bad nep5 balances")
}
tr, ok := bs.Trackers[from]
if !ok {
return
}
delete(bs.Trackers, from)
bs.Trackers[to] = tr
w.Reset()
bs.EncodeBinary(w.BinWriter)
if w.Err != nil {
panic("error on nep5 balance encoding")
}
err := upStore.Put(k, w.Bytes())
if err != nil {
panic("can't put value in the DB")
}
})
_, err := upStore.Persist()
return err
}
// Persist flushes all the changes made into the (supposedly) persistent // Persist flushes all the changes made into the (supposedly) persistent
// underlying store. // underlying store.
func (cd *Cached) Persist() (int, error) { func (cd *Cached) Persist() (int, error) {

View file

@ -28,6 +28,7 @@ type DAO interface {
GetBatch() *storage.MemBatch GetBatch() *storage.MemBatch
GetBlock(hash util.Uint256) (*block.Block, error) GetBlock(hash util.Uint256) (*block.Block, error)
GetContractState(hash util.Uint160) (*state.Contract, error) GetContractState(hash util.Uint160) (*state.Contract, error)
GetContractScriptHash(id int32) (util.Uint160, error)
GetCurrentBlockHeight() (uint32, error) GetCurrentBlockHeight() (uint32, error)
GetCurrentHeaderHeight() (i uint32, h util.Uint256, err error) GetCurrentHeaderHeight() (i uint32, h util.Uint256, err error)
GetCurrentStateRootHeight() (uint32, error) GetCurrentStateRootHeight() (uint32, error)
@ -171,7 +172,10 @@ func (dao *Simple) GetContractState(hash util.Uint160) (*state.Contract, error)
// PutContractState puts given contract state into the given store. // PutContractState puts given contract state into the given store.
func (dao *Simple) PutContractState(cs *state.Contract) error { func (dao *Simple) PutContractState(cs *state.Contract) error {
key := storage.AppendPrefix(storage.STContract, cs.ScriptHash().BytesBE()) key := storage.AppendPrefix(storage.STContract, cs.ScriptHash().BytesBE())
return dao.Put(cs, key) if err := dao.Put(cs, key); err != nil {
return err
}
return dao.putContractScriptHash(cs)
} }
// DeleteContractState deletes given contract state in the given store. // DeleteContractState deletes given contract state in the given store.
@ -195,6 +199,29 @@ func (dao *Simple) GetAndUpdateNextContractID() (int32, error) {
return id, dao.Store.Put(key, data) return id, dao.Store.Put(key, data)
} }
// putContractScriptHash puts given contract script hash into the given store.
// It's a private method because it should be used after PutContractState to keep
// ID-Hash pair always up-to-date.
func (dao *Simple) putContractScriptHash(cs *state.Contract) error {
key := make([]byte, 5)
key[0] = byte(storage.STContractID)
binary.LittleEndian.PutUint32(key[1:], uint32(cs.ID))
return dao.Store.Put(key, cs.ScriptHash().BytesBE())
}
// GetContractScriptHash returns script hash of the contract with the specified ID.
// Contract with the script hash may be destroyed.
func (dao *Simple) GetContractScriptHash(id int32) (util.Uint160, error) {
key := make([]byte, 5)
key[0] = byte(storage.STContractID)
binary.LittleEndian.PutUint32(key[1:], uint32(id))
data := &util.Uint160{}
if err := dao.GetAndDecode(data, key); err != nil {
return *data, err
}
return *data, nil
}
// -- end contracts. // -- end contracts.
// -- start nep5 balances. // -- start nep5 balances.

View file

@ -154,7 +154,6 @@ func contractUpdate(ic *interop.Context, v *vm.VM) error {
if err := ic.DAO.DeleteContractState(oldHash); err != nil { if err := ic.DAO.DeleteContractState(oldHash); err != nil {
return fmt.Errorf("failed to update script: %v", err) return fmt.Errorf("failed to update script: %v", err)
} }
ic.DAO.MigrateNEP5Balances(oldHash, newHash)
} }
// if manifest was provided, update the old contract manifest and check associated // if manifest was provided, update the old contract manifest and check associated
// storage items if needed // storage items if needed

View file

@ -210,7 +210,7 @@ func (n *NEO) unclaimedGas(ic *interop.Context, args []stackitem.Item) stackitem
if err != nil { if err != nil {
panic(err) panic(err)
} }
tr := bs.Trackers[n.Hash] tr := bs.Trackers[n.ContractID]
gen := ic.Chain.CalculateClaimable(&tr.Balance, tr.LastUpdatedBlock, end) gen := ic.Chain.CalculateClaimable(&tr.Balance, tr.LastUpdatedBlock, end)
return stackitem.NewBigInteger(gen) return stackitem.NewBigInteger(gen)

View file

@ -210,7 +210,7 @@ func (c *nep5TokenNative) balanceOf(ic *interop.Context, args []stackitem.Item)
if err != nil { if err != nil {
panic(err) panic(err)
} }
balance := bs.Trackers[c.Hash].Balance balance := bs.Trackers[c.ContractID].Balance
return stackitem.NewBigInteger(&balance) return stackitem.NewBigInteger(&balance)
} }

View file

@ -26,8 +26,8 @@ type NEP5TransferLog struct {
// NEP5Transfer represents a single NEP5 Transfer event. // NEP5Transfer represents a single NEP5 Transfer event.
type NEP5Transfer struct { type NEP5Transfer struct {
// Asset is a NEP5 contract hash. // Asset is a NEP5 contract ID.
Asset util.Uint160 Asset int32
// Address is the address of the sender. // Address is the address of the sender.
From util.Uint160 From util.Uint160
// To is the address of the receiver. // To is the address of the receiver.
@ -43,10 +43,10 @@ type NEP5Transfer struct {
Tx util.Uint256 Tx util.Uint256
} }
// NEP5Balances is a map of the NEP5 contract hashes // NEP5Balances is a map of the NEP5 contract IDs
// to the corresponding structures. // to the corresponding structures.
type NEP5Balances struct { type NEP5Balances struct {
Trackers map[util.Uint160]NEP5Tracker Trackers map[int32]NEP5Tracker
// NextTransferBatch stores an index of the next transfer batch. // NextTransferBatch stores an index of the next transfer batch.
NextTransferBatch uint32 NextTransferBatch uint32
} }
@ -54,7 +54,7 @@ type NEP5Balances struct {
// NewNEP5Balances returns new NEP5Balances. // NewNEP5Balances returns new NEP5Balances.
func NewNEP5Balances() *NEP5Balances { func NewNEP5Balances() *NEP5Balances {
return &NEP5Balances{ return &NEP5Balances{
Trackers: make(map[util.Uint160]NEP5Tracker), Trackers: make(map[int32]NEP5Tracker),
} }
} }
@ -62,11 +62,10 @@ func NewNEP5Balances() *NEP5Balances {
func (bs *NEP5Balances) DecodeBinary(r *io.BinReader) { func (bs *NEP5Balances) DecodeBinary(r *io.BinReader) {
bs.NextTransferBatch = r.ReadU32LE() bs.NextTransferBatch = r.ReadU32LE()
lenBalances := r.ReadVarUint() lenBalances := r.ReadVarUint()
m := make(map[util.Uint160]NEP5Tracker, lenBalances) m := make(map[int32]NEP5Tracker, lenBalances)
for i := 0; i < int(lenBalances); i++ { for i := 0; i < int(lenBalances); i++ {
var key util.Uint160 key := int32(r.ReadU32LE())
var tr NEP5Tracker var tr NEP5Tracker
r.ReadBytes(key[:])
tr.DecodeBinary(r) tr.DecodeBinary(r)
m[key] = tr m[key] = tr
} }
@ -78,7 +77,7 @@ func (bs *NEP5Balances) EncodeBinary(w *io.BinWriter) {
w.WriteU32LE(bs.NextTransferBatch) w.WriteU32LE(bs.NextTransferBatch)
w.WriteVarUint(uint64(len(bs.Trackers))) w.WriteVarUint(uint64(len(bs.Trackers)))
for k, v := range bs.Trackers { for k, v := range bs.Trackers {
w.WriteBytes(k[:]) w.WriteU32LE(uint32(k))
v.EncodeBinary(w) v.EncodeBinary(w)
} }
} }
@ -133,7 +132,7 @@ func (t *NEP5Tracker) DecodeBinary(r *io.BinReader) {
// EncodeBinary implements io.Serializable interface. // EncodeBinary implements io.Serializable interface.
func (t *NEP5Transfer) EncodeBinary(w *io.BinWriter) { func (t *NEP5Transfer) EncodeBinary(w *io.BinWriter) {
w.WriteBytes(t.Asset[:]) w.WriteU32LE(uint32(t.Asset))
w.WriteBytes(t.Tx[:]) w.WriteBytes(t.Tx[:])
w.WriteBytes(t.From[:]) w.WriteBytes(t.From[:])
w.WriteBytes(t.To[:]) w.WriteBytes(t.To[:])
@ -151,7 +150,7 @@ func (t *NEP5Transfer) DecodeBinary(r *io.BinReader) {
// DecodeBinaryReturnCount decodes NEP5Transfer and returns the number of bytes read. // DecodeBinaryReturnCount decodes NEP5Transfer and returns the number of bytes read.
func (t *NEP5Transfer) DecodeBinaryReturnCount(r *io.BinReader) int { func (t *NEP5Transfer) DecodeBinaryReturnCount(r *io.BinReader) int {
r.ReadBytes(t.Asset[:]) t.Asset = int32(r.ReadU32LE())
r.ReadBytes(t.Tx[:]) r.ReadBytes(t.Tx[:])
r.ReadBytes(t.From[:]) r.ReadBytes(t.From[:])
r.ReadBytes(t.To[:]) r.ReadBytes(t.To[:])
@ -161,5 +160,5 @@ func (t *NEP5Transfer) DecodeBinaryReturnCount(r *io.BinReader) int {
amountBytes := make([]byte, amountLen) amountBytes := make([]byte, amountLen)
r.ReadBytes(amountBytes) r.ReadBytes(amountBytes)
t.Amount = *bigint.FromBytes(amountBytes) t.Amount = *bigint.FromBytes(amountBytes)
return util.Uint160Size*3 + 8 + 4 + (8 + len(amountBytes)) + +util.Uint256Size return 4 + util.Uint160Size*2 + 8 + 4 + (8 + len(amountBytes)) + +util.Uint256Size
} }

View file

@ -50,7 +50,7 @@ func TestNEP5Tracker_EncodeBinary(t *testing.T) {
func TestNEP5Transfer_DecodeBinary(t *testing.T) { func TestNEP5Transfer_DecodeBinary(t *testing.T) {
expected := &NEP5Transfer{ expected := &NEP5Transfer{
Asset: util.Uint160{1, 2, 3}, Asset: 123,
From: util.Uint160{5, 6, 7}, From: util.Uint160{5, 6, 7},
To: util.Uint160{8, 9, 10}, To: util.Uint160{8, 9, 10},
Amount: *big.NewInt(42), Amount: *big.NewInt(42),
@ -78,7 +78,7 @@ func randomTransfer(r *rand.Rand) *NEP5Transfer {
return &NEP5Transfer{ return &NEP5Transfer{
Amount: *big.NewInt(int64(r.Uint64())), Amount: *big.NewInt(int64(r.Uint64())),
Block: r.Uint32(), Block: r.Uint32(),
Asset: random.Uint160(), Asset: int32(random.Int(10, 10000000)),
From: random.Uint160(), From: random.Uint160(),
To: random.Uint160(), To: random.Uint160(),
Tx: random.Uint256(), Tx: random.Uint256(),

View file

@ -13,6 +13,7 @@ const (
STAccount KeyPrefix = 0x40 STAccount KeyPrefix = 0x40
STNotification KeyPrefix = 0x4d STNotification KeyPrefix = 0x4d
STContract KeyPrefix = 0x50 STContract KeyPrefix = 0x50
STContractID KeyPrefix = 0x51
STStorage KeyPrefix = 0x70 STStorage KeyPrefix = 0x70
STNEP5Transfers KeyPrefix = 0x72 STNEP5Transfers KeyPrefix = 0x72
STNEP5Balances KeyPrefix = 0x73 STNEP5Balances KeyPrefix = 0x73

View file

@ -70,6 +70,9 @@ func (chain testChain) GetBlock(hash util.Uint256) (*block.Block, error) {
func (chain testChain) GetContractState(hash util.Uint160) *state.Contract { func (chain testChain) GetContractState(hash util.Uint160) *state.Contract {
panic("TODO") panic("TODO")
} }
func (chain testChain) GetContractScriptHash(id int32) (util.Uint160, error) {
panic("TODO")
}
func (chain testChain) GetHeaderHash(int) util.Uint256 { func (chain testChain) GetHeaderHash(int) util.Uint256 {
return util.Uint256{} return util.Uint256{}
} }

View file

@ -514,15 +514,15 @@ func (s *Server) getNEP5Balances(ps request.Params) (interface{}, *response.Erro
Balances: []result.NEP5Balance{}, Balances: []result.NEP5Balance{},
} }
if as != nil { if as != nil {
cache := make(map[util.Uint160]int64) cache := make(map[int32]decimals)
for h, bal := range as.Trackers { for id, bal := range as.Trackers {
dec, err := s.getDecimals(h, cache) dec, err := s.getDecimals(id, cache)
if err != nil { if err != nil {
continue continue
} }
amount := amountToString(&bal.Balance, dec) amount := amountToString(&bal.Balance, dec.Value)
bs.Balances = append(bs.Balances, result.NEP5Balance{ bs.Balances = append(bs.Balances, result.NEP5Balance{
Asset: h, Asset: dec.Hash,
Amount: amount, Amount: amount,
LastUpdated: bal.LastUpdatedBlock, LastUpdated: bal.LastUpdatedBlock,
}) })
@ -543,20 +543,20 @@ func (s *Server) getNEP5Transfers(ps request.Params) (interface{}, *response.Err
Sent: []result.NEP5Transfer{}, Sent: []result.NEP5Transfer{},
} }
lg := s.chain.GetNEP5TransferLog(u) lg := s.chain.GetNEP5TransferLog(u)
cache := make(map[util.Uint160]int64) cache := make(map[int32]decimals)
err = lg.ForEach(func(tr *state.NEP5Transfer) error { err = lg.ForEach(func(tr *state.NEP5Transfer) error {
transfer := result.NEP5Transfer{
Timestamp: tr.Timestamp,
Asset: tr.Asset,
Index: tr.Block,
TxHash: tr.Tx,
}
d, err := s.getDecimals(tr.Asset, cache) d, err := s.getDecimals(tr.Asset, cache)
if err != nil { if err != nil {
return nil return nil
} }
transfer := result.NEP5Transfer{
Timestamp: tr.Timestamp,
Asset: d.Hash,
Index: tr.Block,
TxHash: tr.Tx,
}
if tr.Amount.Sign() > 0 { // token was received if tr.Amount.Sign() > 0 { // token was received
transfer.Amount = amountToString(&tr.Amount, d) transfer.Amount = amountToString(&tr.Amount, d.Value)
if !tr.From.Equals(util.Uint160{}) { if !tr.From.Equals(util.Uint160{}) {
transfer.Address = address.Uint160ToString(tr.From) transfer.Address = address.Uint160ToString(tr.From)
} }
@ -564,7 +564,7 @@ func (s *Server) getNEP5Transfers(ps request.Params) (interface{}, *response.Err
return nil return nil
} }
transfer.Amount = amountToString(new(big.Int).Neg(&tr.Amount), d) transfer.Amount = amountToString(new(big.Int).Neg(&tr.Amount), d.Value)
if !tr.To.Equals(util.Uint160{}) { if !tr.To.Equals(util.Uint160{}) {
transfer.Address = address.Uint160ToString(tr.To) transfer.Address = address.Uint160ToString(tr.To)
} }
@ -590,10 +590,20 @@ func amountToString(amount *big.Int, decimals int64) string {
return fmt.Sprintf(fs, q, r) return fmt.Sprintf(fs, q, r)
} }
func (s *Server) getDecimals(h util.Uint160, cache map[util.Uint160]int64) (int64, *response.Error) { // decimals represents decimals value for the contract with the specified scripthash.
if d, ok := cache[h]; ok { type decimals struct {
Hash util.Uint160
Value int64
}
func (s *Server) getDecimals(contractID int32, cache map[int32]decimals) (decimals, error) {
if d, ok := cache[contractID]; ok {
return d, nil return d, nil
} }
h, err := s.chain.GetContractScriptHash(contractID)
if err != nil {
return decimals{}, err
}
script, err := request.CreateFunctionInvocationScript(h, request.Params{ script, err := request.CreateFunctionInvocationScript(h, request.Params{
{ {
Type: request.StringT, Type: request.StringT,
@ -605,26 +615,26 @@ func (s *Server) getDecimals(h util.Uint160, cache map[util.Uint160]int64) (int6
}, },
}) })
if err != nil { if err != nil {
return 0, response.NewInternalServerError("Can't create script", err) return decimals{}, fmt.Errorf("can't create script: %v", err)
} }
res := s.runScriptInVM(script, nil) res := s.runScriptInVM(script, nil)
if res == nil || res.State != "HALT" || len(res.Stack) == 0 { if res == nil || res.State != "HALT" || len(res.Stack) == 0 {
return 0, response.NewInternalServerError("execution error", errors.New("no result")) return decimals{}, errors.New("execution error : no result")
} }
var d int64 d := decimals{Hash: h}
switch item := res.Stack[len(res.Stack)-1]; item.Type { switch item := res.Stack[len(res.Stack)-1]; item.Type {
case smartcontract.IntegerType: case smartcontract.IntegerType:
d = item.Value.(int64) d.Value = item.Value.(int64)
case smartcontract.ByteArrayType: case smartcontract.ByteArrayType:
d = bigint.FromBytes(item.Value.([]byte)).Int64() d.Value = bigint.FromBytes(item.Value.([]byte)).Int64()
default: default:
return 0, response.NewInternalServerError("invalid result", errors.New("not an integer")) return d, errors.New("invalid result: not an integer")
} }
if d < 0 { if d.Value < 0 {
return 0, response.NewInternalServerError("incorrect result", errors.New("negative result")) return d, errors.New("incorrect result: negative result")
} }
cache[h] = d cache[contractID] = d
return d, nil return d, nil
} }