Merge pull request #723 from nspcc-dev/feature/nep5
core,rpc: implement NEP5-related logic
This commit is contained in:
commit
fbdc60b731
21 changed files with 748 additions and 26 deletions
|
@ -5,6 +5,8 @@ import (
|
|||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
|
@ -20,6 +22,8 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/rpc/response"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
|
||||
"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"
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
@ -202,6 +206,14 @@ Methods:
|
|||
getconnectioncountCalled.Inc()
|
||||
results = s.coreServer.PeerCount()
|
||||
|
||||
case "getnep5balances":
|
||||
getnep5balancesCalled.Inc()
|
||||
results, resultsErr = s.getNEP5Balances(reqParams)
|
||||
|
||||
case "getnep5transfers":
|
||||
getnep5transfersCalled.Inc()
|
||||
results, resultsErr = s.getNEP5Transfers(reqParams)
|
||||
|
||||
case "getversion":
|
||||
getversionCalled.Inc()
|
||||
results = result.Version{
|
||||
|
@ -385,6 +397,130 @@ func (s *Server) getClaimable(ps request.Params) (interface{}, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) getNEP5Balances(ps request.Params) (interface{}, error) {
|
||||
p, ok := ps.ValueWithType(0, request.StringT)
|
||||
if !ok {
|
||||
return nil, response.ErrInvalidParams
|
||||
}
|
||||
u, err := p.GetUint160FromHex()
|
||||
if err != nil {
|
||||
return nil, response.ErrInvalidParams
|
||||
}
|
||||
|
||||
as := s.chain.GetAccountState(u)
|
||||
bs := &result.NEP5Balances{Address: address.Uint160ToString(u)}
|
||||
if as != nil {
|
||||
cache := make(map[util.Uint160]int64)
|
||||
for h, bal := range as.NEP5Balances {
|
||||
dec, err := s.getDecimals(h, cache)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
amount := amountToString(bal.Balance, dec)
|
||||
bs.Balances = append(bs.Balances, result.NEP5Balance{
|
||||
Asset: h,
|
||||
Amount: amount,
|
||||
LastUpdated: bal.LastUpdatedBlock,
|
||||
})
|
||||
}
|
||||
}
|
||||
return bs, nil
|
||||
}
|
||||
|
||||
func (s *Server) getNEP5Transfers(ps request.Params) (interface{}, error) {
|
||||
p, ok := ps.ValueWithType(0, request.StringT)
|
||||
if !ok {
|
||||
return nil, response.ErrInvalidParams
|
||||
}
|
||||
u, err := p.GetUint160FromAddress()
|
||||
if err != nil {
|
||||
return nil, response.ErrInvalidParams
|
||||
}
|
||||
|
||||
bs := &result.NEP5Transfers{Address: address.Uint160ToString(u)}
|
||||
lg := s.chain.GetNEP5TransferLog(u)
|
||||
cache := make(map[util.Uint160]int64)
|
||||
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)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if tr.Amount > 0 { // token was received
|
||||
transfer.Amount = amountToString(tr.Amount, d)
|
||||
if !tr.From.Equals(util.Uint160{}) {
|
||||
transfer.Address = address.Uint160ToString(tr.From)
|
||||
}
|
||||
bs.Received = append(bs.Received, transfer)
|
||||
return nil
|
||||
}
|
||||
|
||||
transfer.Amount = amountToString(-tr.Amount, d)
|
||||
if !tr.From.Equals(util.Uint160{}) {
|
||||
transfer.Address = address.Uint160ToString(tr.To)
|
||||
}
|
||||
bs.Sent = append(bs.Sent, transfer)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, response.NewInternalServerError("invalid NEP5 transfer log", err)
|
||||
}
|
||||
return bs, nil
|
||||
}
|
||||
|
||||
func amountToString(amount int64, decimals int64) string {
|
||||
if decimals == 0 {
|
||||
return strconv.FormatInt(amount, 10)
|
||||
}
|
||||
pow := int64(math.Pow10(int(decimals)))
|
||||
q := amount / pow
|
||||
r := amount % pow
|
||||
if r == 0 {
|
||||
return strconv.FormatInt(q, 10)
|
||||
}
|
||||
fs := fmt.Sprintf("%%d.%%0%dd", decimals)
|
||||
return fmt.Sprintf(fs, q, r)
|
||||
}
|
||||
|
||||
func (s *Server) getDecimals(h util.Uint160, cache map[util.Uint160]int64) (int64, error) {
|
||||
if d, ok := cache[h]; ok {
|
||||
return d, nil
|
||||
}
|
||||
w := io.NewBufBinWriter()
|
||||
emit.Int(w.BinWriter, 0)
|
||||
emit.Opcode(w.BinWriter, opcode.NEWARRAY)
|
||||
emit.String(w.BinWriter, "decimals")
|
||||
emit.AppCall(w.BinWriter, h, true)
|
||||
v, _ := s.chain.GetTestVM()
|
||||
v.LoadScript(w.Bytes())
|
||||
if err := v.Run(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
res := v.PopResult()
|
||||
if res == nil {
|
||||
return 0, errors.New("invalid result")
|
||||
}
|
||||
bi, ok := res.(*big.Int)
|
||||
if !ok {
|
||||
bs, ok := res.([]byte)
|
||||
if !ok {
|
||||
return 0, errors.New("invalid result")
|
||||
}
|
||||
bi = emit.BytesToInt(bs)
|
||||
}
|
||||
d := bi.Int64()
|
||||
if d < 0 {
|
||||
return 0, errors.New("negative decimals")
|
||||
}
|
||||
cache[h] = d
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (s *Server) getStorage(ps request.Params) (interface{}, error) {
|
||||
param, ok := ps.Value(0)
|
||||
if !ok {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue