cli: refactor nep11-related commands and add tests

Specify tokenID via hex-encoded string.
This commit is contained in:
Anna Shaleva 2022-02-04 19:18:35 +03:00 committed by Anna Shaleva
parent 22c39d3916
commit 3c75f047c1
7 changed files with 371 additions and 54 deletions

View file

@ -40,6 +40,7 @@ const (
testWalletAccount = "Nfyz4KcsgYepRJw1W5C2uKCi6QWKf7v6gG" testWalletAccount = "Nfyz4KcsgYepRJw1W5C2uKCi6QWKf7v6gG"
validatorWallet = "testdata/wallet1_solo.json" validatorWallet = "testdata/wallet1_solo.json"
validatorPass = "one"
) )
var ( var (

View file

@ -2,12 +2,15 @@ package main
import ( import (
"bytes" "bytes"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"math/big" "math/big"
"path/filepath" "path/filepath"
"strings" "strconv"
"testing" "testing"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
@ -35,6 +38,8 @@ func TestNEP11Import(t *testing.T) {
// deploy NFT NeoNameService contract // deploy NFT NeoNameService contract
nnsContractHash := deployNNSContract(t, e) nnsContractHash := deployNNSContract(t, e)
// deploy NFT-D NeoFS Object contract
nfsContractHash := deployNFSContract(t, e)
neoContractHash, err := e.Chain.GetNativeContractScriptHash(nativenames.Neo) neoContractHash, err := e.Chain.GetNativeContractScriptHash(nativenames.Neo)
require.NoError(t, err) require.NoError(t, err)
e.Run(t, "neo-go", "wallet", "init", "--wallet", walletPath) e.Run(t, "neo-go", "wallet", "init", "--wallet", walletPath)
@ -47,33 +52,38 @@ func TestNEP11Import(t *testing.T) {
// missing token hash // missing token hash
e.RunWithError(t, args...) e.RunWithError(t, args...)
// good // good: non-divisible
e.Run(t, append(args, "--token", nnsContractHash.StringLE())...) e.Run(t, append(args, "--token", nnsContractHash.StringLE())...)
// good: divisible
e.Run(t, append(args, "--token", nfsContractHash.StringLE())...)
// already exists // already exists
e.RunWithError(t, append(args, "--token", nnsContractHash.StringLE())...) e.RunWithError(t, append(args, "--token", nnsContractHash.StringLE())...)
// not a NEP-11 token // not a NEP-11 token
e.RunWithError(t, append(args, "--token", neoContractHash.StringLE())...) e.RunWithError(t, append(args, "--token", neoContractHash.StringLE())...)
checkInfo := func(t *testing.T, h util.Uint160, name string, symbol string, decimals int) {
e.checkNextLine(t, "^Name:\\s*"+name)
e.checkNextLine(t, "^Symbol:\\s*"+symbol)
e.checkNextLine(t, "^Hash:\\s*"+h.StringLE())
e.checkNextLine(t, "^Decimals:\\s*"+strconv.Itoa(decimals))
e.checkNextLine(t, "^Address:\\s*"+address.Uint160ToString(h))
e.checkNextLine(t, "^Standard:\\s*"+string(manifest.NEP11StandardName))
}
t.Run("Info", func(t *testing.T) { t.Run("Info", func(t *testing.T) {
checkNNSInfo := func(t *testing.T) {
e.checkNextLine(t, "^Name:\\s*NameService")
e.checkNextLine(t, "^Symbol:\\s*NNS")
e.checkNextLine(t, "^Hash:\\s*"+nnsContractHash.StringLE())
e.checkNextLine(t, "^Decimals:\\s*0")
e.checkNextLine(t, "^Address:\\s*"+address.Uint160ToString(nnsContractHash))
e.checkNextLine(t, "^Standard:\\s*"+string(manifest.NEP11StandardName))
}
t.Run("WithToken", func(t *testing.T) { t.Run("WithToken", func(t *testing.T) {
e.Run(t, "neo-go", "wallet", "nep11", "info", e.Run(t, "neo-go", "wallet", "nep11", "info",
"--wallet", walletPath, "--token", nnsContractHash.StringLE()) "--wallet", walletPath, "--token", nnsContractHash.StringLE())
checkNNSInfo(t) checkInfo(t, nnsContractHash, "NameService", "NNS", 0)
}) })
t.Run("NoToken", func(t *testing.T) { t.Run("NoToken", func(t *testing.T) {
e.Run(t, "neo-go", "wallet", "nep11", "info", e.Run(t, "neo-go", "wallet", "nep11", "info",
"--wallet", walletPath) "--wallet", walletPath)
checkNNSInfo(t) checkInfo(t, nnsContractHash, "NameService", "NNS", 0)
e.checkNextLine(t, "")
checkInfo(t, nfsContractHash, "NeoFS Object NFT", "NFSO", 2)
}) })
}) })
@ -83,12 +93,13 @@ func TestNEP11Import(t *testing.T) {
"--wallet", walletPath, "--token", nnsContractHash.StringLE()) "--wallet", walletPath, "--token", nnsContractHash.StringLE())
e.Run(t, "neo-go", "wallet", "nep11", "info", e.Run(t, "neo-go", "wallet", "nep11", "info",
"--wallet", walletPath) "--wallet", walletPath)
checkInfo(t, nfsContractHash, "NeoFS Object NFT", "NFSO", 2)
_, err := e.Out.ReadString('\n') _, err := e.Out.ReadString('\n')
require.Equal(t, err, io.EOF) require.Equal(t, err, io.EOF)
}) })
} }
func TestNEP11_OwnerOf_BalanceOf_Transfer(t *testing.T) { func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) {
e := newExecutor(t, true) e := newExecutor(t, true)
tmpDir := t.TempDir() tmpDir := t.TempDir()
@ -190,7 +201,7 @@ func TestNEP11_OwnerOf_BalanceOf_Transfer(t *testing.T) {
// ownerOf: missing token ID // ownerOf: missing token ID
e.RunWithError(t, cmdOwnerOf...) e.RunWithError(t, cmdOwnerOf...)
cmdOwnerOf = append(cmdOwnerOf, "--id", string(tokenID)) cmdOwnerOf = append(cmdOwnerOf, "--id", hex.EncodeToString(tokenID))
// ownerOf: good // ownerOf: good
e.Run(t, cmdOwnerOf...) e.Run(t, cmdOwnerOf...)
@ -209,7 +220,7 @@ func TestNEP11_OwnerOf_BalanceOf_Transfer(t *testing.T) {
// tokensOf: good // tokensOf: good
e.Run(t, cmdTokensOf...) e.Run(t, cmdTokensOf...)
require.Equal(t, string(tokenID), e.getNextLine(t)) require.Equal(t, hex.EncodeToString(tokenID), e.getNextLine(t))
// properties: no contract // properties: no contract
cmdProperties := []string{ cmdProperties := []string{
@ -221,12 +232,11 @@ func TestNEP11_OwnerOf_BalanceOf_Transfer(t *testing.T) {
// properties: no token ID // properties: no token ID
e.RunWithError(t, cmdProperties...) e.RunWithError(t, cmdProperties...)
cmdProperties = append(cmdProperties, "--id", string(tokenID)) cmdProperties = append(cmdProperties, "--id", hex.EncodeToString(tokenID))
// properties: ok // properties: ok
e.Run(t, cmdProperties...) e.Run(t, cmdProperties...)
marshalledID := strings.Replace(string(tokenID), "+", "\\u002B", -1) require.Equal(t, fmt.Sprintf(`{"name":"HASHY %s"}`, base64.StdEncoding.EncodeToString(tokenID)), e.getNextLine(t))
require.Equal(t, fmt.Sprintf(`{"name":"HASHY %s"}`, marshalledID), e.getNextLine(t))
// tokensOf: good, several tokens // tokensOf: good, several tokens
tokenID1 := mint(t) tokenID1 := mint(t)
@ -236,8 +246,8 @@ func TestNEP11_OwnerOf_BalanceOf_Transfer(t *testing.T) {
fst, snd = snd, fst fst, snd = snd, fst
} }
require.Equal(t, string(fst), e.getNextLine(t)) require.Equal(t, hex.EncodeToString(fst), e.getNextLine(t))
require.Equal(t, string(snd), e.getNextLine(t)) require.Equal(t, hex.EncodeToString(snd), e.getNextLine(t))
// tokens: missing contract hash // tokens: missing contract hash
cmdTokens := []string{"neo-go", "wallet", "nep11", "tokens", cmdTokens := []string{"neo-go", "wallet", "nep11", "tokens",
@ -248,8 +258,8 @@ func TestNEP11_OwnerOf_BalanceOf_Transfer(t *testing.T) {
// tokens: good, several tokens // tokens: good, several tokens
e.Run(t, cmdTokens...) e.Run(t, cmdTokens...)
require.Equal(t, string(fst), e.getNextLine(t)) require.Equal(t, hex.EncodeToString(fst), e.getNextLine(t))
require.Equal(t, string(snd), e.getNextLine(t)) require.Equal(t, hex.EncodeToString(snd), e.getNextLine(t))
// balance check: several tokens, ok // balance check: several tokens, ok
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...) e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
@ -276,7 +286,7 @@ func TestNEP11_OwnerOf_BalanceOf_Transfer(t *testing.T) {
// transfer: good // transfer: good
e.In.WriteString(nftOwnerPass + "\r") e.In.WriteString(nftOwnerPass + "\r")
e.Run(t, append(cmdTransfer, "--id", string(tokenID))...) e.Run(t, append(cmdTransfer, "--id", hex.EncodeToString(tokenID))...)
e.checkTxPersisted(t) e.checkTxPersisted(t)
// check balance after transfer // check balance after transfer
@ -292,7 +302,7 @@ func TestNEP11_OwnerOf_BalanceOf_Transfer(t *testing.T) {
"--to", verifyH.StringLE(), "--to", verifyH.StringLE(),
"--from", nftOwnerAddr, "--from", nftOwnerAddr,
"--token", h.StringLE(), "--token", h.StringLE(),
"--id", string(tokenID1), "--id", hex.EncodeToString(tokenID1),
"--force", "--force",
"string:some_data", "string:some_data",
} }
@ -321,10 +331,267 @@ func TestNEP11_OwnerOf_BalanceOf_Transfer(t *testing.T) {
checkBalanceResult(t, nftOwnerAddr, "0") checkBalanceResult(t, nftOwnerAddr, "0")
} }
func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) {
e := newExecutor(t, true)
tmpDir := t.TempDir()
// copy wallet to temp dir in order not to overwrite the original file
bytesRead, err := ioutil.ReadFile(validatorWallet)
require.NoError(t, err)
wall := filepath.Join(tmpDir, "my_wallet.json")
err = ioutil.WriteFile(wall, bytesRead, 0755)
require.NoError(t, err)
// deploy NeoFS Object contract
h := deployNFSContract(t, e)
mint := func(t *testing.T, containerID, objectID util.Uint256) []byte {
// mint 1.00 NFSO token by transferring 10 GAS to NFSO contract
e.In.WriteString(validatorPass + "\r")
e.Run(t, "neo-go", "wallet", "nep17", "transfer",
"--rpc-endpoint", "http://"+e.RPC.Addr,
"--wallet", wall,
"--to", h.StringLE(),
"--token", "GAS",
"--amount", "10",
"--force",
"--from", validatorAddr,
"--", "[", "hash256:"+containerID.StringLE(), "hash256:"+objectID.StringLE(), "]",
)
txMint, _ := e.checkTxPersisted(t)
// get NFT ID from AER
aer, err := e.Chain.GetAppExecResults(txMint.Hash(), trigger.Application)
require.NoError(t, err)
require.Equal(t, 1, len(aer))
require.Equal(t, 2, len(aer[0].Events))
nfsoMintEvent := aer[0].Events[1]
require.Equal(t, "Transfer", nfsoMintEvent.Name)
tokenID, err := nfsoMintEvent.Item.Value().([]stackitem.Item)[3].TryBytes()
require.NoError(t, err)
require.NotNil(t, tokenID)
return tokenID
}
container1ID := util.Uint256{1, 2, 3}
object1ID := util.Uint256{4, 5, 6}
token1ID := mint(t, container1ID, object1ID)
container2ID := util.Uint256{7, 8, 9}
object2ID := util.Uint256{10, 11, 12}
token2ID := mint(t, container2ID, object2ID)
// check properties
e.Run(t, "neo-go", "wallet", "nep11", "properties",
"--rpc-endpoint", "http://"+e.RPC.Addr,
"--token", h.StringLE(),
"--id", hex.EncodeToString(token1ID))
jProps := e.getNextLine(t)
props := make(map[string]string)
require.NoError(t, json.Unmarshal([]byte(jProps), &props))
require.Equal(t, base64.StdEncoding.EncodeToString(container1ID.BytesBE()), props["containerID"])
require.Equal(t, base64.StdEncoding.EncodeToString(object1ID.BytesBE()), props["objectID"])
e.checkEOF(t)
// check the balance
cmdCheckBalance := []string{"neo-go", "wallet", "nep11", "balance",
"--rpc-endpoint", "http://" + e.RPC.Addr,
"--wallet", wall,
"--address", validatorAddr}
checkBalanceResult := func(t *testing.T, acc string, amount string, id []byte) {
e.checkNextLine(t, "^\\s*Account\\s+"+acc)
if id == nil {
e.checkNextLine(t, "^\\s*NFSO:\\s+NeoFS Object NFT \\("+h.StringLE()+"\\)")
} else {
e.checkNextLine(t, "^\\s*NFSO:\\s+NeoFS Object NFT \\("+h.StringLE()+", "+hex.EncodeToString(id)+"\\)")
}
e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+amount+"$")
e.checkEOF(t)
}
// balance check: by symbol, token is not imported
e.RunWithError(t, append(cmdCheckBalance, "--token", "NFSO")...)
// overall NFSO balance check: by hash, ok
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
checkBalanceResult(t, validatorAddr, "2", nil)
// particular NFSO balance check: by hash, ok
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE(), "--id", hex.EncodeToString(token2ID))...)
checkBalanceResult(t, validatorAddr, "1", token2ID)
// import token
e.Run(t, "neo-go", "wallet", "nep11", "import",
"--rpc-endpoint", "http://"+e.RPC.Addr,
"--wallet", wall,
"--token", h.StringLE())
// overall balance check: by symbol, ok
e.Run(t, append(cmdCheckBalance, "--token", "NFSO")...)
checkBalanceResult(t, validatorAddr, "2", nil)
// particular balance check: by symbol, ok
e.Run(t, append(cmdCheckBalance, "--token", "NFSO", "--id", hex.EncodeToString(token1ID))...)
checkBalanceResult(t, validatorAddr, "1", token1ID)
// remove token from wallet
e.In.WriteString("y\r")
e.Run(t, "neo-go", "wallet", "nep11", "remove",
"--wallet", wall, "--token", h.StringLE())
// ownerOfD: missing contract hash
cmdOwnerOf := []string{"neo-go", "wallet", "nep11", "ownerOfD",
"--rpc-endpoint", "http://" + e.RPC.Addr,
}
e.RunWithError(t, cmdOwnerOf...)
cmdOwnerOf = append(cmdOwnerOf, "--token", h.StringLE())
// ownerOfD: missing token ID
e.RunWithError(t, cmdOwnerOf...)
cmdOwnerOf = append(cmdOwnerOf, "--id", hex.EncodeToString(token1ID))
// ownerOfD: good
e.Run(t, cmdOwnerOf...)
e.checkNextLine(t, validatorAddr)
// tokensOf: missing contract hash
cmdTokensOf := []string{"neo-go", "wallet", "nep11", "tokensOf",
"--rpc-endpoint", "http://" + e.RPC.Addr,
}
e.RunWithError(t, cmdTokensOf...)
cmdTokensOf = append(cmdTokensOf, "--token", h.StringLE())
// tokensOf: missing owner address
e.RunWithError(t, cmdTokensOf...)
cmdTokensOf = append(cmdTokensOf, "--address", validatorAddr)
// tokensOf: good
e.Run(t, cmdTokensOf...)
require.Equal(t, hex.EncodeToString(token1ID), e.getNextLine(t))
require.Equal(t, hex.EncodeToString(token2ID), e.getNextLine(t))
e.checkEOF(t)
// properties: no contract
cmdProperties := []string{
"neo-go", "wallet", "nep11", "properties",
"--rpc-endpoint", "http://" + e.RPC.Addr,
}
e.RunWithError(t, cmdProperties...)
cmdProperties = append(cmdProperties, "--token", h.StringLE())
// properties: no token ID
e.RunWithError(t, cmdProperties...)
cmdProperties = append(cmdProperties, "--id", hex.EncodeToString(token2ID))
// properties: ok
e.Run(t, cmdProperties...)
jProps = e.getNextLine(t)
props = make(map[string]string)
require.NoError(t, json.Unmarshal([]byte(jProps), &props))
require.Equal(t, base64.StdEncoding.EncodeToString(container2ID.BytesBE()), props["containerID"])
require.Equal(t, base64.StdEncoding.EncodeToString(object2ID.BytesBE()), props["objectID"])
e.checkEOF(t)
// tokensOf: good, several tokens
e.Run(t, cmdTokensOf...)
fst, snd := token1ID, token2ID
if bytes.Compare(token1ID, token2ID) == 1 {
fst, snd = snd, fst
}
require.Equal(t, hex.EncodeToString(fst), e.getNextLine(t))
require.Equal(t, hex.EncodeToString(snd), e.getNextLine(t))
// tokens: missing contract hash
cmdTokens := []string{"neo-go", "wallet", "nep11", "tokens",
"--rpc-endpoint", "http://" + e.RPC.Addr,
}
e.RunWithError(t, cmdTokens...)
cmdTokens = append(cmdTokens, "--token", h.StringLE())
// tokens: good, several tokens
e.Run(t, cmdTokens...)
require.Equal(t, hex.EncodeToString(fst), e.getNextLine(t))
require.Equal(t, hex.EncodeToString(snd), e.getNextLine(t))
// balance check: several tokens, ok
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
checkBalanceResult(t, validatorAddr, "2", nil)
cmdTransfer := []string{
"neo-go", "wallet", "nep11", "transfer",
"--rpc-endpoint", "http://" + e.RPC.Addr,
"--wallet", wall,
"--to", nftOwnerAddr,
"--from", validatorAddr,
"--force",
}
// transfer: unimported token with symbol id specified
e.In.WriteString(validatorPass + "\r")
e.RunWithError(t, append(cmdTransfer,
"--token", "NFSO")...)
cmdTransfer = append(cmdTransfer, "--token", h.StringLE())
// transfer: no id specified
e.In.WriteString(validatorPass + "\r")
e.RunWithError(t, cmdTransfer...)
// transfer: good
e.In.WriteString(validatorPass + "\r")
e.Run(t, append(cmdTransfer, "--id", hex.EncodeToString(token1ID))...)
e.checkTxPersisted(t)
// check balance after transfer
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
checkBalanceResult(t, validatorAddr, "1", nil) // only token2ID expected to be on the balance
// transfer: good, 1/4 of the balance, to NEP-11-Payable contract, with data
verifyH := deployVerifyContract(t, e)
cmdTransfer = []string{
"neo-go", "wallet", "nep11", "transfer",
"--rpc-endpoint", "http://" + e.RPC.Addr,
"--wallet", wall,
"--to", verifyH.StringLE(),
"--from", validatorAddr,
"--token", h.StringLE(),
"--id", hex.EncodeToString(token2ID),
"--amount", "0.25",
"--force",
"string:some_data",
}
e.In.WriteString(validatorPass + "\r")
e.Run(t, cmdTransfer...)
tx, _ := e.checkTxPersisted(t)
// check OnNEP11Payment event
aer, err := e.Chain.GetAppExecResults(tx.Hash(), trigger.Application)
require.NoError(t, err)
require.Equal(t, 2, len(aer[0].Events))
validatorHash, err := address.StringToUint160(validatorAddr)
require.NoError(t, err)
require.Equal(t, state.NotificationEvent{
ScriptHash: verifyH,
Name: "OnNEP11Payment",
Item: stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray(validatorHash.BytesBE()),
stackitem.NewBigInteger(big.NewInt(25)),
stackitem.NewByteArray(token2ID),
stackitem.NewByteArray([]byte("some_data")),
}),
}, aer[0].Events[1])
// check balance after transfer
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
checkBalanceResult(t, validatorAddr, "0.75", nil)
}
func deployNFSContract(t *testing.T, e *executor) util.Uint160 {
return deployContract(t, e, "../examples/nft-d/nft.go", "../examples/nft-d/nft.yml", validatorWallet, validatorAddr, validatorPass)
}
func deployNFTContract(t *testing.T, e *executor) util.Uint160 { func deployNFTContract(t *testing.T, e *executor) util.Uint160 {
return deployContract(t, e, "../examples/nft-nd/nft.go", "../examples/nft-nd/nft.yml", nftOwnerWallet, nftOwnerAddr, nftOwnerPass) return deployContract(t, e, "../examples/nft-nd/nft.go", "../examples/nft-nd/nft.yml", nftOwnerWallet, nftOwnerAddr, nftOwnerPass)
} }
func deployNNSContract(t *testing.T, e *executor) util.Uint160 { func deployNNSContract(t *testing.T, e *executor) util.Uint160 {
return deployContract(t, e, "../examples/nft-nd-nns/", "../examples/nft-nd-nns/nns.yml", validatorWallet, validatorAddr, "one") return deployContract(t, e, "../examples/nft-nd-nns/", "../examples/nft-nd-nns/nns.yml", validatorWallet, validatorAddr, validatorPass)
} }

View file

@ -1,6 +1,7 @@
package wallet package wallet
import ( import (
"encoding/hex"
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
@ -31,7 +32,7 @@ func newNEP11Commands() []cli.Command {
} }
tokenID := cli.StringFlag{ tokenID := cli.StringFlag{
Name: "id", Name: "id",
Usage: "Token ID", Usage: "Hex-encoded token ID",
} }
balanceFlags := make([]cli.Flag, len(baseBalanceFlags)) balanceFlags := make([]cli.Flag, len(baseBalanceFlags))
@ -107,7 +108,17 @@ func newNEP11Commands() []cli.Command {
Name: "ownerOf", Name: "ownerOf",
Usage: "print owner of non-divisible NEP-11 token with the specified ID", Usage: "print owner of non-divisible NEP-11 token with the specified ID",
UsageText: "ownerOf --rpc-endpoint <node> --timeout <time> --token <hash> --id <token-id>", UsageText: "ownerOf --rpc-endpoint <node> --timeout <time> --token <hash> --id <token-id>",
Action: printNEP11Owner, Action: printNEP11NDOwner,
Flags: append([]cli.Flag{
tokenAddressFlag,
tokenID,
}, options.RPC...),
},
{
Name: "ownerOfD",
Usage: "print set of owners of divisible NEP-11 token with the specified ID",
UsageText: "ownerOfD --rpc-endpoint <node> --timeout <time> --token <hash> --id <token-id>",
Action: printNEP11DOwner,
Flags: append([]cli.Flag{ Flags: append([]cli.Flag{
tokenAddressFlag, tokenAddressFlag,
tokenID, tokenID,
@ -196,6 +207,10 @@ func getNEP11Balance(ctx *cli.Context) error {
} }
tokenID := ctx.String("id") tokenID := ctx.String("id")
tokenIDBytes, err := hex.DecodeString(tokenID)
if err != nil {
return cli.NewExitError(fmt.Errorf("invalid tokenID bytes: %w", err), 1)
}
for k, acc := range accounts { for k, acc := range accounts {
addrHash, err := address.StringToUint160(acc.Address) addrHash, err := address.StringToUint160(acc.Address)
if err != nil { if err != nil {
@ -208,10 +223,10 @@ func getNEP11Balance(ctx *cli.Context) error {
fmt.Fprintf(ctx.App.Writer, "Account %s\n", acc.Address) fmt.Fprintf(ctx.App.Writer, "Account %s\n", acc.Address)
var amount int64 var amount int64
if tokenID == "" { if len(tokenIDBytes) == 0 {
amount, err = c.NEP11BalanceOf(token.Hash, addrHash) amount, err = c.NEP11BalanceOf(token.Hash, addrHash)
} else { } else {
amount, err = c.NEP11DBalanceOf(token.Hash, addrHash, tokenID) amount, err = c.NEP11DBalanceOf(token.Hash, addrHash, tokenIDBytes)
} }
if err != nil { if err != nil {
continue continue
@ -220,7 +235,7 @@ func getNEP11Balance(ctx *cli.Context) error {
format := "%s: %s (%s)\n" format := "%s: %s (%s)\n"
formatArgs := []interface{}{token.Symbol, token.Name, token.Hash.StringLE()} formatArgs := []interface{}{token.Symbol, token.Name, token.Hash.StringLE()}
if tokenID != "" { if len(tokenIDBytes) != 0 {
format = "%s: %s (%s, %s)\n" format = "%s: %s (%s, %s)\n"
formatArgs = append(formatArgs, tokenID) formatArgs = append(formatArgs, tokenID)
} }
@ -234,7 +249,7 @@ func transferNEP11(ctx *cli.Context) error {
return transferNEP(ctx, manifest.NEP11StandardName) return transferNEP(ctx, manifest.NEP11StandardName)
} }
func signAndSendNEP11Transfer(ctx *cli.Context, c *client.Client, acc *wallet.Account, token, to util.Uint160, tokenID string, amount *big.Int, data interface{}, cosigners []client.SignerAccount) error { func signAndSendNEP11Transfer(ctx *cli.Context, c *client.Client, acc *wallet.Account, token, to util.Uint160, tokenID []byte, amount *big.Int, data interface{}, cosigners []client.SignerAccount) error {
gas := flags.Fixed8FromContext(ctx, "gas") gas := flags.Fixed8FromContext(ctx, "gas")
sysgas := flags.Fixed8FromContext(ctx, "sysgas") sysgas := flags.Fixed8FromContext(ctx, "sysgas")
@ -279,7 +294,15 @@ func signAndSendNEP11Transfer(ctx *cli.Context, c *client.Client, acc *wallet.Ac
return nil return nil
} }
func printNEP11Owner(ctx *cli.Context) error { func printNEP11NDOwner(ctx *cli.Context) error {
return printNEP11Owner(ctx, false)
}
func printNEP11DOwner(ctx *cli.Context) error {
return printNEP11Owner(ctx, true)
}
func printNEP11Owner(ctx *cli.Context, divisible bool) error {
var err error var err error
tokenHash := ctx.Generic("token").(*flags.Address) tokenHash := ctx.Generic("token").(*flags.Address)
if !tokenHash.IsSet { if !tokenHash.IsSet {
@ -290,6 +313,10 @@ func printNEP11Owner(ctx *cli.Context) error {
if tokenID == "" { if tokenID == "" {
return cli.NewExitError(errors.New("token ID should be specified"), 1) return cli.NewExitError(errors.New("token ID should be specified"), 1)
} }
tokenIDBytes, err := hex.DecodeString(tokenID)
if err != nil {
return cli.NewExitError(fmt.Errorf("invalid tokenID bytes: %w", err), 1)
}
gctx, cancel := options.GetTimeoutContext(ctx) gctx, cancel := options.GetTimeoutContext(ctx)
defer cancel() defer cancel()
@ -299,12 +326,22 @@ func printNEP11Owner(ctx *cli.Context) error {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
result, err := c.NEP11NDOwnerOf(tokenHash.Uint160(), tokenID) if divisible {
if err != nil { result, err := c.NEP11DOwnerOf(tokenHash.Uint160(), tokenIDBytes)
return cli.NewExitError(fmt.Sprintf("failed to call NEP-11 `ownerOf` method: %s", err.Error()), 1) if err != nil {
return cli.NewExitError(fmt.Sprintf("failed to call NEP-11 divisible `ownerOf` method: %s", err.Error()), 1)
}
for _, h := range result {
fmt.Fprintln(ctx.App.Writer, address.Uint160ToString(h))
}
} else {
result, err := c.NEP11NDOwnerOf(tokenHash.Uint160(), tokenIDBytes)
if err != nil {
return cli.NewExitError(fmt.Sprintf("failed to call NEP-11 non-divisible `ownerOf` method: %s", err.Error()), 1)
}
fmt.Fprintln(ctx.App.Writer, address.Uint160ToString(result))
} }
fmt.Fprintln(ctx.App.Writer, address.Uint160ToString(result))
return nil return nil
} }
@ -334,7 +371,7 @@ func printNEP11TokensOf(ctx *cli.Context) error {
} }
for i := range result { for i := range result {
fmt.Fprintln(ctx.App.Writer, result[i]) fmt.Fprintln(ctx.App.Writer, hex.EncodeToString(result[i]))
} }
return nil return nil
} }
@ -360,7 +397,7 @@ func printNEP11Tokens(ctx *cli.Context) error {
} }
for i := range result { for i := range result {
fmt.Fprintln(ctx.App.Writer, result[i]) fmt.Fprintln(ctx.App.Writer, hex.EncodeToString(result[i]))
} }
return nil return nil
} }
@ -376,6 +413,10 @@ func printNEP11Properties(ctx *cli.Context) error {
if tokenID == "" { if tokenID == "" {
return cli.NewExitError(errors.New("token ID should be specified"), 1) return cli.NewExitError(errors.New("token ID should be specified"), 1)
} }
tokenIDBytes, err := hex.DecodeString(tokenID)
if err != nil {
return cli.NewExitError(fmt.Errorf("invalid tokenID bytes: %w", err), 1)
}
gctx, cancel := options.GetTimeoutContext(ctx) gctx, cancel := options.GetTimeoutContext(ctx)
defer cancel() defer cancel()
@ -385,7 +426,7 @@ func printNEP11Properties(ctx *cli.Context) error {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
result, err := c.NEP11Properties(tokenHash.Uint160(), tokenID) result, err := c.NEP11Properties(tokenHash.Uint160(), tokenIDBytes)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Sprintf("failed to call NEP-11 `properties` method: %s", err.Error()), 1) return cli.NewExitError(fmt.Sprintf("failed to call NEP-11 `properties` method: %s", err.Error()), 1)
} }

View file

@ -1,6 +1,7 @@
package wallet package wallet
import ( import (
"encoding/hex"
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
@ -618,14 +619,18 @@ func transferNEP(ctx *cli.Context, standard string) error {
if tokenID == "" { if tokenID == "" {
return cli.NewExitError(errors.New("token ID should be specified"), 1) return cli.NewExitError(errors.New("token ID should be specified"), 1)
} }
tokenIDBytes, err := hex.DecodeString(tokenID)
if err != nil {
return cli.NewExitError(fmt.Errorf("invalid token ID: %w", err), 1)
}
if amountArg == "" { if amountArg == "" {
return signAndSendNEP11Transfer(ctx, c, acc, token.Hash, to, tokenID, nil, data, cosignersAccounts) return signAndSendNEP11Transfer(ctx, c, acc, token.Hash, to, tokenIDBytes, nil, data, cosignersAccounts)
} }
amount, err := fixedn.FromString(amountArg, int(token.Decimals)) amount, err := fixedn.FromString(amountArg, int(token.Decimals))
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("invalid amount: %w", err), 1) return cli.NewExitError(fmt.Errorf("invalid amount: %w", err), 1)
} }
return signAndSendNEP11Transfer(ctx, c, acc, token.Hash, to, tokenID, amount, data, cosignersAccounts) return signAndSendNEP11Transfer(ctx, c, acc, token.Hash, to, tokenIDBytes, amount, data, cosignersAccounts)
default: default:
return cli.NewExitError(fmt.Errorf("unsupported token standard %s", standard), 1) return cli.NewExitError(fmt.Errorf("unsupported token standard %s", standard), 1)
} }

View file

@ -10,6 +10,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/interop" "github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/contract" "github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/iterator" "github.com/nspcc-dev/neo-go/pkg/interop/iterator"
"github.com/nspcc-dev/neo-go/pkg/interop/native/crypto"
"github.com/nspcc-dev/neo-go/pkg/interop/native/gas" "github.com/nspcc-dev/neo-go/pkg/interop/native/gas"
"github.com/nspcc-dev/neo-go/pkg/interop/native/management" "github.com/nspcc-dev/neo-go/pkg/interop/native/management"
"github.com/nspcc-dev/neo-go/pkg/interop/native/std" "github.com/nspcc-dev/neo-go/pkg/interop/native/std"
@ -30,7 +31,7 @@ const (
balancePrefix = "b" balancePrefix = "b"
// tokenOwnerPrefix contains map from [token id + owner] to token's owner. // tokenOwnerPrefix contains map from [token id + owner] to token's owner.
tokenOwnerPrefix = "t" tokenOwnerPrefix = "t"
// tokenPrefix contains map from token id to empty array. // tokenPrefix contains map from token id to its properties (serialised containerID + objectID).
tokenPrefix = "i" tokenPrefix = "i"
) )
@ -231,12 +232,13 @@ func Properties(id []byte) map[string]string {
if !isTokenValid(ctx, id) { if !isTokenValid(ctx, id) {
panic("unknown token") panic("unknown token")
} }
t := std.Deserialize(id).(ObjectIdentifier) key := mkTokenKey(id)
props := storage.Get(ctx, key).([]byte)
t := std.Deserialize(props).(ObjectIdentifier)
result := map[string]string{ result := map[string]string{
"name": "NFSO " + string(id), "name": "NeoFS Object " + std.Base64Encode(id), // Not a hex for contract simplicity.
"fullName": "NeoFS Object", "containerID": std.Base64Encode(t.ContainerID),
"containerID": string(t.ContainerID), "objectID": std.Base64Encode(t.ObjectID),
"objectID": string(t.ObjectID),
} }
return result return result
} }
@ -360,11 +362,11 @@ func OnNEP17Payment(from interop.Hash160, amount int, data interface{}) {
panic("invalid 'data'") panic("invalid 'data'")
} }
containerID := tokenInfo[0].([]byte) containerID := tokenInfo[0].([]byte)
if len(containerID) != 32 { if len(containerID) != interop.Hash256Len {
panic("invalid container ID") panic("invalid container ID")
} }
objectID := tokenInfo[1].([]byte) objectID := tokenInfo[1].([]byte)
if len(objectID) != 32 { if len(objectID) != interop.Hash256Len {
panic("invalid object ID") panic("invalid object ID")
} }
@ -372,14 +374,15 @@ func OnNEP17Payment(from interop.Hash160, amount int, data interface{}) {
ContainerID: containerID, ContainerID: containerID,
ObjectID: objectID, ObjectID: objectID,
} }
id := std.Serialize(t) props := std.Serialize(t)
id := crypto.Ripemd160(props)
var ctx = storage.GetContext() var ctx = storage.GetContext()
if isTokenValid(ctx, id) { if isTokenValid(ctx, id) {
panic("NFSO for the specified address is already minted") panic("NFSO for the specified object is already minted")
} }
key := mkTokenKey(id) key := mkTokenKey(id)
storage.Put(ctx, key, []byte{}) storage.Put(ctx, key, props)
total := totalSupply(ctx) total := totalSupply(ctx)

View file

@ -1,4 +1,4 @@
name: "NFSO NFT" name: "NeoFS Object NFT"
sourceurl: https://github.com/nspcc-dev/neo-go/ sourceurl: https://github.com/nspcc-dev/neo-go/
supportedstandards: ["NEP-11"] supportedstandards: ["NEP-11"]
safemethods: ["balanceOf", "decimals", "symbol", "totalSupply", "tokensOf", "ownerOf", "properties", "tokens"] safemethods: ["balanceOf", "decimals", "symbol", "totalSupply", "tokensOf", "ownerOf", "properties", "tokens"]

View file

@ -273,7 +273,7 @@ func Properties(id []byte) map[string]string {
panic("unknown token") panic("unknown token")
} }
result := map[string]string{ result := map[string]string{
"name": "HASHY " + string(id), "name": "HASHY " + std.Base64Encode(id), // Not a hex for contract simplicity.
} }
return result return result
} }