Merge pull request #2581 from nspcc-dev/sym-dec-in-getnep17balances

rpc: add decimals/name/symbol data to getNEPXXBalance
This commit is contained in:
Roman Khimov 2022-07-05 12:45:50 +03:00 committed by GitHub
commit 2ba9017207
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 107 additions and 36 deletions

View file

@ -543,7 +543,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
} }
return c.GetNEP11Balances(hash) 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{} { result: func(c *Client) interface{} {
hash, err := util.Uint160DecodeStringLE("a48b6e1291ba24211ad11bb90ae2a10bf1fcd5a8") hash, err := util.Uint160DecodeStringLE("a48b6e1291ba24211ad11bb90ae2a10bf1fcd5a8")
if err != nil { if err != nil {
@ -551,7 +551,10 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
} }
return &result.NEP11Balances{ return &result.NEP11Balances{
Balances: []result.NEP11AssetBalance{{ Balances: []result.NEP11AssetBalance{{
Asset: hash, Asset: hash,
Decimals: 42,
Name: "Contract",
Symbol: "SOME",
Tokens: []result.NEP11TokenBalance{{ Tokens: []result.NEP11TokenBalance{{
ID: "abcdef", ID: "abcdef",
Amount: "1", Amount: "1",
@ -573,7 +576,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
} }
return c.GetNEP17Balances(hash) 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{} { result: func(c *Client) interface{} {
hash, err := util.Uint160DecodeStringLE("a48b6e1291ba24211ad11bb90ae2a10bf1fcd5a8") hash, err := util.Uint160DecodeStringLE("a48b6e1291ba24211ad11bb90ae2a10bf1fcd5a8")
if err != nil { if err != nil {
@ -582,6 +585,9 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
return &result.NEP17Balances{ return &result.NEP17Balances{
Balances: []result.NEP17Balance{{ Balances: []result.NEP17Balance{{
Asset: hash, Asset: hash,
Decimals: 8,
Name: "Token",
Symbol: "N17",
Amount: "50000000000", Amount: "50000000000",
LastUpdated: 251604, LastUpdated: 251604,
}}, }},

View file

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

View file

@ -681,17 +681,28 @@ func (s *Server) getApplicationLog(reqParams request.Params) (interface{}, *resp
return result.NewApplicationLog(hash, appExecResults, trig), nil return result.NewApplicationLog(hash, appExecResults, trig), nil
} }
func (s *Server) getNEP11Tokens(h util.Uint160, acc util.Uint160, bw *io.BufBinWriter) ([]stackitem.Item, error) { func (s *Server) getNEP11Tokens(h util.Uint160, acc util.Uint160, bw *io.BufBinWriter) ([]stackitem.Item, string, int, error) {
item, finalize, err := s.invokeReadOnly(bw, h, "tokensOf", acc) items, finalize, err := s.invokeReadOnlyMulti(bw, h, []string{"tokensOf", "symbol", "decimals"}, [][]interface{}{{acc}, nil, nil})
if err != nil { if err != nil {
return nil, err return nil, "", 0, err
} }
defer finalize() defer finalize()
if (item.Type() == stackitem.InteropT) && iterator.IsIterator(item) { if (items[0].Type() != stackitem.InteropT) || !iterator.IsIterator(items[0]) {
vals, _ := iterator.Values(item, s.config.MaxNEP11Tokens) return nil, "", 0, fmt.Errorf("invalid `tokensOf` result type %s", items[0].String())
return vals, nil
} }
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) { func (s *Server) getNEP11Balances(ps request.Params) (interface{}, *response.Error) {
@ -713,7 +724,7 @@ func (s *Server) getNEP11Balances(ps request.Params) (interface{}, *response.Err
bw := io.NewBufBinWriter() bw := io.NewBufBinWriter()
contract_loop: contract_loop:
for _, h := range s.chain.GetNEP11Contracts() { 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 { if err != nil {
continue continue
} }
@ -734,8 +745,11 @@ contract_loop:
lub = stateSyncPoint lub = stateSyncPoint
} }
bs.Balances = append(bs.Balances, result.NEP11AssetBalance{ bs.Balances = append(bs.Balances, result.NEP11AssetBalance{
Asset: h, Asset: h,
Tokens: make([]result.NEP11TokenBalance, 0, len(toks)), Decimals: dec,
Name: cs.Manifest.Name,
Symbol: sym,
Tokens: make([]result.NEP11TokenBalance, 0, len(toks)),
}) })
curAsset := &bs.Balances[len(bs.Balances)-1] curAsset := &bs.Balances[len(bs.Balances)-1]
for i := range toks { for i := range toks {
@ -745,7 +759,7 @@ contract_loop:
} }
var amount = "1" var amount = "1"
if isDivisible { if isDivisible {
balance, err := s.getTokenBalance(h, u, id, bw) balance, err := s.getNEP11DTokenBalance(h, u, id, bw)
if err != nil { if err != nil {
continue continue
} }
@ -833,7 +847,7 @@ func (s *Server) getNEP17Balances(ps request.Params) (interface{}, *response.Err
stateSyncPoint := lastUpdated[math.MinInt32] stateSyncPoint := lastUpdated[math.MinInt32]
bw := io.NewBufBinWriter() bw := io.NewBufBinWriter()
for _, h := range s.chain.GetNEP17Contracts() { 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 { if err != nil {
continue continue
} }
@ -855,21 +869,37 @@ func (s *Server) getNEP17Balances(ps request.Params) (interface{}, *response.Err
bs.Balances = append(bs.Balances, result.NEP17Balance{ bs.Balances = append(bs.Balances, result.NEP17Balance{
Asset: h, Asset: h,
Amount: balance.String(), Amount: balance.String(),
Decimals: dec,
LastUpdated: lub, LastUpdated: lub,
Name: cs.Manifest.Name,
Symbol: sym,
}) })
} }
return bs, nil return bs, nil
} }
func (s *Server) invokeReadOnly(bw *io.BufBinWriter, h util.Uint160, method string, params ...interface{}) (stackitem.Item, func(), error) { 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 { if bw == nil {
bw = io.NewBufBinWriter() bw = io.NewBufBinWriter()
} else { } else {
bw.Reset() bw.Reset()
} }
emit.AppCall(bw.BinWriter, h, method, callflag.ReadStates|callflag.AllowCall, params...) if len(methods) != len(params) {
if bw.Err != nil { return nil, nil, fmt.Errorf("asymmetric parameters")
return nil, nil, fmt.Errorf("failed to create `%s` invocation script: %w", method, bw.Err) }
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() script := bw.Bytes()
tx := &transaction.Transaction{Script: script} tx := &transaction.Transaction{Script: script}
@ -883,26 +913,42 @@ func (s *Server) invokeReadOnly(bw *io.BufBinWriter, h util.Uint160, method stri
err = ic.VM.Run() err = ic.VM.Run()
if err != nil { if err != nil {
ic.Finalize() 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() 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) { func (s *Server) getNEP17TokenBalance(h util.Uint160, acc util.Uint160, bw *io.BufBinWriter) (*big.Int, string, int, error) {
var ( items, finalize, err := s.invokeReadOnlyMulti(bw, h, []string{"balanceOf", "symbol", "decimals"}, [][]interface{}{{acc}, nil, nil})
item stackitem.Item if err != nil {
finalize func() return nil, "", 0, err
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)
} }
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 { if err != nil {
return nil, err return nil, err
} }

View file

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