diff --git a/tests/e2e/collection/cli_test.go b/tests/e2e/collection/cli_test.go new file mode 100644 index 0000000000..8bb6422b12 --- /dev/null +++ b/tests/e2e/collection/cli_test.go @@ -0,0 +1,19 @@ +//go:build e2e +// +build e2e + +package collection + +import ( + "testing" + + "github.com/cosmos/cosmos-sdk/testutil/network" + "github.com/stretchr/testify/suite" + + "github.com/Finschia/finschia-sdk/simapp" +) + +func TestIntegrationTestSuite(t *testing.T) { + cfg := network.DefaultConfig(simapp.NewTestNetworkFixture) + cfg.NumValidators = 1 + suite.Run(t, NewE2ETestSuite(cfg)) +} diff --git a/tests/e2e/collection/suite.go b/tests/e2e/collection/suite.go new file mode 100644 index 0000000000..9d01050023 --- /dev/null +++ b/tests/e2e/collection/suite.go @@ -0,0 +1,222 @@ +package collection + +import ( + "fmt" + + abci "github.com/cometbft/cometbft/abci/types" + "github.com/cosmos/cosmos-sdk/codec/address" + "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/cosmos/cosmos-sdk/testutil" + "github.com/cosmos/gogoproto/proto" + + cmath "cosmossdk.io/math" + "github.com/cosmos/cosmos-sdk/client/flags" + clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" + "github.com/cosmos/cosmos-sdk/testutil/network" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/suite" + + "github.com/Finschia/finschia-sdk/x/collection" + "github.com/Finschia/finschia-sdk/x/collection/client/cli" +) + +type E2ETestSuite struct { + suite.Suite + + cfg network.Config + network *network.Network + setupHeight int64 + + commonArgs []string + + vendor sdk.AccAddress + operator sdk.AccAddress + customer sdk.AccAddress + stranger sdk.AccAddress + contractID string + nftClassID string + tokenIDs map[string]string +} + +func NewE2ETestSuite(cfg network.Config) *E2ETestSuite { + return &E2ETestSuite{cfg: cfg} +} + +func (s *E2ETestSuite) SetupSuite() { + s.T().Log("setting up collection e2e test suite") + + genesisState := s.cfg.GenesisState + collectionGenesisState := new(collection.GenesisState) + s.Require().NoError(s.cfg.Codec.UnmarshalJSON(genesisState[collection.ModuleName], collectionGenesisState)) + + var err error + s.network, err = network.New(s.T(), s.T().TempDir(), s.cfg) + s.Require().NoError(err) + + s.commonArgs = []string{ + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, cmath.NewInt(100))).String()), + } + + s.vendor = s.network.Validators[0].Address + s.operator = s.createAccount("operator") + s.customer = s.createAccount("customer") + s.stranger = s.createAccount("stranger") + + // vendor creates nft token class + s.contractID = s.createContract(s.vendor) + s.nftClassID = s.createNFTClass(s.contractID, s.vendor) + + // mint nfts + s.tokenIDs = make(map[string]string, 4) + for _, to := range []sdk.AccAddress{s.customer, s.operator, s.vendor, s.stranger} { + s.tokenIDs[to.String()] = s.mintNFT(s.contractID, s.vendor, to, s.nftClassID) + } + + // grant all the permissions to operator + for _, pv := range collection.Permission_value { + permission := collection.Permission(pv) + if permission == collection.PermissionUnspecified { + continue + } + s.grant(s.contractID, s.vendor, s.operator, permission) + } + + // customer and vendor approves the operator to manipulate its tokens, so vendor can do OperatorXXX (Send or Burn) later. + s.authorizeOperator(s.contractID, s.vendor, s.operator) + s.authorizeOperator(s.contractID, s.customer, s.operator) + // for the revocation. + s.authorizeOperator(s.contractID, s.operator, s.vendor) + + s.Require().NoError(err) + s.Require().NoError(s.network.WaitForNextBlock()) + s.setupHeight, err = s.network.LatestHeight() + +} + +func (s *E2ETestSuite) TearDownSuite() { + s.T().Log("tearing down collection e2e test suite") + s.network.Cleanup() +} + +func (s *E2ETestSuite) createContract(creator sdk.AccAddress) string { + val := s.network.Validators[0] + args := append([]string{ + creator.String(), + }, s.commonArgs...) + + out, err := clitestutil.ExecTestCLICmd(val.ClientCtx, cli.NewTxCmdCreateContract(), args) + s.Require().NoError(err) + txResp := s.getTxResp(out, 0) + var event collection.EventCreatedContract + s.pickEvent(txResp.Events, &event, func(e proto.Message) { + event = *e.(*collection.EventCreatedContract) + }) + return event.ContractId +} + +func (s *E2ETestSuite) createNFTClass(contractID string, operator sdk.AccAddress) string { + val := s.network.Validators[0] + args := append([]string{ + contractID, + operator.String(), + }, s.commonArgs...) + + out, err := clitestutil.ExecTestCLICmd(val.ClientCtx, cli.NewTxCmdIssueNFT(), args) + s.Require().NoError(err) + txResp := s.getTxResp(out, 0) + var event collection.EventCreatedNFTClass + s.pickEvent(txResp.Events, &event, func(e proto.Message) { + event = *e.(*collection.EventCreatedNFTClass) + }) + return event.TokenType +} + +func (s *E2ETestSuite) mintNFT(contractID string, operator, to sdk.AccAddress, classID string) string { + val := s.network.Validators[0] + args := append([]string{ + contractID, + operator.String(), + to.String(), + classID, + fmt.Sprintf("--%s=%s", cli.FlagName, "arctic fox"), + }, s.commonArgs...) + + out, err := clitestutil.ExecTestCLICmd(val.ClientCtx, cli.NewTxCmdMintNFT(), args) + s.Require().NoError(err) + txResp := s.getTxResp(out, 0) + var event collection.EventMintedNFT + s.pickEvent(txResp.Events, &event, func(e proto.Message) { + event = *e.(*collection.EventMintedNFT) + }) + + s.Require().Equal(1, len(event.Tokens)) + return event.Tokens[0].TokenId +} + +func (s *E2ETestSuite) grant(contractID string, granter, grantee sdk.AccAddress, permission collection.Permission) { + val := s.network.Validators[0] + args := append([]string{ + contractID, + granter.String(), + grantee.String(), + collection.LegacyPermission(permission).String(), + }, s.commonArgs...) + + out, err := clitestutil.ExecTestCLICmd(val.ClientCtx, cli.NewTxCmdGrantPermission(), args) + s.Require().NoError(err) + _ = s.getTxResp(out, 0) +} + +func (s *E2ETestSuite) authorizeOperator(contractID string, holder, operator sdk.AccAddress) { + val := s.network.Validators[0] + args := append([]string{ + contractID, + holder.String(), + operator.String(), + }, s.commonArgs...) + + out, err := clitestutil.ExecTestCLICmd(val.ClientCtx, cli.NewTxCmdAuthorizeOperator(), args) + s.Require().NoError(err) + _ = s.getTxResp(out, 0) +} + +func (s *E2ETestSuite) pickEvent(events []abci.Event, event proto.Message, fn func(event proto.Message)) { + for _, e := range events { + if e.Type == proto.MessageName(event) { + msg, err := sdk.ParseTypedEvent(e) + s.Require().NoError(err) + + fn(msg) + return + } + } + + s.Require().Failf("event not found", "%s", events) +} + +// creates an account and send some coins to it for the future transactions. +func (s *E2ETestSuite) createAccount(uid string) sdk.AccAddress { + val := s.network.Validators[0] + keyInfo, _, err := val.ClientCtx.Keyring.NewMnemonic(uid, keyring.English, sdk.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1) + s.Require().NoError(err) + addr, err := keyInfo.GetAddress() + s.Require().NoError(err) + + out, err := clitestutil.MsgSendExec(val.ClientCtx, val.Address, addr, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, cmath.NewInt(1000000))), address.NewBech32Codec("link"), s.commonArgs...) + s.Require().NoError(err) + s.getTxResp(out, 0) + return addr +} + +func (s *E2ETestSuite) getTxResp(out testutil.BufferWriter, expectedCode uint32) sdk.TxResponse { + var res sdk.TxResponse + s.Require().NoError(s.network.Validators[0].ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &res), out.String()) + s.Require().NoError(s.network.WaitForNextBlock()) + txResp, err := clitestutil.GetTxResponse(s.network, s.network.Validators[0].ClientCtx, res.TxHash) + s.Require().NoError(err) + s.Require().EqualValues(expectedCode, txResp.Code, txResp.String()) + return txResp +} diff --git a/tests/e2e/collection/tx.go b/tests/e2e/collection/tx.go new file mode 100644 index 0000000000..18ec172999 --- /dev/null +++ b/tests/e2e/collection/tx.go @@ -0,0 +1,728 @@ +package collection + +import ( + "fmt" + + clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" + + "github.com/Finschia/finschia-sdk/x/collection" + "github.com/Finschia/finschia-sdk/x/collection/client/cli" +) + +func (s *E2ETestSuite) TestNewTxCmdSendNFT() { + val := s.network.Validators[0] + tokenID := s.tokenIDs[s.stranger.String()] + + testCases := map[string]struct { + args []string + valid bool + }{ + "valid transaction": { + []string{ + s.contractID, + s.stranger.String(), + s.customer.String(), + tokenID, + }, + true, + }, + "extra args": { + []string{ + s.contractID, + s.stranger.String(), + s.customer.String(), + tokenID, + "extra", + }, + false, + }, + "not enough args": { + []string{ + s.contractID, + s.stranger.String(), + s.customer.String(), + }, + false, + }, + "invalid contract id": { + []string{ + "", + s.stranger.String(), + s.customer.String(), + tokenID, + }, + false, + }, + } + + for name, tc := range testCases { + tc := tc + + s.Run(name, func() { + cmd := cli.NewTxCmdSendNFT() + out, err := clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, append(tc.args, s.commonArgs...)) + if !tc.valid { + s.Require().Error(err) + return + } + s.Require().NoError(err) + s.getTxResp(out, 0) + }) + } +} + +func (s *E2ETestSuite) TestNewTxCmdOperatorSendNFT() { + val := s.network.Validators[0] + tokenID := s.tokenIDs[s.customer.String()] + + testCases := map[string]struct { + args []string + valid bool + success bool + }{ + "valid transaction": { + []string{ + s.contractID, + s.operator.String(), + s.customer.String(), + s.vendor.String(), + tokenID, + }, + true, + true, + }, + "extra args": { + []string{ + s.contractID, + s.operator.String(), + s.customer.String(), + s.vendor.String(), + tokenID, + "extra", + }, + false, + false, + }, + "not enough args": { + []string{ + s.contractID, + s.operator.String(), + s.customer.String(), + s.vendor.String(), + }, + false, + false, + }, + "invalid contract id": { + []string{ + "", + s.operator.String(), + s.customer.String(), + s.vendor.String(), + tokenID, + }, + false, + false, + }, + "invalid operator": { + []string{ + s.contractID, + s.stranger.String(), + s.customer.String(), + s.vendor.String(), + tokenID, + }, + true, + false, + }, + } + + for name, tc := range testCases { + tc := tc + + s.Run(name, func() { + cmd := cli.NewTxCmdOperatorSendNFT() + out, err := clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, append(tc.args, s.commonArgs...)) + if !tc.valid { + s.Require().Error(err) + return + } + s.Require().NoError(err) + if tc.success { + s.getTxResp(out, 0) + } else { + s.getTxResp(out, 29) + } + }) + } +} + +func (s *E2ETestSuite) TestNewTxCmdCreateContract() { + val := s.network.Validators[0] + + testCases := map[string]struct { + args []string + valid bool + }{ + "valid transaction": { + []string{ + s.vendor.String(), + }, + true, + }, + "extra args": { + []string{ + s.vendor.String(), + "extra", + }, + false, + }, + "not enough args": { + []string{}, + false, + }, + "invalid creator": { + []string{ + "", + }, + false, + }, + } + + for name, tc := range testCases { + tc := tc + + s.Run(name, func() { + cmd := cli.NewTxCmdCreateContract() + out, err := clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, append(tc.args, s.commonArgs...)) + if !tc.valid { + s.Require().Error(err) + return + } + s.Require().NoError(err) + s.getTxResp(out, 0) + }) + } +} + +func (s *E2ETestSuite) TestNewTxCmdIssueNFT() { + val := s.network.Validators[0] + + testCases := map[string]struct { + args []string + valid bool + }{ + "valid transaction": { + []string{ + s.contractID, + s.operator.String(), + }, + true, + }, + "extra args": { + []string{ + s.contractID, + s.operator.String(), + "extra", + }, + false, + }, + "not enough args": { + []string{ + s.contractID, + }, + false, + }, + "invalid contract id": { + []string{ + "", + s.operator.String(), + }, + false, + }, + } + + for name, tc := range testCases { + tc := tc + + s.Run(name, func() { + cmd := cli.NewTxCmdIssueNFT() + out, err := clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, append(tc.args, s.commonArgs...)) + if !tc.valid { + s.Require().Error(err) + return + } + s.Require().NoError(err) + s.getTxResp(out, 0) + }) + } +} + +func (s *E2ETestSuite) TestNewTxCmdMintNFT() { + val := s.network.Validators[0] + + testCases := map[string]struct { + args []string + valid bool + }{ + "valid transaction": { + []string{ + s.contractID, + s.operator.String(), + s.customer.String(), + s.nftClassID, + fmt.Sprintf("--%s=%s", cli.FlagName, "arctic fox"), + }, + true, + }, + "extra args": { + []string{ + s.contractID, + s.operator.String(), + s.customer.String(), + s.nftClassID, + "extra", + }, + false, + }, + "not enough args": { + []string{ + s.contractID, + s.operator.String(), + s.customer.String(), + }, + false, + }, + "invalid contract id": { + []string{ + "", + s.operator.String(), + s.customer.String(), + s.nftClassID, + }, + false, + }, + } + + for name, tc := range testCases { + tc := tc + + s.Run(name, func() { + cmd := cli.NewTxCmdMintNFT() + out, err := clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, append(tc.args, s.commonArgs...)) + if !tc.valid { + s.Require().Error(err) + return + } + s.Require().NoError(err) + s.getTxResp(out, 0) + }) + } +} + +func (s *E2ETestSuite) TestNewTxCmdBurnNFT() { + val := s.network.Validators[0] + tokenID := s.tokenIDs[s.operator.String()] + + testCases := map[string]struct { + args []string + valid bool + }{ + "valid transaction": { + []string{ + s.contractID, + s.operator.String(), + tokenID, + }, + true, + }, + "extra args": { + []string{ + s.contractID, + s.operator.String(), + tokenID, + "extra", + }, + false, + }, + "not enough args": { + []string{ + s.contractID, + s.operator.String(), + }, + false, + }, + "invalid contract id": { + []string{ + "", + s.operator.String(), + tokenID, + }, + false, + }, + } + + for name, tc := range testCases { + tc := tc + + s.Run(name, func() { + cmd := cli.NewTxCmdBurnNFT() + out, err := clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, append(tc.args, s.commonArgs...)) + if !tc.valid { + s.Require().Error(err) + return + } + s.Require().NoError(err) + s.getTxResp(out, 0) + }) + } +} + +func (s *E2ETestSuite) TestNewTxCmdOperatorOperatorBurnNFT() { + val := s.network.Validators[0] + tokenID := s.tokenIDs[s.vendor.String()] + + testCases := map[string]struct { + args []string + valid bool + success bool + }{ + "valid transaction": { + []string{ + s.contractID, + s.operator.String(), + s.vendor.String(), + tokenID, + }, + true, + true, + }, + "extra args": { + []string{ + s.contractID, + s.operator.String(), + s.vendor.String(), + tokenID, + "extra", + }, + false, + false, + }, + "not enough args": { + []string{ + s.contractID, + s.operator.String(), + s.vendor.String(), + }, + false, + false, + }, + "invalid contract id": { + []string{ + "", + s.operator.String(), + s.vendor.String(), + tokenID, + }, + false, + false, + }, + "invalid operator": { + []string{ + s.contractID, + s.stranger.String(), + s.vendor.String(), + tokenID, + }, + true, + false, + }, + } + + for name, tc := range testCases { + tc := tc + + s.Run(name, func() { + cmd := cli.NewTxCmdOperatorBurnNFT() + out, err := clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, append(tc.args, s.commonArgs...)) + if !tc.valid { + s.Require().Error(err) + return + } + s.Require().NoError(err) + if tc.success { + s.getTxResp(out, 0) + } else { + s.getTxResp(out, 29) + } + }) + } +} + +func (s *E2ETestSuite) TestNewTxCmdModify() { + val := s.network.Validators[0] + + testCases := map[string]struct { + args []string + valid bool + }{ + "valid transaction": { + []string{ + s.contractID, + s.operator.String(), + s.nftClassID, + "", + collection.AttributeKeyName.String(), + "tibetian fox", + }, + true, + }, + "extra args": { + []string{ + s.contractID, + s.operator.String(), + s.nftClassID, + "", + collection.AttributeKeyName.String(), + "tibetian fox", + "extra", + }, + false, + }, + "not enough args": { + []string{ + s.contractID, + s.operator.String(), + s.nftClassID, + "", + collection.AttributeKeyName.String(), + }, + false, + }, + "invalid contract id": { + []string{ + "", + s.operator.String(), + s.nftClassID, + "", + collection.AttributeKeyName.String(), + "tibetian fox", + }, + false, + }, + } + + for name, tc := range testCases { + tc := tc + + s.Run(name, func() { + cmd := cli.NewTxCmdModify() + out, err := clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, append(tc.args, s.commonArgs...)) + if !tc.valid { + s.Require().Error(err) + return + } + s.Require().NoError(err) + s.getTxResp(out, 0) + }) + } +} + +func (s *E2ETestSuite) TestNewTxCmdGrantPermission() { + val := s.network.Validators[0] + + testCases := map[string]struct { + args []string + valid bool + }{ + "valid transaction": { + []string{ + s.contractID, + s.operator.String(), + s.customer.String(), + collection.LegacyPermissionMint.String(), + }, + true, + }, + "extra args": { + []string{ + s.contractID, + s.operator.String(), + s.customer.String(), + collection.LegacyPermissionMint.String(), + "extra", + }, + false, + }, + "not enough args": { + []string{ + s.contractID, + s.operator.String(), + s.customer.String(), + }, + false, + }, + } + + for name, tc := range testCases { + tc := tc + + s.Run(name, func() { + cmd := cli.NewTxCmdGrantPermission() + out, err := clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, append(tc.args, s.commonArgs...)) + if !tc.valid { + s.Require().Error(err) + return + } + s.Require().NoError(err) + s.getTxResp(out, 0) + }) + } +} + +func (s *E2ETestSuite) TestNewTxCmdRevokePermission() { + val := s.network.Validators[0] + + testCases := map[string]struct { + args []string + valid bool + }{ + "valid transaction": { + []string{ + s.contractID, + s.vendor.String(), + collection.LegacyPermissionModify.String(), + }, + true, + }, + "extra args": { + []string{ + s.contractID, + s.vendor.String(), + collection.LegacyPermissionModify.String(), + "extra", + }, + false, + }, + "not enough args": { + []string{ + s.contractID, + s.vendor.String(), + }, + false, + }, + } + + for name, tc := range testCases { + tc := tc + + s.Run(name, func() { + cmd := cli.NewTxCmdRevokePermission() + out, err := clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, append(tc.args, s.commonArgs...)) + if !tc.valid { + s.Require().Error(err) + return + } + s.Require().NoError(err) + s.getTxResp(out, 0) + }) + } +} + +func (s *E2ETestSuite) TestNewTxCmdAuthorizeOperator() { + val := s.network.Validators[0] + + testCases := map[string]struct { + args []string + valid bool + }{ + "valid transaction": { + []string{ + s.contractID, + s.vendor.String(), + s.customer.String(), + }, + true, + }, + "extra args": { + []string{ + s.contractID, + s.vendor.String(), + s.customer.String(), + "extra", + }, + false, + }, + "not enough args": { + []string{ + s.contractID, + s.vendor.String(), + }, + false, + }, + } + + for name, tc := range testCases { + tc := tc + + s.Run(name, func() { + cmd := cli.NewTxCmdAuthorizeOperator() + out, err := clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, append(tc.args, s.commonArgs...)) + if !tc.valid { + s.Require().Error(err) + return + } + s.Require().NoError(err) + s.getTxResp(out, 0) + }) + } +} + +func (s *E2ETestSuite) TestNewTxCmdRevokeOperator() { + val := s.network.Validators[0] + + testCases := map[string]struct { + args []string + valid bool + }{ + "valid transaction": { + []string{ + s.contractID, + s.operator.String(), + s.vendor.String(), + }, + true, + }, + "extra args": { + []string{ + s.contractID, + s.operator.String(), + s.vendor.String(), + "extra", + }, + false, + }, + "not enough args": { + []string{ + s.contractID, + s.operator.String(), + }, + false, + }, + } + + for name, tc := range testCases { + tc := tc + + s.Run(name, func() { + cmd := cli.NewTxCmdRevokeOperator() + out, err := clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, append(tc.args, s.commonArgs...)) + if !tc.valid { + s.Require().Error(err) + return + } + s.Require().NoError(err) + s.getTxResp(out, 0) + }) + } +} diff --git a/x/collection/errors.go b/x/collection/errors.go index e1700fccb5..c699d4b8fd 100644 --- a/x/collection/errors.go +++ b/x/collection/errors.go @@ -10,8 +10,6 @@ var ( ErrTokenNotExist = errorsmod.Register(collectionCodespace, 2, "token symbol, token-id does not exist") ErrInvalidTokenName = errorsmod.Register(collectionCodespace, 4, "token name should not be empty") ErrInvalidTokenID = errorsmod.Register(collectionCodespace, 5, "invalid token id") - ErrInvalidTokenDecimals = errorsmod.Register(collectionCodespace, 6, "token decimals should be within the range in 0 ~ 18") - ErrInvalidAmount = errorsmod.Register(collectionCodespace, 8, "invalid token amount") ErrInvalidBaseImgURILength = errorsmod.Register(collectionCodespace, 9, "invalid base_img_uri length") ErrInvalidNameLength = errorsmod.Register(collectionCodespace, 10, "invalid name length") ErrInvalidTokenType = errorsmod.Register(collectionCodespace, 11, "invalid token type pattern found") @@ -44,7 +42,9 @@ var ( // Deprecated: do not use from v0.50.x var ( ErrTokenNotMintable = errorsmod.Register(collectionCodespace, 3, "token symbol, token-id is not mintable") + ErrInvalidTokenDecimals = errorsmod.Register(collectionCodespace, 6, "token decimals should be within the range in 0 ~ 18") ErrInvalidIssueFT = errorsmod.Register(collectionCodespace, 7, "Issuing token with amount[1], decimals[0], mintable[false] prohibited. Issue nft token instead.") + ErrInvalidAmount = errorsmod.Register(collectionCodespace, 8, "invalid token amount") ErrCollectionExist = errorsmod.Register(collectionCodespace, 13, "collection already exists") ErrTokenTypeExist = errorsmod.Register(collectionCodespace, 15, "token type for contract_id, token-type already exists") ErrTokenTypeFull = errorsmod.Register(collectionCodespace, 17, "all token type for contract_id are occupied")