forked from TrueCloudLab/neoneo-go
cli: refactor nep11-related commands and add tests
Specify tokenID via hex-encoded string.
This commit is contained in:
parent
22c39d3916
commit
3c75f047c1
7 changed files with 371 additions and 54 deletions
|
@ -40,6 +40,7 @@ const (
|
|||
testWalletAccount = "Nfyz4KcsgYepRJw1W5C2uKCi6QWKf7v6gG"
|
||||
|
||||
validatorWallet = "testdata/wallet1_solo.json"
|
||||
validatorPass = "one"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
@ -2,12 +2,15 @@ package main
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||
|
@ -35,6 +38,8 @@ func TestNEP11Import(t *testing.T) {
|
|||
|
||||
// deploy NFT NeoNameService contract
|
||||
nnsContractHash := deployNNSContract(t, e)
|
||||
// deploy NFT-D NeoFS Object contract
|
||||
nfsContractHash := deployNFSContract(t, e)
|
||||
neoContractHash, err := e.Chain.GetNativeContractScriptHash(nativenames.Neo)
|
||||
require.NoError(t, err)
|
||||
e.Run(t, "neo-go", "wallet", "init", "--wallet", walletPath)
|
||||
|
@ -47,33 +52,38 @@ func TestNEP11Import(t *testing.T) {
|
|||
// missing token hash
|
||||
e.RunWithError(t, args...)
|
||||
|
||||
// good
|
||||
// good: non-divisible
|
||||
e.Run(t, append(args, "--token", nnsContractHash.StringLE())...)
|
||||
|
||||
// good: divisible
|
||||
e.Run(t, append(args, "--token", nfsContractHash.StringLE())...)
|
||||
|
||||
// already exists
|
||||
e.RunWithError(t, append(args, "--token", nnsContractHash.StringLE())...)
|
||||
|
||||
// not a NEP-11 token
|
||||
e.RunWithError(t, append(args, "--token", neoContractHash.StringLE())...)
|
||||
|
||||
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))
|
||||
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("WithToken", func(t *testing.T) {
|
||||
e.Run(t, "neo-go", "wallet", "nep11", "info",
|
||||
"--wallet", walletPath, "--token", nnsContractHash.StringLE())
|
||||
checkNNSInfo(t)
|
||||
checkInfo(t, nnsContractHash, "NameService", "NNS", 0)
|
||||
})
|
||||
t.Run("NoToken", func(t *testing.T) {
|
||||
e.Run(t, "neo-go", "wallet", "nep11", "info",
|
||||
"--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())
|
||||
e.Run(t, "neo-go", "wallet", "nep11", "info",
|
||||
"--wallet", walletPath)
|
||||
checkInfo(t, nfsContractHash, "NeoFS Object NFT", "NFSO", 2)
|
||||
_, err := e.Out.ReadString('\n')
|
||||
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)
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
|
@ -190,7 +201,7 @@ func TestNEP11_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
|||
|
||||
// ownerOf: missing token ID
|
||||
e.RunWithError(t, cmdOwnerOf...)
|
||||
cmdOwnerOf = append(cmdOwnerOf, "--id", string(tokenID))
|
||||
cmdOwnerOf = append(cmdOwnerOf, "--id", hex.EncodeToString(tokenID))
|
||||
|
||||
// ownerOf: good
|
||||
e.Run(t, cmdOwnerOf...)
|
||||
|
@ -209,7 +220,7 @@ func TestNEP11_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
|||
|
||||
// tokensOf: good
|
||||
e.Run(t, cmdTokensOf...)
|
||||
require.Equal(t, string(tokenID), e.getNextLine(t))
|
||||
require.Equal(t, hex.EncodeToString(tokenID), e.getNextLine(t))
|
||||
|
||||
// properties: no contract
|
||||
cmdProperties := []string{
|
||||
|
@ -221,12 +232,11 @@ func TestNEP11_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
|||
|
||||
// properties: no token ID
|
||||
e.RunWithError(t, cmdProperties...)
|
||||
cmdProperties = append(cmdProperties, "--id", string(tokenID))
|
||||
cmdProperties = append(cmdProperties, "--id", hex.EncodeToString(tokenID))
|
||||
|
||||
// properties: ok
|
||||
e.Run(t, cmdProperties...)
|
||||
marshalledID := strings.Replace(string(tokenID), "+", "\\u002B", -1)
|
||||
require.Equal(t, fmt.Sprintf(`{"name":"HASHY %s"}`, marshalledID), e.getNextLine(t))
|
||||
require.Equal(t, fmt.Sprintf(`{"name":"HASHY %s"}`, base64.StdEncoding.EncodeToString(tokenID)), e.getNextLine(t))
|
||||
|
||||
// tokensOf: good, several tokens
|
||||
tokenID1 := mint(t)
|
||||
|
@ -236,8 +246,8 @@ func TestNEP11_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
|||
fst, snd = snd, fst
|
||||
}
|
||||
|
||||
require.Equal(t, string(fst), e.getNextLine(t))
|
||||
require.Equal(t, string(snd), e.getNextLine(t))
|
||||
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",
|
||||
|
@ -248,8 +258,8 @@ func TestNEP11_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
|||
|
||||
// tokens: good, several tokens
|
||||
e.Run(t, cmdTokens...)
|
||||
require.Equal(t, string(fst), e.getNextLine(t))
|
||||
require.Equal(t, string(snd), e.getNextLine(t))
|
||||
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())...)
|
||||
|
@ -276,7 +286,7 @@ func TestNEP11_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
|||
|
||||
// transfer: good
|
||||
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)
|
||||
|
||||
// check balance after transfer
|
||||
|
@ -292,7 +302,7 @@ func TestNEP11_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
|||
"--to", verifyH.StringLE(),
|
||||
"--from", nftOwnerAddr,
|
||||
"--token", h.StringLE(),
|
||||
"--id", string(tokenID1),
|
||||
"--id", hex.EncodeToString(tokenID1),
|
||||
"--force",
|
||||
"string:some_data",
|
||||
}
|
||||
|
@ -321,10 +331,267 @@ func TestNEP11_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
|||
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 {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package wallet
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
@ -31,7 +32,7 @@ func newNEP11Commands() []cli.Command {
|
|||
}
|
||||
tokenID := cli.StringFlag{
|
||||
Name: "id",
|
||||
Usage: "Token ID",
|
||||
Usage: "Hex-encoded token ID",
|
||||
}
|
||||
|
||||
balanceFlags := make([]cli.Flag, len(baseBalanceFlags))
|
||||
|
@ -107,7 +108,17 @@ func newNEP11Commands() []cli.Command {
|
|||
Name: "ownerOf",
|
||||
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>",
|
||||
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{
|
||||
tokenAddressFlag,
|
||||
tokenID,
|
||||
|
@ -196,6 +207,10 @@ func getNEP11Balance(ctx *cli.Context) error {
|
|||
}
|
||||
|
||||
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 {
|
||||
addrHash, err := address.StringToUint160(acc.Address)
|
||||
if err != nil {
|
||||
|
@ -208,10 +223,10 @@ func getNEP11Balance(ctx *cli.Context) error {
|
|||
fmt.Fprintf(ctx.App.Writer, "Account %s\n", acc.Address)
|
||||
|
||||
var amount int64
|
||||
if tokenID == "" {
|
||||
if len(tokenIDBytes) == 0 {
|
||||
amount, err = c.NEP11BalanceOf(token.Hash, addrHash)
|
||||
} else {
|
||||
amount, err = c.NEP11DBalanceOf(token.Hash, addrHash, tokenID)
|
||||
amount, err = c.NEP11DBalanceOf(token.Hash, addrHash, tokenIDBytes)
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
|
@ -220,7 +235,7 @@ func getNEP11Balance(ctx *cli.Context) error {
|
|||
|
||||
format := "%s: %s (%s)\n"
|
||||
formatArgs := []interface{}{token.Symbol, token.Name, token.Hash.StringLE()}
|
||||
if tokenID != "" {
|
||||
if len(tokenIDBytes) != 0 {
|
||||
format = "%s: %s (%s, %s)\n"
|
||||
formatArgs = append(formatArgs, tokenID)
|
||||
}
|
||||
|
@ -234,7 +249,7 @@ func transferNEP11(ctx *cli.Context) error {
|
|||
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")
|
||||
sysgas := flags.Fixed8FromContext(ctx, "sysgas")
|
||||
|
||||
|
@ -279,7 +294,15 @@ func signAndSendNEP11Transfer(ctx *cli.Context, c *client.Client, acc *wallet.Ac
|
|||
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
|
||||
tokenHash := ctx.Generic("token").(*flags.Address)
|
||||
if !tokenHash.IsSet {
|
||||
|
@ -290,6 +313,10 @@ func printNEP11Owner(ctx *cli.Context) error {
|
|||
if tokenID == "" {
|
||||
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)
|
||||
defer cancel()
|
||||
|
@ -299,12 +326,22 @@ func printNEP11Owner(ctx *cli.Context) error {
|
|||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
||||
result, err := c.NEP11NDOwnerOf(tokenHash.Uint160(), tokenID)
|
||||
if divisible {
|
||||
result, err := c.NEP11DOwnerOf(tokenHash.Uint160(), tokenIDBytes)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Sprintf("failed to call NEP-11 `ownerOf` method: %s", err.Error()), 1)
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -334,7 +371,7 @@ func printNEP11TokensOf(ctx *cli.Context) error {
|
|||
}
|
||||
|
||||
for i := range result {
|
||||
fmt.Fprintln(ctx.App.Writer, result[i])
|
||||
fmt.Fprintln(ctx.App.Writer, hex.EncodeToString(result[i]))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -360,7 +397,7 @@ func printNEP11Tokens(ctx *cli.Context) error {
|
|||
}
|
||||
|
||||
for i := range result {
|
||||
fmt.Fprintln(ctx.App.Writer, result[i])
|
||||
fmt.Fprintln(ctx.App.Writer, hex.EncodeToString(result[i]))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -376,6 +413,10 @@ func printNEP11Properties(ctx *cli.Context) error {
|
|||
if tokenID == "" {
|
||||
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)
|
||||
defer cancel()
|
||||
|
@ -385,7 +426,7 @@ func printNEP11Properties(ctx *cli.Context) error {
|
|||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
||||
result, err := c.NEP11Properties(tokenHash.Uint160(), tokenID)
|
||||
result, err := c.NEP11Properties(tokenHash.Uint160(), tokenIDBytes)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Sprintf("failed to call NEP-11 `properties` method: %s", err.Error()), 1)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package wallet
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
@ -618,14 +619,18 @@ func transferNEP(ctx *cli.Context, standard string) error {
|
|||
if tokenID == "" {
|
||||
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 == "" {
|
||||
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))
|
||||
if err != nil {
|
||||
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:
|
||||
return cli.NewExitError(fmt.Errorf("unsupported token standard %s", standard), 1)
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"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/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/management"
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/std"
|
||||
|
@ -30,7 +31,7 @@ const (
|
|||
balancePrefix = "b"
|
||||
// tokenOwnerPrefix contains map from [token id + owner] to token's owner.
|
||||
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"
|
||||
)
|
||||
|
||||
|
@ -231,12 +232,13 @@ func Properties(id []byte) map[string]string {
|
|||
if !isTokenValid(ctx, id) {
|
||||
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{
|
||||
"name": "NFSO " + string(id),
|
||||
"fullName": "NeoFS Object",
|
||||
"containerID": string(t.ContainerID),
|
||||
"objectID": string(t.ObjectID),
|
||||
"name": "NeoFS Object " + std.Base64Encode(id), // Not a hex for contract simplicity.
|
||||
"containerID": std.Base64Encode(t.ContainerID),
|
||||
"objectID": std.Base64Encode(t.ObjectID),
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
@ -360,11 +362,11 @@ func OnNEP17Payment(from interop.Hash160, amount int, data interface{}) {
|
|||
panic("invalid 'data'")
|
||||
}
|
||||
containerID := tokenInfo[0].([]byte)
|
||||
if len(containerID) != 32 {
|
||||
if len(containerID) != interop.Hash256Len {
|
||||
panic("invalid container ID")
|
||||
}
|
||||
objectID := tokenInfo[1].([]byte)
|
||||
if len(objectID) != 32 {
|
||||
if len(objectID) != interop.Hash256Len {
|
||||
panic("invalid object ID")
|
||||
}
|
||||
|
||||
|
@ -372,14 +374,15 @@ func OnNEP17Payment(from interop.Hash160, amount int, data interface{}) {
|
|||
ContainerID: containerID,
|
||||
ObjectID: objectID,
|
||||
}
|
||||
id := std.Serialize(t)
|
||||
props := std.Serialize(t)
|
||||
id := crypto.Ripemd160(props)
|
||||
|
||||
var ctx = storage.GetContext()
|
||||
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)
|
||||
storage.Put(ctx, key, []byte{})
|
||||
storage.Put(ctx, key, props)
|
||||
|
||||
total := totalSupply(ctx)
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
name: "NFSO NFT"
|
||||
name: "NeoFS Object NFT"
|
||||
sourceurl: https://github.com/nspcc-dev/neo-go/
|
||||
supportedstandards: ["NEP-11"]
|
||||
safemethods: ["balanceOf", "decimals", "symbol", "totalSupply", "tokensOf", "ownerOf", "properties", "tokens"]
|
||||
|
|
|
@ -273,7 +273,7 @@ func Properties(id []byte) map[string]string {
|
|||
panic("unknown token")
|
||||
}
|
||||
result := map[string]string{
|
||||
"name": "HASHY " + string(id),
|
||||
"name": "HASHY " + std.Base64Encode(id), // Not a hex for contract simplicity.
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue