mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-30 09:33:36 +00:00
rpc: display for NEP5 token amount properly
Every NEP5 contract MUST have `decimals` method which is used to properly display token amount.
This commit is contained in:
parent
95a8fa234f
commit
547bd3bde3
2 changed files with 68 additions and 6 deletions
|
@ -5,6 +5,8 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
@ -20,6 +22,8 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpc/response"
|
"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/rpc/response/result"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"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"
|
"github.com/pkg/errors"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
@ -402,8 +406,13 @@ func (s *Server) getNEP5Balances(ps request.Params) (interface{}, error) {
|
||||||
as := s.chain.GetAccountState(u)
|
as := s.chain.GetAccountState(u)
|
||||||
bs := &result.NEP5Balances{Address: address.Uint160ToString(u)}
|
bs := &result.NEP5Balances{Address: address.Uint160ToString(u)}
|
||||||
if as != nil {
|
if as != nil {
|
||||||
|
cache := make(map[util.Uint160]int64)
|
||||||
for h, bal := range as.NEP5Balances {
|
for h, bal := range as.NEP5Balances {
|
||||||
amount := strconv.FormatInt(bal.Balance, 10)
|
dec, err := s.getDecimals(h, cache)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
amount := amountToString(bal.Balance, dec)
|
||||||
bs.Balances = append(bs.Balances, result.NEP5Balance{
|
bs.Balances = append(bs.Balances, result.NEP5Balance{
|
||||||
Asset: h,
|
Asset: h,
|
||||||
Amount: amount,
|
Amount: amount,
|
||||||
|
@ -426,6 +435,7 @@ func (s *Server) getNEP5Transfers(ps request.Params) (interface{}, error) {
|
||||||
|
|
||||||
bs := &result.NEP5Transfers{Address: address.Uint160ToString(u)}
|
bs := &result.NEP5Transfers{Address: address.Uint160ToString(u)}
|
||||||
lg := s.chain.GetNEP5TransferLog(u)
|
lg := s.chain.GetNEP5TransferLog(u)
|
||||||
|
cache := make(map[util.Uint160]int64)
|
||||||
err = lg.ForEach(func(tr *state.NEP5Transfer) error {
|
err = lg.ForEach(func(tr *state.NEP5Transfer) error {
|
||||||
transfer := result.NEP5Transfer{
|
transfer := result.NEP5Transfer{
|
||||||
Timestamp: tr.Timestamp,
|
Timestamp: tr.Timestamp,
|
||||||
|
@ -433,8 +443,12 @@ func (s *Server) getNEP5Transfers(ps request.Params) (interface{}, error) {
|
||||||
Index: tr.Block,
|
Index: tr.Block,
|
||||||
TxHash: tr.Tx,
|
TxHash: tr.Tx,
|
||||||
}
|
}
|
||||||
|
d, err := s.getDecimals(tr.Asset, cache)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
if tr.Amount > 0 { // token was received
|
if tr.Amount > 0 { // token was received
|
||||||
transfer.Amount = strconv.FormatInt(tr.Amount, 10)
|
transfer.Amount = amountToString(tr.Amount, d)
|
||||||
if !tr.From.Equals(util.Uint160{}) {
|
if !tr.From.Equals(util.Uint160{}) {
|
||||||
transfer.Address = address.Uint160ToString(tr.From)
|
transfer.Address = address.Uint160ToString(tr.From)
|
||||||
}
|
}
|
||||||
|
@ -442,7 +456,7 @@ func (s *Server) getNEP5Transfers(ps request.Params) (interface{}, error) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
transfer.Amount = strconv.FormatInt(-tr.Amount, 10)
|
transfer.Amount = amountToString(-tr.Amount, d)
|
||||||
if !tr.From.Equals(util.Uint160{}) {
|
if !tr.From.Equals(util.Uint160{}) {
|
||||||
transfer.Address = address.Uint160ToString(tr.To)
|
transfer.Address = address.Uint160ToString(tr.To)
|
||||||
}
|
}
|
||||||
|
@ -455,6 +469,54 @@ func (s *Server) getNEP5Transfers(ps request.Params) (interface{}, error) {
|
||||||
return bs, nil
|
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) {
|
func (s *Server) getStorage(ps request.Params) (interface{}, error) {
|
||||||
param, ok := ps.Value(0)
|
param, ok := ps.Value(0)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
|
@ -165,7 +165,7 @@ var rpcTestCases = map[string][]rpcTestCase{
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
require.Equal(t, "AKkkumHbBipZ46UMZJoFynJMXzSRnBvKcs", res.Address)
|
require.Equal(t, "AKkkumHbBipZ46UMZJoFynJMXzSRnBvKcs", res.Address)
|
||||||
require.Equal(t, 1, len(res.Balances))
|
require.Equal(t, 1, len(res.Balances))
|
||||||
require.Equal(t, "877", res.Balances[0].Amount)
|
require.Equal(t, "8.77", res.Balances[0].Amount)
|
||||||
require.Equal(t, "d864728bdbc88da799bc43862ae6aaa62adc3a87", res.Balances[0].Asset.StringLE())
|
require.Equal(t, "d864728bdbc88da799bc43862ae6aaa62adc3a87", res.Balances[0].Asset.StringLE())
|
||||||
require.Equal(t, uint32(208), res.Balances[0].LastUpdated)
|
require.Equal(t, uint32(208), res.Balances[0].LastUpdated)
|
||||||
},
|
},
|
||||||
|
@ -195,12 +195,12 @@ var rpcTestCases = map[string][]rpcTestCase{
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Equal(t, 1, len(res.Received))
|
require.Equal(t, 1, len(res.Received))
|
||||||
require.Equal(t, "1000", res.Received[0].Amount)
|
require.Equal(t, "10", res.Received[0].Amount)
|
||||||
require.Equal(t, assetHash, res.Received[0].Asset)
|
require.Equal(t, assetHash, res.Received[0].Asset)
|
||||||
require.Equal(t, address.Uint160ToString(assetHash), res.Received[0].Address)
|
require.Equal(t, address.Uint160ToString(assetHash), res.Received[0].Address)
|
||||||
|
|
||||||
require.Equal(t, 1, len(res.Sent))
|
require.Equal(t, 1, len(res.Sent))
|
||||||
require.Equal(t, "123", res.Sent[0].Amount)
|
require.Equal(t, "1.23", res.Sent[0].Amount)
|
||||||
require.Equal(t, assetHash, res.Sent[0].Asset)
|
require.Equal(t, assetHash, res.Sent[0].Asset)
|
||||||
require.Equal(t, "AWLYWXB8C9Lt1nHdDZJnC5cpYJjgRDLk17", res.Sent[0].Address)
|
require.Equal(t, "AWLYWXB8C9Lt1nHdDZJnC5cpYJjgRDLk17", res.Sent[0].Address)
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue