mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-29 23:33:37 +00:00
Merge pull request #2581 from nspcc-dev/sym-dec-in-getnep17balances
rpc: add decimals/name/symbol data to getNEPXXBalance
This commit is contained in:
commit
2ba9017207
4 changed files with 107 additions and 36 deletions
|
@ -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,
|
||||||
}},
|
}},
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(),
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue