Skip to content

Commit

Permalink
Update NFT system
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
celbalrai authored and wagerr-builder committed May 15, 2022
1 parent f9187a3 commit 9083d24
Show file tree
Hide file tree
Showing 10 changed files with 537 additions and 449 deletions.
14 changes: 11 additions & 3 deletions src/consensus/tokengroups.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)) &
Expand Down
5 changes: 2 additions & 3 deletions src/rpc/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand Down
346 changes: 146 additions & 200 deletions src/tokens/rpctokens.cpp

Large diffs are not rendered by default.

202 changes: 54 additions & 148 deletions src/tokens/rpctokenwallet.cpp

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/tokens/tokengroupconfiguration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ template void TGFilterTickerCharacters(CTokenGroupDescriptionMGT& tgDesc);

template <typename T>
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;

Expand Down
59 changes: 46 additions & 13 deletions src/tokens/tokengroupdescription.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand All @@ -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());
}
Expand All @@ -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) {
Expand All @@ -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++;
Expand Down Expand Up @@ -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++;
Expand Down Expand Up @@ -151,6 +151,37 @@ std::vector<unsigned char> 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<CTokenGroupDescriptionRegular>& tgDesc, bool &confirmed)
{
std::string strCurparamValue;
Expand All @@ -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<CTokenGroupDescriptionRegular>(strTicker, strName, nDecimalPos, strDocumentUrl, documentHash);

Expand All @@ -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<CTokenGroupDescriptionMGT>(strTicker, strName, nDecimalPos, strDocumentUrl, documentHash, blsPubKey);
Expand Down Expand Up @@ -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<unsigned char> vchData = ConsumeParamNFTData(request, curparam);
std::string strDataFilename = ConsumeParamFilename(request, curparam);

tgDesc = std::make_shared<CTokenGroupDescriptionNFT>(strName, strDocumentUrl, documentHash, vchData);
tgDesc = std::make_shared<CTokenGroupDescriptionNFT>(strName, nMintAmount, strDocumentUrl, documentHash, vchData, strDataFilename);

if (curparam >= request.params.size())
{
Expand Down
18 changes: 14 additions & 4 deletions src/tokens/tokengroupdescription.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<unsigned char> vchData;
std::string strDataFilename; // File name for the data

CTokenGroupDescriptionNFT() {
SetNull();
};
CTokenGroupDescriptionNFT(std::string strName, std::string strDocumentUrl, uint256 documentHash, std::vector<unsigned char> vchData) :
strName(strName), strDocumentUrl(strDocumentUrl), documentHash(documentHash), vchData(vchData) { };
CTokenGroupDescriptionNFT(std::string strName, uint64_t nMintAmount, std::string strDocumentUrl, uint256 documentHash, std::vector<unsigned char> 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;
Expand All @@ -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);
}
};

Expand Down Expand Up @@ -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<CAmount>
{
Expand Down
Loading

0 comments on commit 9083d24

Please sign in to comment.