From 23907644593fda06c0b2791ac30b27999b391584 Mon Sep 17 00:00:00 2001 From: celbalrai <80897309+celbalrai@users.noreply.github.com> Date: Wed, 16 Jun 2021 15:01:30 +0200 Subject: [PATCH] Update NFT system - Only 1 mint output - Mint output must match the mint_amount specified with configurenft - Renamed token document functions to: - encodetokenmetadata - decodetokenmetadata - signtokenmetadata - validatetokenmetadata - Switched from camelCase to snake_case - Updated token help texts - Allow dash inside token names (not at start or end) - Add binary blob to NFT - Simplify token metadata parsing and validating --- src/consensus/tokengroups.cpp | 14 +- src/rpc/client.cpp | 5 +- src/tokens/rpctokens.cpp | 346 +++++++++++-------------- src/tokens/rpctokenwallet.cpp | 202 ++++----------- src/tokens/tokengroupconfiguration.cpp | 2 +- src/tokens/tokengroupdescription.cpp | 59 ++++- src/tokens/tokengroupdescription.h | 18 +- src/tokens/tokengroupdocument.cpp | 205 +++++++++++++-- src/tokens/tokengroupdocument.h | 121 +++++---- src/validation.cpp | 14 + 10 files changed, 537 insertions(+), 449 deletions(-) diff --git a/src/consensus/tokengroups.cpp b/src/consensus/tokengroups.cpp index 974647b694cd76..ca3ced4b46c6d1 100644 --- a/src/consensus/tokengroups.cpp +++ b/src/consensus/tokengroups.cpp @@ -283,10 +283,18 @@ bool CheckTokenGroups(const CTransaction &tx, CValidationState &state, const CCo return state.Invalid(false, REJECT_GROUP_IMBALANCE, "grp-invalid-melt", "Group input exceeds output, but no melt permission"); } - if ((bal.input < bal.output) && !hasCapability(bal.ctrlPerms, GroupAuthorityFlags::MINT)) + if (bal.input < bal.output) { - return state.Invalid(false, REJECT_GROUP_IMBALANCE, "grp-invalid-mint", - "Group output exceeds input, but no mint permission"); + if (!hasCapability(bal.ctrlPerms, GroupAuthorityFlags::MINT)) + { + return state.Invalid(false, REJECT_GROUP_IMBALANCE, "grp-invalid-mint", + "Group output exceeds input, but no mint permission"); + } + if (txo.first.hasFlag(TokenGroupIdFlags::NFT_TOKEN) && hasCapability(bal.allowedCtrlOutputPerms, GroupAuthorityFlags::MINT)) { + // Redundant + return state.Invalid(false, REJECT_GROUP_IMBALANCE, "grp-invalid-mint", + "NFT mint cannot have mint authority output"); + } } // Some output permissions are set that are not in the inputs if (((uint64_t)(bal.ctrlOutputPerms & GroupAuthorityFlags::ALL)) & diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index eb31c7ed82e8dc..11afda466c2ced 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -189,9 +189,8 @@ static const CRPCConvertParam vRPCConvertParams[] = { "createrawtokentransaction", 1, "outputs" }, { "createrawtokentransaction", 2, "token_outputs" }, { "createrawtokentransaction", 3, "locktime" }, - { "createrawtokendocument", 0, "spec" }, - { "createrawtokendocument", 2, "verbose" }, - { "signrawtokendocument", 2, "verbose" }, + { "encodetokenmetadata", 0, "spec" }, + { "signtokenmetadata", 2, "verbose" }, { "listunspenttokens", 0, "groupid" }, { "listunspenttokens", 1, "minconf" }, { "listunspenttokens", 2, "maxconf" }, diff --git a/src/tokens/rpctokens.cpp b/src/tokens/rpctokens.cpp index 1352f3678b1eef..2a265aeb3a07a4 100644 --- a/src/tokens/rpctokens.cpp +++ b/src/tokens/rpctokens.cpp @@ -33,21 +33,21 @@ void TokenGroupCreationToJSON(const CTokenGroupID &tgID, const CTokenGroupCreati if (tgID.isSubgroup()) { CTokenGroupID parentgrp = tgID.parentGroup(); const std::vector subgroupData = tgID.GetSubGroupData(); - entry.push_back(Pair("parentGroupID", EncodeTokenGroup(tgCreation.tokenGroupInfo.associatedGroup))); - entry.push_back(Pair("subgroupData", std::string(subgroupData.begin(), subgroupData.end()))); + entry.push_back(Pair("parent_groupID", EncodeTokenGroup(tgCreation.tokenGroupInfo.associatedGroup))); + entry.push_back(Pair("subgroup_data", std::string(subgroupData.begin(), subgroupData.end()))); } entry.push_back(Pair("ticker", tgDescGetTicker(*tgCreation.pTokenGroupDescription))); entry.push_back(Pair("name", tgDescGetName(*tgCreation.pTokenGroupDescription))); - entry.push_back(Pair("decimalPos", tgDescGetDecimalPos(*tgCreation.pTokenGroupDescription))); - entry.push_back(Pair("URL", tgDescGetDocumentURL(*tgCreation.pTokenGroupDescription))); - entry.push_back(Pair("documentHash", tgDescGetDocumentHash(*tgCreation.pTokenGroupDescription).ToString())); + entry.push_back(Pair("decimal_pos", tgDescGetDecimalPos(*tgCreation.pTokenGroupDescription))); + entry.push_back(Pair("metadata_url", tgDescGetDocumentURL(*tgCreation.pTokenGroupDescription))); + entry.push_back(Pair("metadata_hash", tgDescGetDocumentHash(*tgCreation.pTokenGroupDescription).ToString())); std::string flags = tgID.encodeFlags(); if (flags != "none") entry.push_back(Pair("flags", flags)); if (extended) { UniValue extendedEntry(UniValue::VOBJ); extendedEntry.push_back(Pair("txid", tgCreation.creationTransaction->GetHash().GetHex())); - extendedEntry.push_back(Pair("blockHash", tgCreation.creationBlockHash.GetHex())); + extendedEntry.push_back(Pair("blockhash", tgCreation.creationBlockHash.GetHex())); extendedEntry.push_back(Pair("address", EncodeDestination(creationDestination))); entry.push_back(Pair("creation", extendedEntry)); } @@ -67,13 +67,16 @@ extern UniValue tokeninfo(const JSONRPCRequest& request) "tokeninfo [list, all, stats, groupid, ticker, name] ( \"specifier \" ) ( \"extended_info\" ) \n" "\nReturns information on all tokens configured on the blockchain.\n" "\nArguments:\n" - "'list' lists all token groupID's and corresponding token tickers\n" - "'all' shows extended information on all tokens\n" - "'stats' shows statistical information on the management tokens in a specific block. Args: block height (optional)\n" - "'groupid' shows information on the token configuration with the specified grouID\n" - "'ticker' shows information on the token configuration with the specified ticker\n" - "'name' shows information on the token configuration with the specified name'\n" - "'extended_info' (optional) show extended information'\n" + "'list' lists all token groupID's and corresponding token tickers\n" + "'all' shows extended information on all tokens\n" + "'stats' shows statistical information on the management tokens in a specific block.\n" + " Args: block hash (optional)\n" + "'groupid' shows information on the token configuration with the specified grouID\n" + "'ticker' shows information on the token configuration with the specified ticker\n" + "'name' shows information on the token configuration with the specified name'\n" + "\n" + "'specifier' (string, optional) parameter to couple with the main action'\n" + "'extended_info' (bool, optional) show extended information'\n" "\n" + HelpExampleCli("tokeninfo", "ticker \"WAGERR\"") + "\n" @@ -241,8 +244,8 @@ void RpcTokenTxnoutToUniv(const CTxOut& txout, CTokenGroupID parentgrp = tokenGroupInfo.associatedGroup.parentGroup(); std::vector subgroupData = tokenGroupInfo.associatedGroup.GetSubGroupData(); tgTicker = tokenGroupManager.get()->GetTokenGroupTickerByID(parentgrp); - out.pushKV("parentGroupID", EncodeTokenGroup(parentgrp)); - out.pushKV("subgroupData", std::string(subgroupData.begin(), subgroupData.end())); + out.pushKV("parent_groupID", EncodeTokenGroup(parentgrp)); + out.pushKV("subgroup_data", std::string(subgroupData.begin(), subgroupData.end())); } else { tgTicker = tokenGroupManager.get()->GetTokenGroupTickerByID(tokenGroupInfo.associatedGroup); } @@ -658,245 +661,186 @@ UniValue createrawtokentransaction(const JSONRPCRequest& request) return EncodeHexTx(rawTx); } -UniValue createrawtokendocument(const JSONRPCRequest& request) +UniValue encodetokenmetadata(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) { + if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( - "createrawtokendocument {\"ticker\":\"ticker\",\"name\":\"token name\",...} ( verbose )\n" - "\nCreate a token document that is to be signed and published online.\n" + "encodetokenmetadata {\"ticker\":\"ticker\",\"name\":\"token name\",...} \n" + "\nCreate the hash and hexadecimal representation of a token metadata document.\n" "\nThe document's hash is included when configuring a new token.\n" - "Returns either a hex-encoded representation or a json representation.\n" - "\n" - "Note that the created document is not signed, and that it is not stored in the wallet or transmitted to the network. .\n" + "\nThe document's hexadecimal representation is used to sign the metadata document.\n" + "\nNote that the attribute format is a suggestion, not a requirement.\n" "\nArguments:\n" - "1. \"specification\" (json, required) The document specification.\n" + "1. \"specification\": (json, required) The document specification.\n" " {\n" - " \"ticker\":\"ticker\", (string, optional) The ticker\n" - " \"name\":\"name\", (string, optional) The token name\n" - " \"chain\":\"chain\", (string, optional) Chain identifier, e.g. \"WAGERR\" (for mainnet) or \"WAGERR.testnet\" or \"WAGERR.regtest\"\n" - " \"summary\":\"summary\", (string, optional) Short introduction to the token\n" + " \"atp\": { (object, required) ATP specification data\n" + " \"version\": \"1\", (string, required) ATP version number. Current version: 1\n" + " \"type\": \"type\" (string, required) 'regular|mgt|nft' for either regular\n" + " tokens, management tokens or non-fungible tokens\n" + " }\n" + " \"ticker\":\"ticker\", (string) The ticker. Required for regular tokens and management tokens\n" + " \"name\":\"name\", (string, required) The token name\n" + " \"chain\":\"chain\", (string, required) Chain identifier, e.g. \"WAGERR\" (for mainnet) or \"WAGERR.testnet\" or \"WAGERR.regtest\"\n" + " \"summary\":\"summary\", (string, optional) Short introduction to the token.\n" " \"description\":\"description\", (string, optional) Description of the token\n" " \"creator\":\"creator\", (string, optional) Token creator\n" - " \"contact\": { (object, optional) Contact information\n" - " \"url\": \"id\", (string, optional) URL that points to token contact information\n" - " \"email\": \"email\" (string, optional) Mail address\n" + " \"attributes\": [ (array, required for NFT) URL that points to a json file that holds an array with attributes\n" + " { (object, optional) object with attribute data\n" + " \"trait_type\": \"type\", (string, required) Trait type; e.g., 'Seat number', 'Shape', 'Power'\n" + " \"display_type\": \"type\", (string, optional) Display type; e.g., 'Number', 'Percentage', 'Currency'\n" + " \"value\": \"value\" (any, required) Attribute value\n" + " }\n" + " ]\n" + " \"attributes_url\": \"id\", (string, required for NFT) URL that points to a dynamic json file that holds an array with attributes\n" " }\n" " }\n" - "2. \"signature\" (string, optional, default="") Fill out the signature field with a given signature string\n" - "3. \"verbose\" (bool, optional, default=false) Output the json encoded specification instead of the hex-encoded serialized data\n" "\nResult:\n" - "\"hex\" : \"value\", (string) The hex-encoded raw token document\n" + "\"hash\" : \"value\", (string) The hash of the token metadata document\n" + "\"hex_data\" : \"value\", (string) The hex-encoded token metadata document\n" "\nExamples:\n" "\nCreate the MGT testnet document\n" - + HelpExampleCli("createrawtokendocument", - "\"{\\\"ticker\\\": \\\"MGT\\\", \\\"name\\\": \\\"Management Token\\\", \\\"chain\\\": \\\"WAGERR.testnet\\\", " - "\\\"summary\\\": \\\"The MGT token is a tokenized management key on the WAGERR blockchain with special authorities " - "necessary for: (1) the construction of a token system with coherent economic incentives; (2) the inception of " - "Nucleus Tokens (special tokens that have interrelated monetary policies); and (3) the distribution of rewards that " - "sustain this system of cryptographic tokens on the blockchain.\\\", \\\"description\\\": \\\"The Atomic Token " - "Protocol (ATP) introduces cross-coin and cross-token policy. WAGERR utilizes ATP for its reward system and rights " - "structure. Management Token (MGT), Guardian Validator Token (GVT), and Guardian Validators all participate in an " - "interconnected managent system, and are considered the Nucleus Tokens. The MGT token itself is a tokenized " - "management key with special authorities needed for token inception on the blockchain. The MGT token continues " - "to play a role in the management of and access to special features.\\\", \\\"creator\\\": \\\"The WAGERR Core " - "Developers\\\", \\\"contact\\\":{\\\"url\\\":\\\"https://github.com/wagerr/wagerr\\\"}}\"") + - "\nCreate a partial document, add a signature, output the json specification\n" - + HelpExampleCli("createrawtokendocument", - "\"{\\\"ticker\\\": \\\"MGT\\\", \\\"name\\\": \\\"Management Token\\\", \\\"chain\\\": \\\"WAGERR.testnet\\\"}\" " - "20fa4cc8f93c6d52ce6690b6997b7ae3c785fe291c5c6e44370ef1557f61aeb1242fddd9aa13941e4b5be53d07998ebb201ce2cfa96c832d5fee743c5600c7277b true") + - "\nCreate a partial document as a json rpc call\n" - + HelpExampleRpc("createrawtokendocument", - "\"{\\\"ticker\\\": \\\"MGT\\\", \\\"name\\\": \\\"Management Token\\\", \\\"chain\\\": \\\"WAGERR.testnet\\\"}\"") + + HelpExampleCli("encodetokenmetadata", + "\"{\\\"atp\\\":{\\\"version\\\":1,\\\"type\\\":\\\"nft\\\"},\\\"name\\\":\\\"John Doe concert tickets - Garden of Eden" + " tour\\\",\\\"chain\\\":\\\"WAGERR.testnet\\\",\\\"creator\\\":\\\"DoeTours Ltd.\\\",\\\"description\\\":\\\"From April " + "1st through April 9th, John Doe will visit Eden, NC. This booking grants you access.\\\",\\\"external_url\\\":\\\"http" + "s://yourtickettomusic.com/nft/{id}/\\\",\\\"image\\\":\\\"https://www.stockvault.net/data/2018/10/09/255077/preview16." + "jpg\\\",\\\"attributes\\\":[{\\\"trait_type\\\":\\\"Ticket class\\\",\\\"value\\\":\\\"Gold\\\"},{\\\"display_type\\\"" + ":\\\"currency_dollar\\\",\\\"trait_type\\\":\\\"Base price\\\",\\\"value\\\":\\\"50.0\\\"},{\\\"trait_type\\\":\\\"All" + "ow resale\\\",\\\"value\\\":\\\"Yes\\\"}],\\\"attributes_url\\\":\\\"https://yourtickettomusic.com/nft/{id}_attributes" + ".json\\\"}\"") ); } - RPCTypeCheck(request.params, {UniValue::VOBJ, UniValue::VSTR, UniValue::VBOOL}); - - std::string strTicker; - std::string strName; - std::string strChain; - std::string strSummary; - std::string strDescription; - std::string strCreator; - std::string strContactURL; - std::string strContactEmail; - - UniValue spec = request.params[0]; - RPCTypeCheckObj(spec, - { - {"ticker", UniValueType(UniValue::VSTR)}, - {"name", UniValueType(UniValue::VSTR)}, - {"chain", UniValueType(UniValue::VSTR)}, - {"summary", UniValueType(UniValue::VSTR)}, - {"description", UniValueType(UniValue::VSTR)}, - {"creator", UniValueType(UniValue::VSTR)}, - {"contact", UniValueType()}, // will be checked below - }, - true, true); - - if (spec.exists("ticker")) { - strTicker = spec["ticker"].get_str(); - } - if (spec.exists("name")) { - strName = spec["name"].get_str(); - } - if (spec.exists("chain")) { - strChain = spec["chain"].get_str(); - } - if (spec.exists("summary")) { - strSummary = spec["summary"].get_str(); - } - if (spec.exists("description")) { - strDescription = spec["description"].get_str(); - } - if (spec.exists("creator")) { - strCreator = spec["creator"].get_str(); - } - if (spec.exists("contact")) { - UniValue contact = spec["contact"]; - RPCTypeCheckObj(contact, - { - {"url", UniValueType(UniValue::VSTR)}, - {"email", UniValueType(UniValue::VSTR)}, - }, - true, true); - if (contact.exists("url")) { - strContactURL = contact["url"].get_str(); - } - if (contact.exists("email")) { - strContactEmail = contact["email"].get_str(); - } - } - CTokenGroupDocument tgDocument = CTokenGroupDocument(strTicker, strName, strChain, strSummary, strDescription, strCreator, strContactURL, strContactEmail); + RPCTypeCheck(request.params, {UniValue::VOBJ, UniValue::VBOOL}); - if (request.params.size() > 1) { - std::string strSignature = request.params[1].get_str(); - if (!IsHex(strSignature)) - throw std::runtime_error("invalid signature data"); - tgDocument.SetSignature(strSignature); - } + UniValue data = request.params[0].get_obj(); - bool fVerbose = false; - if (request.params.size() > 2) { - fVerbose = request.params[2].get_bool(); - } + CTokenGroupDocument tgDocument = CTokenGroupDocument(data); - if (fVerbose) { - UniValue ret(UniValue::VOBJ); - tgDocument.ToJson(ret); - return ret; - } - CDataStream ssTGDocumentOut(SER_NETWORK, PROTOCOL_VERSION); - ssTGDocumentOut << tgDocument; - std::string strData = HexStr(ssTGDocumentOut.begin(), ssTGDocumentOut.end()); - - return strData; + UniValue ret(UniValue::VOBJ); + ret.pushKV("hash", tgDocument.GetSignatureHash().GetHex()); + ret.pushKV("hex_data", tgDocument.GetDataAsHexString()); + return ret; } -UniValue decoderawtokendocument(const JSONRPCRequest& request) +UniValue decodetokenmetadata(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( - "decoderawtokendocument \"data\"\n" + "decodetokenmetadata \"data\"\n" "\nDecode a hex-encoded token document to json\n" "\nArguments:\n" - "1. \"data\" (hex, required) The serialized token document\n" + "1. \"hex_data\" (hex, required) The hex-encoded token metadata document\n" "\nResult:\n" - "{\n" - " \"atp\":\"atp\", (string) Atomic Token Protocol version number\n" - " \"data\": { (object) Data object\n" - " \"ticker\":\"ticker\", (string) The ticker\n" - " \"name\":\"name\", (string) The token name\n" - " \"chain\":\"chain\", (string) Chain identifier, e.g. \"WAGERR\" or \"WAGERR.testnet\"\n" - " \"summary\":\"summary\", (string) Short introduction to the token\n" - " \"description\":\"description\", (string) Description of the token\n" - " \"creator\":\"creator\", (string) Token creator\n" - " \"contact\": { (object) Contact information\n" - " \"url\": \"id\", (string) URL that points to token contact information\n" - " \"email\": \"email\" (string) Mail address\n" - " }\n" - " },\n" - " \"hash\":\"hash\", (string) Hash of the serialized document (excluding the signature)\n" - " \"signature\":\"signature\", (string) Signature of the serialized document\n" + " {\n" + " \"atp\": { (object, required) ATP specification data\n" + " \"version\": \"1\", (string, required) ATP version number. Current version: 1\n" + " \"type\": \"type\" (string, required) 'regular|mgt|nft' for either regular\n" + " tokens, management tokens or non-fungible tokens\n" + " }\n" + " \"ticker\":\"ticker\", (string) The ticker. Required for regular tokens and management tokens\n" + " \"name\":\"name\", (string, required) The token name\n" + " \"chain\":\"chain\", (string, required) Chain identifier, e.g. \"WAGERR\" (for mainnet) or \"WAGERR.testnet\" or \"WAGERR.regtest\"\n" + " \"summary\":\"summary\", (string, optional) Short introduction to the token.\n" + " \"description\":\"description\", (string, optional) Description of the token\n" + " \"creator\":\"creator\", (string, optional) Token creator\n" + " \"attributes\": [ (array, required for NFT) URL that points to a json file that holds an array with attributes\n" + " { (object, optional) object with attribute data\n" + " \"trait_type\": \"type\", (string, required) Trait type; e.g., 'Seat number', 'Shape', 'Power'\n" + " \"display_type\": \"type\", (string, optional) Display type; e.g., 'Number', 'Percentage', 'Currency'\n" + " \"value\": \"value\" (any, required) Attribute value\n" + " }\n" + " ]\n" + " \"attributes_url\": \"id\", (string, required for NFT) URL that points to a dynamic json file that holds an array with attributes\n" + " }\n" + " }\n" "\nExamples:\n" "\nDecode the hex-encoded MGT testnet document\n" - + HelpExampleCli("decoderawtokendocument", - "0100034d4754104d616e6167656d656e7420546f6b656e0c4259545a2e746573746e6574fd7b01546865204d475420746f6b656e206973206120746f6b656e697a6564206d6" - "16e6167656d656e74206b6579206f6e20746865204259545a20626c6f636b636861696e2077697468207370656369616c20617574686f726974696573206e65636573736172" - "7920666f723a202831292074686520636f6e737472756374696f6e206f66206120746f6b656e2073797374656d207769746820636f686572656e742065636f6e6f6d6963206" - "96e63656e74697665733b202832292074686520696e63657074696f6e206f66204e75636c65757320546f6b656e7320287370656369616c20746f6b656e7320746861742068" - "61766520696e74657272656c61746564206d6f6e657461727920706f6c6963696573293b20616e64202833292074686520646973747269627574696f6e206f6620726577617" - "264732074686174207375737461696e20746869732073797374656d206f662063727970746f6772617068696320746f6b656e73206f6e2074686520626c6f636b636861696e" - "2efd0e025468652041746f6d696320546f6b656e2050726f746f636f6c20284154502920696e74726f64756365732063726f73732d636f696e20616e642063726f73732d746" - "f6b656e20706f6c6963792e204259545a207574696c697a65732041545020666f7220697473207265776172642073797374656d20616e642072696768747320737472756374" - "7572652e204d616e6167656d656e7420546f6b656e20284d4754292c20477561726469616e2056616c696461746f7220546f6b656e2028475654292c20616e6420477561726" - "469616e2056616c696461746f727320616c6c20706172746963697061746520696e20616e20696e746572636f6e6e6563746564206d616e6167656e742073797374656d2c20" - "616e642061726520636f6e7369646572656420746865204e75636c65757320546f6b656e732e20546865204d475420746f6b656e20697473656c66206973206120746f6b656" - "e697a6564206d616e6167656d656e74206b65792077697468207370656369616c20617574686f726974696573206e656564656420666f7220746f6b656e20696e6365707469" - "6f6e206f6e2074686520626c6f636b636861696e2e20546865204d475420746f6b656e20636f6e74696e75657320746f20706c6179206120726f6c6520696e20746865206d6" - "16e6167656d656e74206f6620616e642061636365737320746f207370656369616c2066656174757265732e18546865204259545a20436f726520446576656c6f7065727324" - "68747470733a2f2f6769746875622e636f6d2f6279747a63757272656e63792f6279747a004120fa4cc8f93c6d52ce6690b6997b7ae3c785fe291c5c6e44370ef1557f61aeb" - "1242fddd9aa13941e4b5be53d07998ebb201ce2cfa96c832d5fee743c5600c7277b") + + HelpExampleCli("decodetokenmetadata", + "7b0a202022617470223a207b0a2020202276657273696f6e223a20312c0a2020202274797065223a20226d616e6167656d656e74220a20207d2c0a2020227469636b6572223" + "a20224d4754222c0a2020226e616d65223a20224d616e6167656d656e7420546f6b656e222c0a202022636861696e223a20224259545a2e746573746e6574222c0a20202263" + "726561746f72223a2022546865204279747a20436f726520646576656c6f70657273222c0a20202273756d6d617279223a2022546865204d475420746f6b656e20697320612" + "0746f6b656e697a6564206d616e6167656d656e74206b6579206f6e20746865204259545a20626c6f636b636861696e2077697468207370656369616c20617574686f726974" + "696573206e656365737361727920666f723a202831292074686520636f6e737472756374696f6e206f66206120746f6b656e2073797374656d207769746820636f686572656" + "e742065636f6e6f6d696320696e63656e74697665733b202832292074686520696e63657074696f6e206f66204e75636c65757320546f6b656e7320287370656369616c2074" + "6f6b656e732074686174206861766520696e74657272656c61746564206d6f6e657461727920706f6c6963696573293b20616e6420283329207468652064697374726962757" + "4696f6e206f6620726577617264732074686174207375737461696e20746869732073797374656d206f662063727970746f6772617068696320746f6b656e73206f6e207468" + "6520626c6f636b636861696e2e222c0a2020226465736372697074696f6e223a20225468652041746f6d696320546f6b656e2050726f746f636f6c20284154502920696e747" + "26f64756365732063726f73732d636f696e20616e642063726f73732d746f6b656e20706f6c6963792e204259545a207574696c697a65732041545020666f72206974732072" + "65776172642073797374656d20616e6420726967687473207374727563747572652e204d616e6167656d656e7420546f6b656e20284d4754292c20477561726469616e20566" + "16c696461746f7220546f6b656e2028475654292c20616e6420477561726469616e2056616c696461746f727320616c6c20706172746963697061746520696e20616e20696e" + "746572636f6e6e6563746564206d616e6167656e742073797374656d2c20616e642061726520636f6e7369646572656420746865204e75636c65757320546f6b656e732e205" + "46865204d475420746f6b656e20697473656c66206973206120746f6b656e697a6564206d616e6167656d656e74206b65792077697468207370656369616c20617574686f72" + "6974696573206e656564656420666f7220746f6b656e20696e63657074696f6e206f6e2074686520626c6f636b636861696e2e20546865204d475420746f6b656e20636f6e7" + "4696e75657320746f20706c6179206120726f6c6520696e20746865206d616e6167656d656e74206f6620616e642061636365737320746f207370656369616c206665617475" + "7265732e222c0a20202265787465726e616c5f75726c223a202268747470733a2f2f6769746875622e636f6d2f6279747a63757272656e63792f6279747a222c0a202022696" + "d616765223a202268747470733a2f2f6279747a2e67672f696d616765732f6272616e64696e672f6279747a2d686f72697a6f6e74616c2d6c6f676f2e737667222c0a202022" + "617474726962757465735f75726c223a202268747470733a2f2f6769746875622e636f6d2f6279747a63757272656e63792f4154502d6465736372697074696f6e732f74657" + "3746e65742f7b69647d5f617474726962757465732e6a736f6e220a207d") ); } RPCTypeCheck(request.params, {UniValue::VSTR}); - CDataStream ssTGDocument(ParseHexV(request.params[0], "data"), SER_NETWORK, PROTOCOL_VERSION); - CTokenGroupDocument tgDocument; - ssTGDocument >> tgDocument; - UniValue ret(UniValue::VOBJ); + CTokenGroupDocument tgDocument(ParseHexV(request.params[0], "data")); tgDocument.ToJson(ret); return ret; } -UniValue verifyrawtokendocument(const JSONRPCRequest& request) +UniValue verifytokenmetadata(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 2) { + if (request.fHelp || request.params.size() != 3) { throw std::runtime_error( - "verifyrawtokendocument \"data\" \"address\"\n" - "\nCalculates a diff between two deterministic masternode lists. The result also contains proof data.\n" + "verifytokenmetadata \"hex_data\" \"creation_address\" \"signature\"\n" + "\nVerifies that the given address is used to sign the token metadata document.\n" "\nArguments:\n" - "1. \"data\" (hex, required) The starting block height.\n" - "2. \"address\" (string, required) The ending block height.\n" + "1. \"hex_data\" (hex, required) The hex-encoded token metadata document as returned by encodetokenmetadata\n" + "2. \"creation_address\" (string, required) The token creation address, which will be used to sign the token document\n" + "3. \"signature\" (string, optional) The token metadata document signature.\n" "\nResult:\n" "true|false (boolean) If the signature is verified or not\n" "\nExamples:\n" "\nVerify the hex-encoded MGT testnet document\n" - + HelpExampleCli("verifyrawtokendocument", - "0100034d4754104d616e6167656d656e7420546f6b656e0c4259545a2e746573746e6574fd7b01546865204d475420746f6b656e206973206120746f6b656e697a6564206d6" - "16e6167656d656e74206b6579206f6e20746865204259545a20626c6f636b636861696e2077697468207370656369616c20617574686f726974696573206e65636573736172" - "7920666f723a202831292074686520636f6e737472756374696f6e206f66206120746f6b656e2073797374656d207769746820636f686572656e742065636f6e6f6d6963206" - "96e63656e74697665733b202832292074686520696e63657074696f6e206f66204e75636c65757320546f6b656e7320287370656369616c20746f6b656e7320746861742068" - "61766520696e74657272656c61746564206d6f6e657461727920706f6c6963696573293b20616e64202833292074686520646973747269627574696f6e206f6620726577617" - "264732074686174207375737461696e20746869732073797374656d206f662063727970746f6772617068696320746f6b656e73206f6e2074686520626c6f636b636861696e" - "2efd0e025468652041746f6d696320546f6b656e2050726f746f636f6c20284154502920696e74726f64756365732063726f73732d636f696e20616e642063726f73732d746" - "f6b656e20706f6c6963792e204259545a207574696c697a65732041545020666f7220697473207265776172642073797374656d20616e642072696768747320737472756374" - "7572652e204d616e6167656d656e7420546f6b656e20284d4754292c20477561726469616e2056616c696461746f7220546f6b656e2028475654292c20616e6420477561726" - "469616e2056616c696461746f727320616c6c20706172746963697061746520696e20616e20696e746572636f6e6e6563746564206d616e6167656e742073797374656d2c20" - "616e642061726520636f6e7369646572656420746865204e75636c65757320546f6b656e732e20546865204d475420746f6b656e20697473656c66206973206120746f6b656" - "e697a6564206d616e6167656d656e74206b65792077697468207370656369616c20617574686f726974696573206e656564656420666f7220746f6b656e20696e6365707469" - "6f6e206f6e2074686520626c6f636b636861696e2e20546865204d475420746f6b656e20636f6e74696e75657320746f20706c6179206120726f6c6520696e20746865206d6" - "16e6167656d656e74206f6620616e642061636365737320746f207370656369616c2066656174757265732e18546865204259545a20436f726520446576656c6f7065727324" - "68747470733a2f2f6769746875622e636f6d2f6279747a63757272656e63792f6279747a004120fa4cc8f93c6d52ce6690b6997b7ae3c785fe291c5c6e44370ef1557f61aeb" - "1242fddd9aa13941e4b5be53d07998ebb201ce2cfa96c832d5fee743c5600c7277b Tq15q6NNKDLKsD8uRwLo8Za355afgavuVb") + + HelpExampleCli("verifytokenmetadata", + "7b0a202022617470223a207b0a2020202276657273696f6e223a20312c0a2020202274797065223a20226d616e6167656d656e74220a20207d2c0a2020227469636b6572223" + "a20224d4754222c0a2020226e616d65223a20224d616e6167656d656e7420546f6b656e222c0a202022636861696e223a20224259545a2e746573746e6574222c0a20202263" + "726561746f72223a2022546865204279747a20436f726520646576656c6f70657273222c0a20202273756d6d617279223a2022546865204d475420746f6b656e20697320612" + "0746f6b656e697a6564206d616e6167656d656e74206b6579206f6e20746865204259545a20626c6f636b636861696e2077697468207370656369616c20617574686f726974" + "696573206e656365737361727920666f723a202831292074686520636f6e737472756374696f6e206f66206120746f6b656e2073797374656d207769746820636f686572656" + "e742065636f6e6f6d696320696e63656e74697665733b202832292074686520696e63657074696f6e206f66204e75636c65757320546f6b656e7320287370656369616c2074" + "6f6b656e732074686174206861766520696e74657272656c61746564206d6f6e657461727920706f6c6963696573293b20616e6420283329207468652064697374726962757" + "4696f6e206f6620726577617264732074686174207375737461696e20746869732073797374656d206f662063727970746f6772617068696320746f6b656e73206f6e207468" + "6520626c6f636b636861696e2e222c0a2020226465736372697074696f6e223a20225468652041746f6d696320546f6b656e2050726f746f636f6c20284154502920696e747" + "26f64756365732063726f73732d636f696e20616e642063726f73732d746f6b656e20706f6c6963792e204259545a207574696c697a65732041545020666f72206974732072" + "65776172642073797374656d20616e6420726967687473207374727563747572652e204d616e6167656d656e7420546f6b656e20284d4754292c20477561726469616e20566" + "16c696461746f7220546f6b656e2028475654292c20616e6420477561726469616e2056616c696461746f727320616c6c20706172746963697061746520696e20616e20696e" + "746572636f6e6e6563746564206d616e6167656e742073797374656d2c20616e642061726520636f6e7369646572656420746865204e75636c65757320546f6b656e732e205" + "46865204d475420746f6b656e20697473656c66206973206120746f6b656e697a6564206d616e6167656d656e74206b65792077697468207370656369616c20617574686f72" + "6974696573206e656564656420666f7220746f6b656e20696e63657074696f6e206f6e2074686520626c6f636b636861696e2e20546865204d475420746f6b656e20636f6e7" + "4696e75657320746f20706c6179206120726f6c6520696e20746865206d616e6167656d656e74206f6620616e642061636365737320746f207370656369616c206665617475" + "7265732e222c0a20202265787465726e616c5f75726c223a202268747470733a2f2f6769746875622e636f6d2f6279747a63757272656e63792f6279747a222c0a202022696" + "d616765223a202268747470733a2f2f6279747a2e67672f696d616765732f6272616e64696e672f6279747a2d686f72697a6f6e74616c2d6c6f676f2e737667222c0a202022" + "617474726962757465735f75726c223a202268747470733a2f2f6769746875622e636f6d2f6279747a63757272656e63792f4154502d6465736372697074696f6e732f74657" + "3746e65742f7b69647d5f617474726962757465732e6a736f6e220a207d TwXyY5uJmzU9bMXPDbf5LyqrBczboMdeNL " + "1f8c4276ade8a8c2f6ba20bfa3e48b6bf520e35a65dbbd070a9583b097d0e78b7d4f68abb1df4393f31dd3e961bc1a49e59dd8378c0ffcb8f2361e87bfdf7535fe") ); } - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VSTR}); + RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VSTR, UniValue::VSTR}); std::string strHexDescription = request.params[0].get_str(); std::string strAddress = request.params[1].get_str(); + std::string strSignature = request.params[2].get_str(); CTxDestination dest = DecodeDestination(strAddress); if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Wagerr address"); @@ -906,9 +850,11 @@ UniValue verifyrawtokendocument(const JSONRPCRequest& request) throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to a key"); } - CDataStream ssTGDocument(ParseHexV(request.params[0], "data"), SER_NETWORK, PROTOCOL_VERSION); - CTokenGroupDocument tgDocument; - ssTGDocument >> tgDocument; + UniValue ret(UniValue::VOBJ); + CTokenGroupDocument tgDocument(ParseHexV(request.params[0], "data")); + tgDocument.ToJson(ret); + + tgDocument.SetSignature(strSignature); return tgDocument.CheckSignature(*keyID); } @@ -920,9 +866,9 @@ static const CRPCCommand commands[] = { "tokens", "gettokentransaction", &gettokentransaction, {} }, { "tokens", "getsubgroupid", &getsubgroupid, {} }, { "tokens", "createrawtokentransaction",&createrawtokentransaction, {} }, - { "tokens", "createrawtokendocument", &createrawtokendocument, {"options", "verbose"} }, - { "tokens", "decoderawtokendocument", &decoderawtokendocument, {} }, - { "tokens", "verifyrawtokendocument", &verifyrawtokendocument, {"hexstring","address"} }, + { "tokens", "encodetokenmetadata", &encodetokenmetadata, {"spec"} }, + { "tokens", "decodetokenmetadata", &decodetokenmetadata, {} }, + { "tokens", "verifytokenmetadata", &verifytokenmetadata, {"hex_data","creation_address", "signature"} }, }; void RegisterTokensRPCCommands(CRPCTable &t) diff --git a/src/tokens/rpctokenwallet.cpp b/src/tokens/rpctokenwallet.cpp index 88e7469491db1f..f764fb876fe0ba 100644 --- a/src/tokens/rpctokenwallet.cpp +++ b/src/tokens/rpctokenwallet.cpp @@ -316,8 +316,8 @@ extern UniValue gettokenbalance(const JSONRPCRequest& request) CTokenGroupID parentgrp = grpID.parentGroup(); std::vector subgroupData = grpID.GetSubGroupData(); tokenGroupManager.get()->GetTokenGroupCreation(parentgrp, tgCreation); - retobj.push_back(Pair("parentGroupID", EncodeTokenGroup(parentgrp))); - retobj.push_back(Pair("subgroupData", std::string(subgroupData.begin(), subgroupData.end()))); + retobj.push_back(Pair("parent_groupID", EncodeTokenGroup(parentgrp))); + retobj.push_back(Pair("subgroup_data", std::string(subgroupData.begin(), subgroupData.end()))); } else { tokenGroupManager.get()->GetTokenGroupCreation(grpID, tgCreation); } @@ -711,92 +711,6 @@ extern UniValue sendtoken(const JSONRPCRequest& request) return tx->GetHash().GetHex(); } -extern UniValue configuretokendryrun(const JSONRPCRequest& request) -{ - std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request); - CWallet* const pwallet = wallet.get(); - - if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { - return NullUniValue; - } - - LOCK2(cs_main, pwallet->cs_wallet); - - unsigned int curparam = 0; - bool confirmed = false; - - COutput coin(nullptr, 0, 0, false, false, false); - - { - std::vector coins; - CAmount lowest = MAX_MONEY; - pwallet->FilterCoins(coins, [&lowest](const CWalletTx *tx, const CTxOut *out) { - CTokenGroupInfo tg(out->scriptPubKey); - // although its possible to spend a grouped input to produce - // a single mint group, I won't allow it to make the tx construction easier. - if ((tg.associatedGroup == NoGroup) && (out->nValue < lowest)) - { - lowest = out->nValue; - return true; - } - return false; - }); - - if (0 == coins.size()) - { - throw JSONRPCError(RPC_INVALID_PARAMS, "No coins available in the wallet"); - } - coin = coins[coins.size() - 1]; - } - - uint64_t grpNonce = 0; - - std::vector chosenCoins; - chosenCoins.push_back(coin); - - std::vector outputs; - - CReserveKey authKeyReservation(pwallet); - CTxDestination authDest; - std::shared_ptr tgDesc; - - if (!ParseGroupDescParamsRegular(request, curparam, tgDesc, confirmed)) { - return false; - } - - CPubKey authKey; - authKeyReservation.GetReservedKey(authKey, true); - authDest = authKey.GetID(); - curparam++; - - TokenGroupIdFlags tgFlags = TokenGroupIdFlags::NONE; - CTokenGroupID grpID = findGroupId(coin.GetOutPoint(), tgDesc, tgFlags, grpNonce); - - CScript script = GetScriptForDestination(authDest, grpID, (CAmount)GroupAuthorityFlags::ALL | grpNonce); - CRecipient recipient = {script, GROUPED_SATOSHI_AMT, false}; - outputs.push_back(recipient); - - UniValue ret(UniValue::VOBJ); - - ret.push_back(Pair("groupID", EncodeTokenGroup(grpID))); - - CTokenGroupInfo tokenGroupInfo(script); - CTokenGroupStatus tokenGroupStatus; - CTokenGroupDescriptionVariant tgDescVariant = *tgDesc.get(); - CTransaction dummyTransaction; - CTokenGroupCreation tokenGroupCreation(MakeTransactionRef(dummyTransaction), uint256(), tokenGroupInfo, std::make_shared(tgDescVariant), tokenGroupStatus); - tokenGroupCreation.ValidateDescription(); - - ret.push_back(Pair("ticker", tgDescGetTicker(*tokenGroupCreation.pTokenGroupDescription))); - ret.push_back(Pair("name", tgDescGetName(*tokenGroupCreation.pTokenGroupDescription))); - ret.push_back(Pair("decimalPos", tgDescGetDecimalPos(*tokenGroupCreation.pTokenGroupDescription))); - ret.push_back(Pair("URL", tgDescGetDocumentURL(*tokenGroupCreation.pTokenGroupDescription))); - ret.push_back(Pair("documentHash", tgDescGetDocumentHash(*tokenGroupCreation.pTokenGroupDescription).ToString())); - ret.push_back(Pair("status", tokenGroupCreation.status.messages)); - - return ret; -} - extern UniValue configuretoken(const JSONRPCRequest& request) { std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request); @@ -808,15 +722,15 @@ extern UniValue configuretoken(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 5) throw std::runtime_error( - "configuretoken \"ticker\" \"name\" \"description_url\" description_hash decimalpos ( confirm_send ) \n" + "configuretoken \"ticker\" \"name\" decimal_pos \"metadata_url\" metadata_hash ( confirm_send ) \n" "\n" "Configures a new token type.\n" "\nArguments:\n" "1. \"ticker\" (string, required) the token ticker\n" "2. \"name\" (string, required) the token name\n" - "3. \"description_url\" (string, required) the URL of the token's description document\n" - "4. \"description_hash\" (hex, required) the hash of the token description document\n" - "5. \"decimalpos\" (numeric, required) the number of decimals after the decimal separator\n" + "3. \"decimal_pos\" (numeric, required) the number of decimals after the decimal separator\n" + "4. \"metadata_url\" (string, required) the URL of the token's description document\n" + "5. \"metadata_hash\" (hex, required) the hash of the token description document\n" "6. \"confirm_send\" (boolean, optional, default=false) the configuration transaction will be sent\n" "\n" "\nExamples:\n" + @@ -824,10 +738,6 @@ extern UniValue configuretoken(const JSONRPCRequest& request) "\n" ); - if (request.params.size() < 6 || request.params[5].get_str() != "true") { - return configuretokendryrun(request); - } - EnsureWalletIsUnlocked(pwallet); LOCK2(cs_main, pwallet->cs_wallet); @@ -871,7 +781,7 @@ extern UniValue configuretoken(const JSONRPCRequest& request) CScript opretScript; std::shared_ptr tgDesc; - if (!ParseGroupDescParamsRegular(request, curparam, tgDesc, confirmed)) { + if (!ParseGroupDescParamsRegular(request, curparam, tgDesc, confirmed) || !confirmed) { return false; } CPubKey authKey; @@ -906,16 +816,16 @@ extern UniValue configuremanagementtoken(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 5) throw std::runtime_error( - "configuremanagementtoken \"ticker\" \"name\" \"description_url\" description_hash decimalpos \"blsPubKey\" sticky_melt ( confirm_send ) \n" + "configuremanagementtoken \"ticker\" \"name\" decimal_pos \"metadata_url\" metadata_hash \"bls_pubkey\" sticky_melt ( confirm_send ) \n" "\n" "Configures a new management token type. Currelty the only management tokens are MGT and GVN.\n" "\nArguments:\n" "1. \"ticker\" (string, required) the token ticker\n" "2. \"name\" (string, required) the token name\n" - "3. \"description_url\" (string, required) the URL of the token's description document\n" - "4. \"description_hash\" (hex) the hash of the token description document\n" - "5. \"decimalpos\" (numeric, required) the number of decimals after the decimal separator\n" - "6. \"blsPubKey\" (string, required) the BLS public key. The BLS private key does not have to be known.\n" + "3. \"decimal_pos\" (numeric, required) the number of decimals after the decimal separator\n" + "4. \"metadata_url\" (string, required) the URL of the token's description document\n" + "5. \"metadata_hash\" (hex) the hash of the token description document\n" + "6. \"bls_pubkey\" (string, required) the BLS public key. The BLS private key does not have to be known.\n" "7. \"sticky_melt\" (boolean, required) the token can be melted, also without a token melt authority\n" "8. \"confirm_send\" (boolean, optional, default=false) the configuration transaction will be sent\n" "\n" @@ -937,7 +847,7 @@ extern UniValue configuremanagementtoken(const JSONRPCRequest& request) std::vector outputs; std::shared_ptr tgDesc; - if (!ParseGroupDescParamsMGT(request, curparam, tgDesc, fStickyMelt, confirmed)) { + if (!ParseGroupDescParamsMGT(request, curparam, tgDesc, fStickyMelt, confirmed) || !confirmed) { return false; } CPubKey authKey; @@ -1057,18 +967,21 @@ extern UniValue configurenft(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 5) throw std::runtime_error( - "configurenft \"name\" \"description_url\" description_hash data ( confirm_send ) \n" + "configurenft \"name\" \"mint_amount\" \"metadata_url\" metadata_hash data data_filename ( confirm_send ) \n" "\n" "Configures a new token type.\n" "\nArguments:\n" - "1. \"name\" (string, required) the token name\n" - "2. \"description_url\" (string, required) the URL of the token's description document\n" - "3. \"description_hash\" (hex, required) the hash of the token description document\n" - "4. \"data\" (base64, required) Base64 encoded data\n" - "5. \"confirm_send\" (boolean, optional, default=false) the configuration transaction will be sent\n" + "1. \"name\" (string, required) the token name\n" + "2. \"mint_amount\" (number, required) the fixed mint amount\n" + " This amount MUST be minted in the token's first and only mint action\n" + "3. \"metadata_url\" (string, required) the URL of the token's description document\n" + "4. \"metadata_hash\" (hex, required) the hash of the token description document\n" + "5. \"data\" (base64, required) Base64 encoded data\n" + "6. \"data_filename\" (string, required) Filename for the base64 encoded data\n" + "7. \"confirm_send\" (boolean, optional, default=false) the configuration transaction will be sent\n" "\n" "\nExamples:\n" + - HelpExampleCli("configureNFT", "\"UniquelyFun\" \"https://raw.githubusercontent.com/wagerr/ATP-descriptions/master/WAGERR-testnet-UniquelyFun.json\" 4f92d91db24bb0b8ca24a2ec86c4b012ccdc4b2e9d659c2079f5cc358413a765 VGhpcyB0b2tlbiBpcyB1bmlxdWUgYW5kIGZ1bi4= true") + + HelpExampleCli("configurenft", "\"John Doe concert tickets - Garden of Eden tour\" 300 \"https://yourtickettomusic.com/nft/{id}.json\" d49f449afe7548d428c1c317a79e3411b2dcf932d7a4880c832333b3f7c7fd24 \"WW91ciB0aWNrZXQ=\" \"file.txt\" true") + "\n" ); @@ -1115,7 +1028,7 @@ extern UniValue configurenft(const JSONRPCRequest& request) CScript opretScript; std::shared_ptr tgDesc; - if (!ParseGroupDescParamsNFT(request, curparam, tgDesc, confirmed)) { + if (!ParseGroupDescParamsNFT(request, curparam, tgDesc, confirmed) || !confirmed) { return false; } CPubKey authKey; @@ -1854,7 +1767,7 @@ UniValue listunspenttokens(const JSONRPCRequest& request) return results; } -UniValue signrawtokendocument(const JSONRPCRequest& request) +UniValue signtokenmetadata(const JSONRPCRequest& request) { std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request); CWallet* const pwallet = wallet.get(); @@ -1862,36 +1775,40 @@ UniValue signrawtokendocument(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 2 || request.params.size() > 3) { + if (request.fHelp || request.params.size() != 2) { throw std::runtime_error( - "signrawtokendocument \"data\" \"address\" ( \"verbose\" )\n" + "signtokenmetadata \"hex_data\" \"creation_address\"\n" "\nSigns the raw token document using the supplied address.\n" "\nArguments:\n" - "1. \"data\" (hex, required) The (unsigned) serialized token document\n" - "2. \"address\" (string, required) The address used to sign the token document\n" - "3. \"verbose\" (bool, optional,default=false) Output the json encoded specification instead of the hex-encoded serialized data\n" + "1. \"hex_data\" (hex, required) The hex-encoded token metadata document as returned by encodetokenmetadata\n" + "2. \"creation_address\" (string, required) The token creation address, which will be used to sign the token document\n" "\nResult:\n" - "\"hex\" : \"value\" (string) The hex-encoded signed raw token document\n" + "\"signature\" : \"value\" (string) The signature of the token metadata document\n" "\nExamples:\n" "\nSign the hex-encoded MGT testnet document\n" - + HelpExampleCli("signrawtokendocument", - "0100034d4754104d616e6167656d656e7420546f6b656e0c4259545a2e746573746e6574fd7b01546865204d475420746f6b656e206973206120746f6b656e697a6564206d6" - "16e6167656d656e74206b6579206f6e20746865204259545a20626c6f636b636861696e2077697468207370656369616c20617574686f726974696573206e65636573736172" - "7920666f723a202831292074686520636f6e737472756374696f6e206f66206120746f6b656e2073797374656d207769746820636f686572656e742065636f6e6f6d6963206" - "96e63656e74697665733b202832292074686520696e63657074696f6e206f66204e75636c65757320546f6b656e7320287370656369616c20746f6b656e7320746861742068" - "61766520696e74657272656c61746564206d6f6e657461727920706f6c6963696573293b20616e64202833292074686520646973747269627574696f6e206f6620726577617" - "264732074686174207375737461696e20746869732073797374656d206f662063727970746f6772617068696320746f6b656e73206f6e2074686520626c6f636b636861696e" - "2efd0e025468652041746f6d696320546f6b656e2050726f746f636f6c20284154502920696e74726f64756365732063726f73732d636f696e20616e642063726f73732d746" - "f6b656e20706f6c6963792e204259545a207574696c697a65732041545020666f7220697473207265776172642073797374656d20616e642072696768747320737472756374" - "7572652e204d616e6167656d656e7420546f6b656e20284d4754292c20477561726469616e2056616c696461746f7220546f6b656e2028475654292c20616e6420477561726" - "469616e2056616c696461746f727320616c6c20706172746963697061746520696e20616e20696e746572636f6e6e6563746564206d616e6167656e742073797374656d2c20" - "616e642061726520636f6e7369646572656420746865204e75636c65757320546f6b656e732e20546865204d475420746f6b656e20697473656c66206973206120746f6b656" - "e697a6564206d616e6167656d656e74206b65792077697468207370656369616c20617574686f726974696573206e656564656420666f7220746f6b656e20696e6365707469" - "6f6e206f6e2074686520626c6f636b636861696e2e20546865204d475420746f6b656e20636f6e74696e75657320746f20706c6179206120726f6c6520696e20746865206d6" - "16e6167656d656e74206f6620616e642061636365737320746f207370656369616c2066656174757265732e18546865204259545a20436f726520446576656c6f7065727324" - "68747470733a2f2f6769746875622e636f6d2f6279747a63757272656e63792f6279747a0000 Tq15q6NNKDLKsD8uRwLo8Za355afgavuVb") + + HelpExampleCli("signtokenmetadata", + "7b0a202022617470223a207b0a2020202276657273696f6e223a20312c0a2020202274797065223a20226d616e6167656d656e74220a20207d2c0a2020227469636b6572223" + "a20224d4754222c0a2020226e616d65223a20224d616e6167656d656e7420546f6b656e222c0a202022636861696e223a20224259545a2e746573746e6574222c0a20202263" + "726561746f72223a2022546865204279747a20436f726520646576656c6f70657273222c0a20202273756d6d617279223a2022546865204d475420746f6b656e20697320612" + "0746f6b656e697a6564206d616e6167656d656e74206b6579206f6e20746865204259545a20626c6f636b636861696e2077697468207370656369616c20617574686f726974" + "696573206e656365737361727920666f723a202831292074686520636f6e737472756374696f6e206f66206120746f6b656e2073797374656d207769746820636f686572656" + "e742065636f6e6f6d696320696e63656e74697665733b202832292074686520696e63657074696f6e206f66204e75636c65757320546f6b656e7320287370656369616c2074" + "6f6b656e732074686174206861766520696e74657272656c61746564206d6f6e657461727920706f6c6963696573293b20616e6420283329207468652064697374726962757" + "4696f6e206f6620726577617264732074686174207375737461696e20746869732073797374656d206f662063727970746f6772617068696320746f6b656e73206f6e207468" + "6520626c6f636b636861696e2e222c0a2020226465736372697074696f6e223a20225468652041746f6d696320546f6b656e2050726f746f636f6c20284154502920696e747" + "26f64756365732063726f73732d636f696e20616e642063726f73732d746f6b656e20706f6c6963792e204259545a207574696c697a65732041545020666f72206974732072" + "65776172642073797374656d20616e6420726967687473207374727563747572652e204d616e6167656d656e7420546f6b656e20284d4754292c20477561726469616e20566" + "16c696461746f7220546f6b656e2028475654292c20616e6420477561726469616e2056616c696461746f727320616c6c20706172746963697061746520696e20616e20696e" + "746572636f6e6e6563746564206d616e6167656e742073797374656d2c20616e642061726520636f6e7369646572656420746865204e75636c65757320546f6b656e732e205" + "46865204d475420746f6b656e20697473656c66206973206120746f6b656e697a6564206d616e6167656d656e74206b65792077697468207370656369616c20617574686f72" + "6974696573206e656564656420666f7220746f6b656e20696e63657074696f6e206f6e2074686520626c6f636b636861696e2e20546865204d475420746f6b656e20636f6e7" + "4696e75657320746f20706c6179206120726f6c6520696e20746865206d616e6167656d656e74206f6620616e642061636365737320746f207370656369616c206665617475" + "7265732e222c0a20202265787465726e616c5f75726c223a202268747470733a2f2f6769746875622e636f6d2f6279747a63757272656e63792f6279747a222c0a202022696" + "d616765223a202268747470733a2f2f6279747a2e67672f696d616765732f6272616e64696e672f6279747a2d686f72697a6f6e74616c2d6c6f676f2e737667222c0a202022" + "617474726962757465735f75726c223a202268747470733a2f2f6769746875622e636f6d2f6279747a63757272656e63792f4154502d6465736372697074696f6e732f74657" + "3746e65742f7b69647d5f617474726962757465732e6a736f6e220a207d TwXyY5uJmzU9bMXPDbf5LyqrBczboMdeNL") ); } @@ -1920,24 +1837,13 @@ UniValue signrawtokendocument(const JSONRPCRequest& request) throw JSONRPCError(RPC_WALLET_ERROR, "Private key for address " + strAddress + " is not known"); } - CDataStream ssTGDocument(ParseHexV(request.params[0], "data"), SER_NETWORK, PROTOCOL_VERSION); - CTokenGroupDocument tgDocument; - ssTGDocument >> tgDocument; + CTokenGroupDocument tgDocument(ParseHexV(request.params[0], "data")); if (!tgDocument.Sign(vchSecret)) { throw JSONRPCError(RPC_WALLET_ERROR, "Unable to sign document"); } - if (fVerbose) { - UniValue ret(UniValue::VOBJ); - tgDocument.ToJson(ret); - return ret; - } - CDataStream ssTGDocumentOut(SER_NETWORK, PROTOCOL_VERSION); - ssTGDocumentOut << tgDocument; - std::string strData = HexStr(ssTGDocumentOut.begin(), ssTGDocumentOut.end()); - - return strData; + return tgDocument.GetSignature(); } static const CRPCCommand commands[] = @@ -1951,7 +1857,7 @@ static const CRPCCommand commands[] = { "tokens", "configuretoken", &configuretoken, {} }, { "tokens", "configuremanagementtoken", &configuremanagementtoken, {} }, { "tokens", "configurenft", &configurenft, {} }, - { "tokens", "signrawtokendocument", &signrawtokendocument, {"data","address","verbose"} }, + { "tokens", "signtokenmetadata", &signtokenmetadata, {"data","address","verbose"} }, { "tokens", "createtokenauthorities", &createtokenauthorities, {} }, { "tokens", "listtokenauthorities", &listtokenauthorities, {} }, { "tokens", "droptokenauthorities", &droptokenauthorities, {} }, diff --git a/src/tokens/tokengroupconfiguration.cpp b/src/tokens/tokengroupconfiguration.cpp index 977dbfc9f102a3..7767aba3cd2303 100644 --- a/src/tokens/tokengroupconfiguration.cpp +++ b/src/tokens/tokengroupconfiguration.cpp @@ -70,7 +70,7 @@ template void TGFilterTickerCharacters(CTokenGroupDescriptionMGT& tgDesc); template void TGFilterNameCharacters(T& tgDesc) { - std::regex regexName("^[a-zA-Z0-9][a-zA-Z0-9 ]*[a-zA-Z0-9]$"); // letters, numbers and spaces; at least 2 characters; no space at beginning or end + std::regex regexName("^[a-zA-Z0-9][a-zA-Z0-9- ]*[a-zA-Z0-9]$"); // letters, numbers and spaces; at least 2 characters; no space or dash at beginning or end std::smatch matchResult; diff --git a/src/tokens/tokengroupdescription.cpp b/src/tokens/tokengroupdescription.cpp index 82c1e3507214fd..e73635e244aa2e 100644 --- a/src/tokens/tokengroupdescription.cpp +++ b/src/tokens/tokengroupdescription.cpp @@ -16,8 +16,8 @@ void CTokenGroupDescriptionRegular::ToJson(UniValue& obj) const obj.setObject(); obj.pushKV("ticker", strTicker); obj.pushKV("name", strName); - obj.pushKV("document_URL", strDocumentUrl); - obj.pushKV("documentHash", documentHash.ToString()); + obj.pushKV("metadata_url", strDocumentUrl); + obj.pushKV("metadata_hash", documentHash.ToString()); obj.pushKV("decimal_pos", (int)nDecimalPos); } @@ -27,8 +27,8 @@ void CTokenGroupDescriptionMGT::ToJson(UniValue& obj) const obj.setObject(); obj.pushKV("ticker", strTicker); obj.pushKV("name", strName); - obj.pushKV("document_URL", strDocumentUrl); - obj.pushKV("documentHash", documentHash.ToString()); + obj.pushKV("metadata_url", strDocumentUrl); + obj.pushKV("metadata_hash", documentHash.ToString()); obj.pushKV("decimal_pos", (int)nDecimalPos); obj.pushKV("bls_pubkey", blsPubKey.ToString()); } @@ -38,9 +38,9 @@ void CTokenGroupDescriptionNFT::ToJson(UniValue& obj) const obj.clear(); obj.setObject(); obj.pushKV("name", strName); - obj.pushKV("document_URL", strDocumentUrl); - obj.pushKV("documentHash", documentHash.ToString()); - obj.pushKV("vchData", "TODO"); + obj.pushKV("metadata_url", strDocumentUrl); + obj.pushKV("metadata_hash", documentHash.ToString()); + obj.pushKV("data", EncodeBase64(vchData.data(), vchData.size())); } std::string ConsumeParamTicker(const JSONRPCRequest& request, unsigned int &curparam) { @@ -63,8 +63,8 @@ std::string ConsumeParamName(const JSONRPCRequest& request, unsigned int &curpar throw JSONRPCError(RPC_INVALID_PARAMS, "Missing parameter: token name"); } std::string strName = request.params[curparam].get_str(); - if (strName.size() > 30) { - std::string strError = strprintf("Name %s has too many characters (30 max)", strName); + if (strName.size() > 80) { + std::string strError = strprintf("Name %s has too many characters (80 max)", strName); throw JSONRPCError(RPC_INVALID_PARAMS, strError); } curparam++; @@ -109,7 +109,7 @@ uint8_t ConsumeParamDecimalPos(const JSONRPCRequest& request, unsigned int &curp std::string strCurparamValue = request.params[curparam].get_str(); int32_t nDecimalPos32; if (!ParseInt32(strCurparamValue, &nDecimalPos32) || nDecimalPos32 > 16 || nDecimalPos32 < 0) { - std::string strError = strprintf("Parameter %d is invalid - valid values are between 0 and 16", nDecimalPos32); + std::string strError = strprintf("Parameter %s is invalid - valid values are between 0 and 16", strCurparamValue); throw JSONRPCError(RPC_INVALID_PARAMS, strError); } curparam++; @@ -151,6 +151,37 @@ std::vector ConsumeParamNFTData(const JSONRPCRequest& request, un return vchData; } +uint64_t ConsumeParamMintAmount(const JSONRPCRequest& request, unsigned int &curparam) { + if (curparam >= request.params.size()) + { + std::string strError = strprintf("Not enough paramaters"); + throw JSONRPCError(RPC_INVALID_PARAMS, strError); + } + std::string strCurparamValue = request.params[curparam].get_str(); + uint64_t nMintAmount; + if (!ParseUInt64(strCurparamValue, &nMintAmount) || !MoneyRange(nMintAmount)) { + std::string strError = strprintf("Parameter %s is invalid", strCurparamValue); + throw JSONRPCError(RPC_INVALID_PARAMS, strError); + } + curparam++; + return nMintAmount; +} + +std::string ConsumeParamFilename(const JSONRPCRequest& request, unsigned int &curparam) { + if (curparam >= request.params.size()) + { + std::string strError = strprintf("Not enough paramaters"); + throw JSONRPCError(RPC_INVALID_PARAMS, strError); + } + std::string strFilename = request.params[curparam].get_str(); + if (strFilename.size() > 98) { + std::string strError = strprintf("Filename %s has too many characters (98 max)", strFilename); + throw JSONRPCError(RPC_INVALID_PARAMS, strError); + } + curparam++; + return strFilename; +} + bool ParseGroupDescParamsRegular(const JSONRPCRequest& request, unsigned int &curparam, std::shared_ptr& tgDesc, bool &confirmed) { std::string strCurparamValue; @@ -159,9 +190,9 @@ bool ParseGroupDescParamsRegular(const JSONRPCRequest& request, unsigned int &cu std::string strTicker = ConsumeParamTicker(request, curparam); std::string strName = ConsumeParamName(request, curparam); + uint8_t nDecimalPos = ConsumeParamDecimalPos(request, curparam); std::string strDocumentUrl = ConsumeParamDocumentURL(request, curparam); uint256 documentHash = ConsumeParamDocumentHash(request, curparam); - uint8_t nDecimalPos = ConsumeParamDecimalPos(request, curparam); tgDesc = std::make_shared(strTicker, strName, nDecimalPos, strDocumentUrl, documentHash); @@ -185,9 +216,9 @@ bool ParseGroupDescParamsMGT(const JSONRPCRequest& request, unsigned int &curpar std::string strTicker = ConsumeParamTicker(request, curparam); std::string strName = ConsumeParamName(request, curparam); + uint8_t nDecimalPos = ConsumeParamDecimalPos(request, curparam); std::string strDocumentUrl = ConsumeParamDocumentURL(request, curparam); uint256 documentHash = ConsumeParamDocumentHash(request, curparam); - uint8_t nDecimalPos = ConsumeParamDecimalPos(request, curparam); CBLSPublicKey blsPubKey = ConsumeParamBLSPublicKey(request, curparam); tgDesc = std::make_shared(strTicker, strName, nDecimalPos, strDocumentUrl, documentHash, blsPubKey); @@ -221,11 +252,13 @@ bool ParseGroupDescParamsNFT(const JSONRPCRequest& request, unsigned int &curpar confirmed = false; std::string strName = ConsumeParamName(request, curparam); + uint64_t nMintAmount = ConsumeParamMintAmount(request, curparam); std::string strDocumentUrl = ConsumeParamDocumentURL(request, curparam); uint256 documentHash = ConsumeParamDocumentHash(request, curparam); std::vector vchData = ConsumeParamNFTData(request, curparam); + std::string strDataFilename = ConsumeParamFilename(request, curparam); - tgDesc = std::make_shared(strName, strDocumentUrl, documentHash, vchData); + tgDesc = std::make_shared(strName, nMintAmount, strDocumentUrl, documentHash, vchData, strDataFilename); if (curparam >= request.params.size()) { diff --git a/src/tokens/tokengroupdescription.h b/src/tokens/tokengroupdescription.h index 650180fecae474..428a3c6b2efc37 100644 --- a/src/tokens/tokengroupdescription.h +++ b/src/tokens/tokengroupdescription.h @@ -162,22 +162,26 @@ class CTokenGroupDescriptionNFT uint16_t nVersion{CURRENT_VERSION}; std::string strName; // Token name + uint64_t nMintAmount; // Fixed token mint amount std::string strDocumentUrl; // Extended token description document URL uint256 documentHash; std::vector vchData; + std::string strDataFilename; // File name for the data CTokenGroupDescriptionNFT() { SetNull(); }; - CTokenGroupDescriptionNFT(std::string strName, std::string strDocumentUrl, uint256 documentHash, std::vector vchData) : - strName(strName), strDocumentUrl(strDocumentUrl), documentHash(documentHash), vchData(vchData) { }; + CTokenGroupDescriptionNFT(std::string strName, uint64_t nMintAmount, std::string strDocumentUrl, uint256 documentHash, std::vector vchData, std::string strDataFilename) : + strName(strName), nMintAmount(nMintAmount), strDocumentUrl(strDocumentUrl), documentHash(documentHash), vchData(vchData), strDataFilename(strDataFilename) { }; void SetNull() { strName = ""; + nMintAmount = 0; strDocumentUrl = ""; documentHash = uint256(); vchData.clear(); + strDataFilename = ""; } ADD_SERIALIZE_METHODS; @@ -187,26 +191,32 @@ class CTokenGroupDescriptionNFT { READWRITE(nVersion); READWRITE(strName); + READWRITE(nMintAmount); READWRITE(strDocumentUrl); READWRITE(documentHash); READWRITE(vchData); + READWRITE(strDataFilename); } void ToJson(UniValue& obj) const; void WriteHashable(CHashWriter& ss) const { ss << nVersion; ss << strName; + ss << nMintAmount; ss << strDocumentUrl; ss << documentHash; ss << vchData; + ss << strDataFilename; } bool operator==(const CTokenGroupDescriptionNFT &c) { return (strName == c.strName && + nMintAmount == c.nMintAmount && strDocumentUrl == c.strDocumentUrl && documentHash == c.documentHash && - vchData == c.vchData); + vchData == c.vchData && + strDataFilename == c.strDataFilename); } }; @@ -298,7 +308,7 @@ inline uint8_t tgDescGetDecimalPos(CTokenGroupDescriptionVariant& tgDesc) { } // Tokens with no fractional quantities have nDecimalPos=0 -// Wagerr has has decimalpos=8 (1 WAGERR is 100000000 satoshi) +// Wagerr has has decimalPos=8 (1 WAGERR is 100000000 satoshi) // Maximum value is 10^16 class tgdesc_get_coin_amount : public boost::static_visitor { diff --git a/src/tokens/tokengroupdocument.cpp b/src/tokens/tokengroupdocument.cpp index f77b98f612af06..de9b9e74be7b1b 100644 --- a/src/tokens/tokengroupdocument.cpp +++ b/src/tokens/tokengroupdocument.cpp @@ -2,41 +2,196 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include "tokens/tokengroupdocument.h" -#include "util.h" +#include + +#include +#include +#include #include #include #include -void CTokenGroupDocument::ToJson(UniValue& obj) const +// Sets the json object, returns status +bool CTokenGroupDocument::LoadJSONData() +{ + bool res = false; + if (!fRawLoaded) { + fUnparsable = true; + return false; + } + + try { + // ATTEMPT TO LOAD JSON STRING FROM VCHDATA + res = GetJSONFromRawData(data); + LogPrint(BCLog::TOKEN, "CTokenGroupDocument::LoadJSONData -- GetDataAsPlainString = %s\n", GetDataAsPlainString()); + } catch (std::exception& e) { + std::ostringstream ostr; + ostr << "CTokenGroupDocument::LoadJSONData Error parsing JSON" + << ", e.what() = " << e.what(); + LogPrintf("%s\n", ostr.str()); + return false; + } catch (...) { + std::ostringstream ostr; + ostr << "CTokenGroupDocument::LoadJSONData Unknown Error parsing JSON"; + LogPrintf("%s\n", ostr.str()); + return false; + } + return res; +} + +// GetJSONFromRawData loads vchData into objResult. Throws an exception on failure. +bool CTokenGroupDocument::GetJSONFromRawData(UniValue& objResult) { - obj.clear(); - obj.setObject(); - obj.pushKV("atp", nVersion); - UniValue dataObj(UniValue::VOBJ); - dataObj.pushKV("ticker", strTicker); - dataObj.pushKV("name", strName); - dataObj.pushKV("chain", strChain); - dataObj.pushKV("summary", strSummary); - dataObj.pushKV("description", strDescription); - dataObj.pushKV("creator", strCreator); - UniValue dataContactObj(UniValue::VOBJ); - dataContactObj.pushKV("url", strContactURL); - dataContactObj.pushKV("email", strContactEmail); - dataObj.pushKV("contact", dataContactObj); - obj.pushKV("data", dataObj); - obj.pushKV("hash", GetSignatureHash().GetHex()); - obj.pushKV("signature", HexStr(vchSig)); + UniValue o(UniValue::VOBJ); + std::string s = GetDataAsPlainString(); + bool res = o.read(s); + objResult = o; + return res; +} + +std::string CTokenGroupDocument::GetDataAsHexString() const +{ + return HexStr(vchData); +} + +std::string CTokenGroupDocument::GetDataAsPlainString() const +{ + return std::string(vchData.begin(), vchData.end()); +} + +std::vector CTokenGroupDocument::GetRawDataFromJson() const +{ + std::string strPlainText = data.write(true, 2); + return std::vector(strPlainText.begin(), strPlainText.end()); +} + + +bool CTokenGroupDocument::ParseATPParams() { + bool res = false; + + const UniValue& atp = data["atp"]; + const UniValue& jsonVersion = find_value(atp, "version"); + + if (!jsonVersion.isNum() && !jsonVersion.isStr()) + throw JSONRPCError(RPC_TYPE_ERROR, "Version is not a number or string"); + + int64_t nVersionIn; + if (!ParseFixedPoint(jsonVersion.getValStr(), 0, &nVersionIn)) + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid version"); + + if (nVersionIn < 0 || nVersionIn > std::numeric_limits::max()) { + throw JSONRPCError(RPC_TYPE_ERROR, "Version out of range"); + } + nVersion = nVersionIn; + + std::string strType = atp["type"].get_str(); + if (strType == "regular") { + nSpecialTxType = TRANSACTION_GROUP_CREATION_REGULAR; + } else if (strType == "management") { + nSpecialTxType = TRANSACTION_GROUP_CREATION_MGT; + } else if (strType == "nft") { + nSpecialTxType = TRANSACTION_GROUP_CREATION_NFT; + } else { + std::string err = strprintf("Invalid token type %s", strType); + throw JSONRPCError(RPC_TYPE_ERROR, err); + } + return true; } -uint256 CTokenGroupDocument::GetHash() const +void CTokenGroupDocument::ToJson(UniValue& obj) const { - return SerializeHash(*this); + obj = data; +} + +bool CTokenGroupDocument::ValidateData() const { + if (!fParsed || !fJsonLoaded) { + return false; + } + switch (nSpecialTxType) + { + case TRANSACTION_GROUP_CREATION_REGULAR: + case TRANSACTION_GROUP_CREATION_MGT: + // The following fields must be present - other fields are also allowed + RPCTypeCheckObj(data, + { + {"atp", UniValueType(UniValue::VOBJ)}, + {"ticker", UniValueType(UniValue::VSTR)}, + {"name", UniValueType(UniValue::VSTR)}, + {"chain", UniValueType(UniValue::VSTR)}, + {"creator", UniValueType(UniValue::VSTR)}, + {"description", UniValueType(UniValue::VSTR)}, + {"attributes_url", UniValueType(UniValue::VSTR)} + }, + false, false); + + // If following optional fields are present, they must have these types + RPCTypeCheckObj(data, + { + {"external_url", UniValueType(UniValue::VSTR)}, + {"image", UniValueType(UniValue::VSTR)}, + {"summary", UniValueType(UniValue::VSTR)}, + {"attributes", UniValueType(UniValue::VARR)}, + {"properties", UniValueType(UniValue::VARR)}, + {"localization", UniValueType(UniValue::VOBJ)} + }, + true, false); + + break; + case TRANSACTION_GROUP_CREATION_NFT: + // The following fields must be present - other fields are also allowed + RPCTypeCheckObj(data, + { + {"atp", UniValueType(UniValue::VOBJ)}, + {"name", UniValueType(UniValue::VSTR)}, + {"chain", UniValueType(UniValue::VSTR)}, + {"creator", UniValueType(UniValue::VSTR)}, + {"description", UniValueType(UniValue::VSTR)}, + {"attributes_url", UniValueType(UniValue::VSTR)} + }, + false, false); + + // If following optional fields are present, they must have these types + RPCTypeCheckObj(data, + { + {"external_url", UniValueType(UniValue::VSTR)}, + {"image", UniValueType(UniValue::VSTR)}, + {"attributes", UniValueType(UniValue::VARR)}, + {"properties", UniValueType(UniValue::VARR)}, + {"localization", UniValueType(UniValue::VOBJ)} + }, + true, false); + + break; + + default: + return false; + break; + } + + RPCTypeCheckObj(data["atp"], + { + {"version", UniValueType()}, // Checked explicitly in ParseATPParams() + {"type", UniValueType(UniValue::VSTR)}, + }, + false, true); + + if (data.exists("localization")) { + UniValue localization = data["localization"]; + RPCTypeCheckObj(localization, + { + {"uri", UniValueType(UniValue::VSTR)}, + {"default", UniValueType(UniValue::VSTR)}, + {"locales", UniValueType(UniValue::VARR)}, + }, + false, true); + } + + return true; } uint256 CTokenGroupDocument::GetSignatureHash() const { - return GetHash(); + return SerializeHash(vchData); } bool CTokenGroupDocument::Sign(const CKey& key) @@ -94,3 +249,7 @@ bool CTokenGroupDocument::GetSignerKeyID(CKeyID &retKeyidSporkSigner) const void CTokenGroupDocument::SetSignature(const std::string& strSignature) { vchSig = ParseHex(strSignature); } + +std::string CTokenGroupDocument::GetSignature() { + return HexStr(vchSig); +} \ No newline at end of file diff --git a/src/tokens/tokengroupdocument.h b/src/tokens/tokengroupdocument.h index b35839d52d6f95..6af80ae6f75af6 100644 --- a/src/tokens/tokengroupdocument.h +++ b/src/tokens/tokengroupdocument.h @@ -6,8 +6,7 @@ #define TOKEN_GROUP_DOCUMENT #include - -class UniValue; +#include class CTokenGroupDocument { @@ -16,49 +15,79 @@ class CTokenGroupDocument private: uint16_t nVersion{CURRENT_VERSION}; + int nSpecialTxType; // data - std::string strTicker; - std::string strName; - std::string strChain; - std::string strSummary; - std::string strDescription; - std::string strCreator; - std::string strContactURL; - std::string strContactEmail; + UniValue data; + std::vector vchData; + // signature data std::vector vchSig; + /// Failed to parse object data + bool fUnparsable; + bool fJsonLoaded; + bool fRawLoaded; + bool fParsed; + bool fValidated; + public: - CTokenGroupDocument() { - SetNull(); + CTokenGroupDocument() : nVersion(CURRENT_VERSION), nSpecialTxType(), vchData(), vchSig(), fUnparsable(false), fJsonLoaded(false), fRawLoaded(false), fParsed(false), fValidated(false) { + data = UniValue(UniValue::VOBJ); + }; + + CTokenGroupDocument(const std::vector& vchDataIn) : + nVersion(CURRENT_VERSION), nSpecialTxType(nSpecialTxType), vchData(vchDataIn), vchSig(), fUnparsable(false), fJsonLoaded(false), fRawLoaded(true), fParsed(false), fValidated(false) { + fJsonLoaded = LoadJSONData(); + if (!fJsonLoaded) { + fUnparsable = true; + } + if (!ParseATPParams()) { + fUnparsable = true; + return; + } + fParsed = true; + vchData = GetRawDataFromJson(); + fRawLoaded = true; + + if (!ValidateData()) { + return; + } + fValidated = true; + }; + + CTokenGroupDocument(const UniValue& data) : + nVersion(CURRENT_VERSION), nSpecialTxType(), data(data), vchData(), vchSig(), fUnparsable(false), fJsonLoaded(true), fRawLoaded(false), fParsed(false) { + if (!ParseATPParams()) { + fUnparsable = true; + return; + } + fParsed = true; + vchData = GetRawDataFromJson(); + fRawLoaded = true; + + if (!ValidateData()) { + return; + } + fValidated = true; }; - CTokenGroupDocument(std::string strTicker, std::string strName, std::string strChain, std::string strSummary, - std::string strDescription, std::string strCreator, std::string strContactURL, std::string strContactEmail) : - strTicker(strTicker), strName(strName), strChain(strChain), strSummary(strSummary), strDescription(strDescription), - strCreator(strCreator), strContactURL(strContactURL), strContactEmail(strContactEmail) {}; void SetNull() { - strTicker = ""; - strName = ""; - strChain = ""; - strSummary = ""; - strDescription = ""; - strCreator = ""; - strContactURL = ""; - strContactEmail = ""; + data = UniValue(UniValue::VOBJ); + vchData.clear(); vchSig.clear(); + fUnparsable = false; + nSpecialTxType = 0; } /** - * GetHash returns the double-sha256 hash of the serialized item. + * ValidateData will ensure that the json data matches the required format */ - uint256 GetHash() const; + bool ValidateData() const; /** - * GetSignatureHash returns the hash of the serialized item - * without the signature included. The intent of this method is to get the - * hash to be signed. + * GetSignatureHash returns the hash of the data's text representation. + * The intent of this method is to get the hash to be signed. */ uint256 GetSignatureHash() const; @@ -84,39 +113,23 @@ class CTokenGroupDocument */ void SetSignature(const std::string& strSignature); + /** + * GetSignature is used to get the signature data as a string + */ + std::string GetSignature(); + // FUNCTIONS FOR DEALING WITH DATA STRING std::string GetDataAsHexString() const; std::string GetDataAsPlainString() const; + std::vector GetRawDataFromJson() const; - // SERIALIZER + bool LoadJSONData(); + bool GetJSONFromRawData(UniValue& objResult); - ADD_SERIALIZE_METHODS; - - template - inline void SerializationOp(Stream& s, Operation ser_action) - { - if (!(s.GetType() & SER_GETHASH)) { - READWRITE(nVersion); - } - READWRITE(strTicker); - READWRITE(strName); - READWRITE(strChain); - READWRITE(strSummary); - READWRITE(strDescription); - READWRITE(strCreator); - READWRITE(strContactURL); - READWRITE(strContactEmail); - if (!(s.GetType() & SER_GETHASH)) { - READWRITE(vchSig); - } - } + bool ParseATPParams(); void ToJson(UniValue& obj) const; - - // FUNCTIONS FOR DEALING WITH DATA STRING - void LoadData(); - void GetData(UniValue& objResult); }; #endif \ No newline at end of file diff --git a/src/validation.cpp b/src/validation.cpp index 43f6d134a82876..513fae7aaa5ffc 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -1455,6 +1456,19 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi } } } + for (std::pair mintMeltItem : tgMintMeltBalance) { + if (mintMeltItem.first.hasFlag(TokenGroupIdFlags::NFT_TOKEN) && mintMeltItem.second.output > 0) { + CTokenGroupCreation tgCreation; + if (!tokenGroupManager.get()->GetTokenGroupCreation(mintMeltItem.first, tgCreation)) { + return state.DoS(0, error("Unable to find token group %s", EncodeTokenGroup(mintMeltItem.first)), REJECT_INVALID, "op_group-bad-mint"); + } + CTokenGroupDescriptionNFT *tgDesc = boost::get(tgCreation.pTokenGroupDescription.get()); + if (tgDesc->nMintAmount != (mintMeltItem.second.output - mintMeltItem.second.input)) { + return state.DoS(0, error("NFT mints the wrong amount (%d instead of %d)", + (mintMeltItem.second.output - mintMeltItem.second.input), tgDesc->nMintAmount), REJECT_INVALID, "op_group-bad-mint"); + } + } + } } if (pvChecks)