rpc: add decimals/name/symbol data to getNEPXXBalance

See neo-project/neo-modules#738 and neo-project/neo-modules#741.
This commit is contained in:
Roman Khimov 2022-07-04 17:12:42 +03:00
parent 4afdb9fd89
commit c356c14741
4 changed files with 107 additions and 36 deletions

View file

@ -543,7 +543,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
}
return c.GetNEP11Balances(hash)
},
serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"balance":[{"assethash":"a48b6e1291ba24211ad11bb90ae2a10bf1fcd5a8","tokens":[{"tokenid":"abcdef","amount":"1","lastupdatedblock":251604}]}],"address":"NcEkNmgWmf7HQVQvzhxpengpnt4DXjmZLe"}}`,
serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"balance":[{"assethash":"a48b6e1291ba24211ad11bb90ae2a10bf1fcd5a8","symbol":"SOME","decimals":"42","name":"Contract","tokens":[{"tokenid":"abcdef","amount":"1","lastupdatedblock":251604}]}],"address":"NcEkNmgWmf7HQVQvzhxpengpnt4DXjmZLe"}}`,
result: func(c *Client) interface{} {
hash, err := util.Uint160DecodeStringLE("a48b6e1291ba24211ad11bb90ae2a10bf1fcd5a8")
if err != nil {
@ -551,7 +551,10 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
}
return &result.NEP11Balances{
Balances: []result.NEP11AssetBalance{{
Asset: hash,
Asset: hash,
Decimals: 42,
Name: "Contract",
Symbol: "SOME",
Tokens: []result.NEP11TokenBalance{{
ID: "abcdef",
Amount: "1",
@ -573,7 +576,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
}
return c.GetNEP17Balances(hash)
},
serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"balance":[{"assethash":"a48b6e1291ba24211ad11bb90ae2a10bf1fcd5a8","amount":"50000000000","lastupdatedblock":251604}],"address":"AY6eqWjsUFCzsVELG7yG72XDukKvC34p2w"}}`,
serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"balance":[{"assethash":"a48b6e1291ba24211ad11bb90ae2a10bf1fcd5a8","symbol":"N17","decimals":"8","name":"Token","amount":"50000000000","lastupdatedblock":251604}],"address":"AY6eqWjsUFCzsVELG7yG72XDukKvC34p2w"}}`,
result: func(c *Client) interface{} {
hash, err := util.Uint160DecodeStringLE("a48b6e1291ba24211ad11bb90ae2a10bf1fcd5a8")
if err != nil {
@ -582,6 +585,9 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
return &result.NEP17Balances{
Balances: []result.NEP17Balance{{
Asset: hash,
Decimals: 8,
Name: "Token",
Symbol: "N17",
Amount: "50000000000",
LastUpdated: 251604,
}},

View file

@ -12,8 +12,11 @@ type NEP11Balances struct {
// NEP11Balance is a structure holding balance of a NEP-11 asset.
type NEP11AssetBalance struct {
Asset util.Uint160 `json:"assethash"`
Tokens []NEP11TokenBalance `json:"tokens"`
Asset util.Uint160 `json:"assethash"`
Decimals int `json:"decimals,string"`
Name string `json:"name"`
Symbol string `json:"symbol"`
Tokens []NEP11TokenBalance `json:"tokens"`
}
// NEP11TokenBalance represents balance of a single NFT.
@ -33,7 +36,10 @@ type NEP17Balances struct {
type NEP17Balance struct {
Asset util.Uint160 `json:"assethash"`
Amount string `json:"amount"`
Decimals int `json:"decimals,string"`
LastUpdated uint32 `json:"lastupdatedblock"`
Name string `json:"name"`
Symbol string `json:"symbol"`
}
// NEP11Transfers is a result for the getnep11transfers RPC.

View file

@ -677,17 +677,28 @@ func (s *Server) getApplicationLog(reqParams request.Params) (interface{}, *resp
return result.NewApplicationLog(hash, appExecResults, trig), nil
}
func (s *Server) getNEP11Tokens(h util.Uint160, acc util.Uint160, bw *io.BufBinWriter) ([]stackitem.Item, error) {
item, finalize, err := s.invokeReadOnly(bw, h, "tokensOf", acc)
func (s *Server) getNEP11Tokens(h util.Uint160, acc util.Uint160, bw *io.BufBinWriter) ([]stackitem.Item, string, int, error) {
items, finalize, err := s.invokeReadOnlyMulti(bw, h, []string{"tokensOf", "symbol", "decimals"}, [][]interface{}{{acc}, nil, nil})
if err != nil {
return nil, err
return nil, "", 0, err
}
defer finalize()
if (item.Type() == stackitem.InteropT) && iterator.IsIterator(item) {
vals, _ := iterator.Values(item, s.config.MaxNEP11Tokens)
return vals, nil
if (items[0].Type() != stackitem.InteropT) || !iterator.IsIterator(items[0]) {
return nil, "", 0, fmt.Errorf("invalid `tokensOf` result type %s", items[0].String())
}
return nil, fmt.Errorf("invalid `tokensOf` result type %s", item.String())
vals, _ := iterator.Values(items[0], s.config.MaxNEP11Tokens)
sym, err := stackitem.ToString(items[1])
if err != nil {
return nil, "", 0, fmt.Errorf("`symbol` return value error: %w", err)
}
dec, err := items[2].TryInteger()
if err != nil {
return nil, "", 0, fmt.Errorf("`decimals` return value error: %w", err)
}
if !dec.IsInt64() || dec.Sign() == -1 || dec.Int64() > math.MaxInt32 {
return nil, "", 0, errors.New("`decimals` returned a bad integer")
}
return vals, sym, int(dec.Int64()), nil
}
func (s *Server) getNEP11Balances(ps request.Params) (interface{}, *response.Error) {
@ -709,7 +720,7 @@ func (s *Server) getNEP11Balances(ps request.Params) (interface{}, *response.Err
bw := io.NewBufBinWriter()
contract_loop:
for _, h := range s.chain.GetNEP11Contracts() {
toks, err := s.getNEP11Tokens(h, u, bw)
toks, sym, dec, err := s.getNEP11Tokens(h, u, bw)
if err != nil {
continue
}
@ -730,8 +741,11 @@ contract_loop:
lub = stateSyncPoint
}
bs.Balances = append(bs.Balances, result.NEP11AssetBalance{
Asset: h,
Tokens: make([]result.NEP11TokenBalance, 0, len(toks)),
Asset: h,
Decimals: dec,
Name: cs.Manifest.Name,
Symbol: sym,
Tokens: make([]result.NEP11TokenBalance, 0, len(toks)),
})
curAsset := &bs.Balances[len(bs.Balances)-1]
for i := range toks {
@ -741,7 +755,7 @@ contract_loop:
}
var amount = "1"
if isDivisible {
balance, err := s.getTokenBalance(h, u, id, bw)
balance, err := s.getNEP11DTokenBalance(h, u, id, bw)
if err != nil {
continue
}
@ -829,7 +843,7 @@ func (s *Server) getNEP17Balances(ps request.Params) (interface{}, *response.Err
stateSyncPoint := lastUpdated[math.MinInt32]
bw := io.NewBufBinWriter()
for _, h := range s.chain.GetNEP17Contracts() {
balance, err := s.getTokenBalance(h, u, nil, bw)
balance, sym, dec, err := s.getNEP17TokenBalance(h, u, bw)
if err != nil {
continue
}
@ -851,21 +865,37 @@ func (s *Server) getNEP17Balances(ps request.Params) (interface{}, *response.Err
bs.Balances = append(bs.Balances, result.NEP17Balance{
Asset: h,
Amount: balance.String(),
Decimals: dec,
LastUpdated: lub,
Name: cs.Manifest.Name,
Symbol: sym,
})
}
return bs, nil
}
func (s *Server) invokeReadOnly(bw *io.BufBinWriter, h util.Uint160, method string, params ...interface{}) (stackitem.Item, func(), error) {
r, f, err := s.invokeReadOnlyMulti(bw, h, []string{method}, [][]interface{}{params})
if err != nil {
return nil, nil, err
}
return r[0], f, nil
}
func (s *Server) invokeReadOnlyMulti(bw *io.BufBinWriter, h util.Uint160, methods []string, params [][]interface{}) ([]stackitem.Item, func(), error) {
if bw == nil {
bw = io.NewBufBinWriter()
} else {
bw.Reset()
}
emit.AppCall(bw.BinWriter, h, method, callflag.ReadStates|callflag.AllowCall, params...)
if bw.Err != nil {
return nil, nil, fmt.Errorf("failed to create `%s` invocation script: %w", method, bw.Err)
if len(methods) != len(params) {
return nil, nil, fmt.Errorf("asymmetric parameters")
}
for i := range methods {
emit.AppCall(bw.BinWriter, h, methods[i], callflag.ReadStates|callflag.AllowCall, params[i]...)
if bw.Err != nil {
return nil, nil, fmt.Errorf("failed to create `%s` invocation script: %w", methods[i], bw.Err)
}
}
script := bw.Bytes()
tx := &transaction.Transaction{Script: script}
@ -879,26 +909,42 @@ func (s *Server) invokeReadOnly(bw *io.BufBinWriter, h util.Uint160, method stri
err = ic.VM.Run()
if err != nil {
ic.Finalize()
return nil, nil, fmt.Errorf("failed to run `%s` for %s: %w", method, h.StringLE(), err)
return nil, nil, fmt.Errorf("failed to run %d methods of %s: %w", len(methods), h.StringLE(), err)
}
if ic.VM.Estack().Len() != 1 {
estack := ic.VM.Estack()
if estack.Len() != len(methods) {
ic.Finalize()
return nil, nil, fmt.Errorf("invalid `%s` return values count: expected 1, got %d", method, ic.VM.Estack().Len())
return nil, nil, fmt.Errorf("invalid return values count: expected %d, got %d", len(methods), estack.Len())
}
return ic.VM.Estack().Pop().Item(), ic.Finalize, nil
return estack.ToArray(), ic.Finalize, nil
}
func (s *Server) getTokenBalance(h util.Uint160, acc util.Uint160, id []byte, bw *io.BufBinWriter) (*big.Int, error) {
var (
item stackitem.Item
finalize func()
err error
)
if id == nil { // NEP-17 and NEP-11 generic.
item, finalize, err = s.invokeReadOnly(bw, h, "balanceOf", acc)
} else { // NEP-11 divisible.
item, finalize, err = s.invokeReadOnly(bw, h, "balanceOf", acc, id)
func (s *Server) getNEP17TokenBalance(h util.Uint160, acc util.Uint160, bw *io.BufBinWriter) (*big.Int, string, int, error) {
items, finalize, err := s.invokeReadOnlyMulti(bw, h, []string{"balanceOf", "symbol", "decimals"}, [][]interface{}{{acc}, nil, nil})
if err != nil {
return nil, "", 0, err
}
finalize()
res, err := items[0].TryInteger()
if err != nil {
return nil, "", 0, fmt.Errorf("unexpected `balanceOf` result type: %w", err)
}
sym, err := stackitem.ToString(items[1])
if err != nil {
return nil, "", 0, fmt.Errorf("`symbol` return value error: %w", err)
}
dec, err := items[2].TryInteger()
if err != nil {
return nil, "", 0, fmt.Errorf("`decimals` return value error: %w", err)
}
if !dec.IsInt64() || dec.Sign() == -1 || dec.Int64() > math.MaxInt32 {
return nil, "", 0, errors.New("`decimals` returned a bad integer")
}
return res, sym, int(dec.Int64()), nil
}
func (s *Server) getNEP11DTokenBalance(h util.Uint160, acc util.Uint160, id []byte, bw *io.BufBinWriter) (*big.Int, error) {
item, finalize, err := s.invokeReadOnly(bw, h, "balanceOf", acc, id)
if err != nil {
return nil, err
}

View file

@ -2428,7 +2428,9 @@ func checkNep11Balances(t *testing.T, e *executor, acc interface{}) {
expected := result.NEP11Balances{
Balances: []result.NEP11AssetBalance{
{
Asset: nnsHash,
Asset: nnsHash,
Name: "NameService",
Symbol: "NNS",
Tokens: []result.NEP11TokenBalance{
{
ID: nnsToken1ID,
@ -2438,7 +2440,10 @@ func checkNep11Balances(t *testing.T, e *executor, acc interface{}) {
},
},
{
Asset: nfsoHash,
Asset: nfsoHash,
Decimals: 2,
Name: "NeoFS Object NFT",
Symbol: "NFSO",
Tokens: []result.NEP11TokenBalance{
{
ID: nfsoToken1ID,
@ -2464,17 +2469,25 @@ func checkNep17Balances(t *testing.T, e *executor, acc interface{}) {
{
Asset: rubles,
Amount: "877",
Decimals: 2,
LastUpdated: 6,
Name: "Rubl",
Symbol: "RUB",
},
{
Asset: e.chain.GoverningTokenHash(),
Amount: "99998000",
LastUpdated: 4,
Name: "NeoToken",
Symbol: "NEO",
},
{
Asset: e.chain.UtilityTokenHash(),
Amount: "47102199200",
Decimals: 8,
LastUpdated: 19,
Name: "GasToken",
Symbol: "GAS",
}},
Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(),
}