From 73c33eb2a3e383f6b91f649aa5f8e2b037b19b17 Mon Sep 17 00:00:00 2001 From: dimxy Date: Mon, 11 Feb 2019 22:53:08 +0500 Subject: [PATCH 1/9] added burned non-fungible tokens validation --- src/cc/CCtokens.cpp | 96 +++++++++++++++++++++++++++++++++++++++++---- src/cc/CCtokens.h | 2 + 2 files changed, 91 insertions(+), 7 deletions(-) diff --git a/src/cc/CCtokens.cpp b/src/cc/CCtokens.cpp index eb802b65d..bcebb1a09 100644 --- a/src/cc/CCtokens.cpp +++ b/src/cc/CCtokens.cpp @@ -236,7 +236,7 @@ bool TokensValidate(struct CCcontract_info *cp, Eval* eval, const CTransaction & std::vector vopretExtra, tmporigpubkey, ignorepubkey; uint8_t funcid, evalCodeInOpret; char destaddr[64], origaddr[64], CCaddr[64]; - std::vector voutTokenPubkeys; + std::vector voutTokenPubkeys, vinTokenPubkeys; //return true; @@ -257,7 +257,7 @@ bool TokensValidate(struct CCcontract_info *cp, Eval* eval, const CTransaction & if (eval->GetTxUnconfirmed(tokenid, createTx, hashBlock) == 0) return eval->Invalid("cant find token create txid"); //else if (IsCCInput(tx.vin[0].scriptSig) != 0) - // return eval->Invalid("illegal token vin0"); // this validation was removed because some token tx might not have normal vins + // return eval->Invalid("illegal token vin0"); // <-- this validation was removed because some token tx might not have normal vins else if (funcid != 'c') { if (tokenid == zeroid) @@ -270,6 +270,18 @@ bool TokensValidate(struct CCcontract_info *cp, Eval* eval, const CTransaction & } } + // validate spending from token cc addr: allowed only for burned non-fungible tokens: + if (ExtractTokensVinPubkeys(tx, vinTokenPubkeys) && std::find(vinTokenPubkeys.begin(), vinTokenPubkeys.end(), GetUnspendable(cp, NULL)) != vinTokenPubkeys.end()) { + // validate spending from token unspendable cc addr: + int64_t burnedAmount = HasBurnedTokensvouts(cp, eval, tx, tokenid); + if (burnedAmount > 0) { + std::vector vopretNonfungible; + GetNonfungibleData(tokenid, vopretNonfungible); + if( vopretNonfungible.empty() ) + return eval->Invalid("spending cc marker not supported for fungible tokens"); + } + } + switch (funcid) { case 'c': // create wont be called to be verified as it has no CC inputs @@ -445,8 +457,7 @@ int64_t IsTokensvout(bool goDeeper, bool checkPubkeys /*<--not used, always true return(0); } - //TODO: validate cc vouts are EVAL_TOKENS! - if (tx.vout[v].scriptPubKey.IsPayToCryptoCondition()) // maybe check address too? dimxy: possibly no, because there are too many cases with different addresses here + if (tx.vout[v].scriptPubKey.IsPayToCryptoCondition()) { if (goDeeper) { //validate all tx @@ -489,7 +500,7 @@ int64_t IsTokensvout(bool goDeeper, bool checkPubkeys /*<--not used, always true GetNonfungibleData(reftokenid, vopretNonfungible); - voutPubkeys = FilterOutTokensUnspendablePk(voutPubkeysInOpret); + voutPubkeys = FilterOutTokensUnspendablePk(voutPubkeysInOpret); // cannot send tokens to token unspendable cc addr (only marker is allowed there) // NOTE: evalcode order in vouts is important: // non-fungible-eval -> EVAL_TOKENS -> assets-eval @@ -551,7 +562,7 @@ int64_t IsTokensvout(bool goDeeper, bool checkPubkeys /*<--not used, always true // maybe it is single-eval or dual/three-eval token change? std::vector vinPubkeys, vinPubkeysUnfiltered; ExtractTokensVinPubkeys(tx, vinPubkeysUnfiltered); - vinPubkeys = FilterOutTokensUnspendablePk(vinPubkeysUnfiltered); + vinPubkeys = FilterOutTokensUnspendablePk(vinPubkeysUnfiltered); // cannot send tokens to token unspendable cc addr (only marker is allowed there) for(std::vector::iterator it = vinPubkeys.begin(); it != vinPubkeys.end(); it++) { testVouts.push_back(std::make_pair(MakeCC1vout(EVAL_TOKENS, tx.vout[v].nValue, *it), std::string("single-eval cc1 self vin pk"))); @@ -767,7 +778,6 @@ int64_t AddTokenCCInputs(struct CCcontract_info *cp, CMutableTransaction &mtx, C LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << "AddTokenCCInputs() found incorrect non-fungible opret payload for vintxid=" << vintxid.GetHex() << std::endl); continue; } - // non-fungible evalCode2 cc contract should also check if there exists only one non-fungible vout with amount = 1 } @@ -790,6 +800,78 @@ int64_t AddTokenCCInputs(struct CCcontract_info *cp, CMutableTransaction &mtx, C return(totalinputs); } +// checks if any token vouts are sent to 'dead' pubkey +int64_t HasBurnedTokensvouts(struct CCcontract_info *cp, Eval* eval, const CTransaction& tx, uint256 reftokenid) +{ + uint8_t dummyEvalCode; + uint256 tokenIdOpret; + std::vector voutPubkeys, voutPubkeysDummy; + std::vector vopretExtra, vopretNonfungible; + + uint8_t evalCode = EVAL_TOKENS; // if both payloads are empty maybe it is a transfer to non-payload-one-eval-token vout like GatewaysClaim + uint8_t evalCode2 = 0; // will be checked if zero or not + + // test vouts for possible token use-cases: + std::vector> testVouts; + + int32_t n = tx.vout.size(); + // just check boundaries: + if (n == 0) { + LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << "HasBurnedTokensvouts() incorrect params: tx.vout.size() == 0, txid=" << tx.GetHash().GetHex() << std::endl); + return(0); + } + + + if (DecodeTokenOpRet(tx.vout.back().scriptPubKey, dummyEvalCode, tokenIdOpret, voutPubkeysDummy, vopretExtra) == 0) { + LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << "HasBurnedTokensvouts() cannot parse opret DecodeTokenOpRet returned 0, txid=" << tx.GetHash().GetHex() << std::endl); + return 0; + } + + LOGSTREAM((char *)"cctokens", CCLOG_DEBUG2, stream << "HasBurnedTokensvouts() vopretExtra=" << HexStr(vopretExtra) << std::endl); + + GetNonfungibleData(reftokenid, vopretNonfungible); + + if (vopretNonfungible.size() > 0) + evalCode = vopretNonfungible.begin()[0]; + if (vopretExtra.size() > 0) + evalCode2 = vopretExtra.begin()[0]; + + if (evalCode == EVAL_TOKENS && evalCode2 != 0) { + evalCode = evalCode2; + evalCode2 = 0; + } + + voutPubkeys.push_back(pubkey2pk(ParseHex(CC_BURNPUBKEY))); + + int64_t burnedAmount = 0; + + for (int i = 0; i < tx.vout.size(); i++) { + + if (tx.vout[i].scriptPubKey.IsPayToCryptoCondition()) + { + // make all possible token vouts for dead pk: + for (std::vector::iterator it = voutPubkeys.begin(); it != voutPubkeys.end(); it++) { + testVouts.push_back(std::make_pair(MakeCC1vout(EVAL_TOKENS, tx.vout[i].nValue, *it), std::string("single-eval cc1 burn pk"))); + testVouts.push_back(std::make_pair(MakeTokensCC1vout(evalCode, evalCode2, tx.vout[i].nValue, *it), std::string("three-eval cc1 burn pk"))); + + if (evalCode2 != 0) + // also check in backward evalcode order: + testVouts.push_back(std::make_pair(MakeTokensCC1vout(evalCode2, evalCode, tx.vout[i].nValue, *it), std::string("three-eval cc1 burn pk backward-eval"))); + } + + // try all test vouts: + for (auto t : testVouts) { + if (t.first == tx.vout[i]) { + LOGSTREAM((char *)"cctokens", CCLOG_DEBUG1, stream << "HasBurnedTokensvouts() burned amount=" << tx.vout[i].nValue << " msg=" << t.second << " evalCode=" << (int)evalCode << " evalCode2=" << (int)evalCode2 << " txid=" << tx.GetHash().GetHex() << " tokenid=" << reftokenid.GetHex() << std::endl); + burnedAmount += tx.vout[i].nValue; + } + } + LOGSTREAM((char *)"cctokens", CCLOG_DEBUG2, stream << "HasBurnedTokensvouts() no burned vouts evalCode=" << (int)evalCode << " evalCode2=" << (int)evalCode2 << " for txid=" << tx.GetHash().GetHex() << " for tokenid=" << reftokenid.GetHex() << std::endl); + } + } + + return burnedAmount; +} std::string CreateToken(int64_t txfee, int64_t tokensupply, std::string name, std::string description, std::vector nonfungibleData) { diff --git a/src/cc/CCtokens.h b/src/cc/CCtokens.h index 231b64ac9..791107648 100644 --- a/src/cc/CCtokens.h +++ b/src/cc/CCtokens.h @@ -30,6 +30,8 @@ bool TokensValidate(struct CCcontract_info *cp,Eval* eval,const CTransaction &tx bool TokensExactAmounts(bool goDeeper, struct CCcontract_info *cpTokens, int64_t &inputs, int64_t &outputs, Eval* eval, const CTransaction &tx, uint256 tokenid); std::string CreateToken(int64_t txfee, int64_t assetsupply, std::string name, std::string description, std::vector nonfungibleData); std::string TokenTransfer(int64_t txfee, uint256 assetid, std::vector destpubkey, int64_t total); +bool ExtractTokensVinPubkeys(CTransaction tx, std::vector &vinPubkeys); +int64_t HasBurnedTokensvouts(struct CCcontract_info *cp, Eval* eval, const CTransaction& tx, uint256 reftokenid); int64_t GetTokenBalance(CPubKey pk, uint256 tokenid); UniValue TokenInfo(uint256 tokenid); From 672b3939f6eafed26ae00bf2047e603c88eb66a0 Mon Sep 17 00:00:00 2001 From: dimxy Date: Mon, 11 Feb 2019 23:27:39 +0500 Subject: [PATCH 2/9] added cc marker spending in rogue_register --- src/cc/CCtokens.cpp | 2 +- src/cc/rogue_rpc.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cc/CCtokens.cpp b/src/cc/CCtokens.cpp index bcebb1a09..cb8c65342 100644 --- a/src/cc/CCtokens.cpp +++ b/src/cc/CCtokens.cpp @@ -909,7 +909,7 @@ std::string CreateToken(int64_t txfee, int64_t tokensupply, std::string name, st mtx.vout.push_back(MakeTokensCC1vout(destEvalCode, tokensupply, mypk)); //mtx.vout.push_back(CTxOut(txfee, CScript() << ParseHex(cp->CChexstr) << OP_CHECKSIG)); // old marker (non-burnable because spending could not be validated) // NOTE: we should prevent spending fake-tokens from this marker in IsTokenvout(): - mtx.vout.push_back(MakeCC1vout(EVAL_TOKENS, txfee, GetUnspendable(cp, NULL))); // new marker to token cc addr, burnable and validated + mtx.vout.push_back(MakeCC1vout(EVAL_TOKENS, txfee, GetUnspendable(cp, NULL))); // new marker to token cc addr, burnable and validated, this vout must be=1 return(FinalizeCCTx(0, cp, mtx, mypk, txfee, EncodeTokenCreateOpRet('c', Mypubkey(), name, description, nonfungibleData))); } diff --git a/src/cc/rogue_rpc.cpp b/src/cc/rogue_rpc.cpp index e6ccc3352..14d6f4a09 100644 --- a/src/cc/rogue_rpc.cpp +++ b/src/cc/rogue_rpc.cpp @@ -712,6 +712,7 @@ UniValue rogue_register(uint64_t txfee,struct CCcontract_info *cp,cJSON *params) return(cclib_error(result,"couldnt find enough normal funds for buyin")); if ( playertxid != zeroid ) AddNormalinputs2(mtx,txfee,10); + mtx.vin.push_back(CTxIn(playertxid, 1)); // spending cc marker as token is being burned mtx.vout.push_back(MakeCC1of2vout(cp->evalcode,buyin + inputsum - txfee,roguepk,mypk)); GetCCaddress1of2(cp,destaddr,roguepk,roguepk); CCaddr1of2set(cp,roguepk,roguepk,cp->CCpriv,destaddr); From cb0db91e9fbc4a327efd2f3f40ac987095c5aa30 Mon Sep 17 00:00:00 2001 From: dimxy Date: Mon, 11 Feb 2019 23:31:37 +0500 Subject: [PATCH 3/9] added playertxid check for non zero --- src/cc/rogue_rpc.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cc/rogue_rpc.cpp b/src/cc/rogue_rpc.cpp index 14d6f4a09..8ee7a0528 100644 --- a/src/cc/rogue_rpc.cpp +++ b/src/cc/rogue_rpc.cpp @@ -712,7 +712,8 @@ UniValue rogue_register(uint64_t txfee,struct CCcontract_info *cp,cJSON *params) return(cclib_error(result,"couldnt find enough normal funds for buyin")); if ( playertxid != zeroid ) AddNormalinputs2(mtx,txfee,10); - mtx.vin.push_back(CTxIn(playertxid, 1)); // spending cc marker as token is being burned + if (playertxid != zeroid) + mtx.vin.push_back(CTxIn(playertxid, 1)); // spending cc marker as token is being burned mtx.vout.push_back(MakeCC1of2vout(cp->evalcode,buyin + inputsum - txfee,roguepk,mypk)); GetCCaddress1of2(cp,destaddr,roguepk,roguepk); CCaddr1of2set(cp,roguepk,roguepk,cp->CCpriv,destaddr); From 7a4072b4f1496f52f013d68d3ea1dd2a9679ba11 Mon Sep 17 00:00:00 2001 From: dimxy Date: Tue, 12 Feb 2019 00:14:08 +0500 Subject: [PATCH 4/9] test_burntx added --- src/rpc/server.cpp | 2 ++ src/rpc/server.h | 2 ++ src/wallet/rpcwallet.cpp | 30 ++++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 208b3cf02..77c714531 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -539,6 +539,8 @@ static const CRPCCommand vRPCCommands[] = { "hidden", "setmocktime", &setmocktime, true }, { "hidden", "test_ac", &test_ac, true }, { "hidden", "test_heirmarker", &test_heirmarker, true }, + { "hidden", "test_burntx", &test_burntx, true }, + #ifdef ENABLE_WALLET /* Wallet */ diff --git a/src/rpc/server.h b/src/rpc/server.h index b812ccc9d..9682cbf5c 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -474,5 +474,7 @@ extern UniValue paxwithdraw(const UniValue& params, bool fHelp); // test rpc: extern UniValue test_ac(const UniValue& params, bool fHelp); extern UniValue test_heirmarker(const UniValue& params, bool fHelp); +extern UniValue test_burntx(const UniValue& params, bool fHelp); + #endif // BITCOIN_RPCSERVER_H diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 0087abf58..f20b1cb5c 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -7818,3 +7818,33 @@ UniValue test_heirmarker(const UniValue& params, bool fHelp) return(FinalizeCCTx(0, cp, mtx, myPubkey, 10000, opret)); } +UniValue test_burntx(const UniValue& params, bool fHelp) +{ + // make fake token tx: + struct CCcontract_info *cp, C; + + if (fHelp || (params.size() != 1)) + throw runtime_error("incorrect params\n"); + if (ensure_CCrequirements(EVAL_TOKENS) < 0) + throw runtime_error("to use CC contracts, you need to launch daemon with valid -pubkey= for an address in your wallet\n"); + + uint256 tokenid = Parseuint256((char *)params[0].get_str().c_str()); + + CPubKey myPubkey = pubkey2pk(Mypubkey()); + CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); + + int64_t normalInputs = AddNormalinputs(mtx, myPubkey, 10000, 60); + if (normalInputs < 10000) + throw runtime_error("not enough normals\n"); + + CPubKey burnpk = pubkey2pk(ParseHex(CC_BURNPUBKEY)); + + mtx.vin.push_back(CTxIn(tokenid, 1)); + mtx.vout.push_back(MakeTokensCC1vout(EVAL_TOKENS, 1, burnpk)); + + std::vector voutPubkeys; + voutPubkeys.push_back(burnpk); + + cp = CCinit(&C, EVAL_TOKENS); + return(FinalizeCCTx(0, cp, mtx, myPubkey, 10000, EncodeTokenOpRet(tokenid, voutPubkeys, CScript()))); +} From 1754fb8821a73fc057d000a9f1ca64b0a100315c Mon Sep 17 00:00:00 2001 From: dimxy Date: Tue, 12 Feb 2019 00:47:19 +0500 Subject: [PATCH 5/9] added CCaddr2set for test_burntx --- src/wallet/rpcwallet.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index f20b1cb5c..e10ba621c 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -7846,5 +7846,11 @@ UniValue test_burntx(const UniValue& params, bool fHelp) voutPubkeys.push_back(burnpk); cp = CCinit(&C, EVAL_TOKENS); + + uint8_t tokenpriv[33]; + char unspendableTokenAddr[64]; + CPubKey unspPk = GetUnspendable(cp, tokenpriv); + GetCCaddress(cp, unspendableTokenAddr, unspPk); + CCaddr2set(cp, EVAL_TOKENS, unspPk, tokenpriv, unspendableTokenAddr); return(FinalizeCCTx(0, cp, mtx, myPubkey, 10000, EncodeTokenOpRet(tokenid, voutPubkeys, CScript()))); } From e15764f36293ac7a1bdc8b92a86b7ab3bfeb0d24 Mon Sep 17 00:00:00 2001 From: dimxy Date: Tue, 12 Feb 2019 01:20:21 +0500 Subject: [PATCH 6/9] added vin for vout0 in test_burntx --- src/wallet/rpcwallet.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index e10ba621c..a8bcf2660 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -7839,6 +7839,7 @@ UniValue test_burntx(const UniValue& params, bool fHelp) CPubKey burnpk = pubkey2pk(ParseHex(CC_BURNPUBKEY)); + mtx.vin.push_back(CTxIn(tokenid, 0)); mtx.vin.push_back(CTxIn(tokenid, 1)); mtx.vout.push_back(MakeTokensCC1vout(EVAL_TOKENS, 1, burnpk)); From 0a9f2e0b8e3ea2f41e0791fda7ffd285cb18c902 Mon Sep 17 00:00:00 2001 From: dimxy Date: Tue, 12 Feb 2019 01:29:49 +0500 Subject: [PATCH 7/9] added additionalEvalcode2 in test_burntx --- src/wallet/rpcwallet.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index a8bcf2660..51d7cb6ad 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -7848,6 +7848,11 @@ UniValue test_burntx(const UniValue& params, bool fHelp) cp = CCinit(&C, EVAL_TOKENS); + std::vector vopret; + GetNonfungibleData(tokenid, vopret); + if (vopret.size() > 0) + cp->additionalTokensEvalcode2 = vopret.begin()[0]; + uint8_t tokenpriv[33]; char unspendableTokenAddr[64]; CPubKey unspPk = GetUnspendable(cp, tokenpriv); From 0b8fffc5010fbcb7b50cd2aca0c9d65dfd4ebda0 Mon Sep 17 00:00:00 2001 From: dimxy Date: Tue, 12 Feb 2019 01:40:59 +0500 Subject: [PATCH 8/9] logging corrected --- src/cc/CCtokens.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cc/CCtokens.cpp b/src/cc/CCtokens.cpp index cb8c65342..6e1afbf33 100644 --- a/src/cc/CCtokens.cpp +++ b/src/cc/CCtokens.cpp @@ -866,7 +866,7 @@ int64_t HasBurnedTokensvouts(struct CCcontract_info *cp, Eval* eval, const CTran burnedAmount += tx.vout[i].nValue; } } - LOGSTREAM((char *)"cctokens", CCLOG_DEBUG2, stream << "HasBurnedTokensvouts() no burned vouts evalCode=" << (int)evalCode << " evalCode2=" << (int)evalCode2 << " for txid=" << tx.GetHash().GetHex() << " for tokenid=" << reftokenid.GetHex() << std::endl); + LOGSTREAM((char *)"cctokens", CCLOG_DEBUG2, stream << "HasBurnedTokensvouts() total burned=" << burnedAmount << " evalCode=" << (int)evalCode << " evalCode2=" << (int)evalCode2 << " for txid=" << tx.GetHash().GetHex() << " for tokenid=" << reftokenid.GetHex() << std::endl); } } From 9fdb634a4ea378e4887cb73ea2b09c2e3c7dede5 Mon Sep 17 00:00:00 2001 From: dimxy Date: Tue, 12 Feb 2019 02:01:52 +0500 Subject: [PATCH 9/9] cc log levels corr --- src/cc/CCinclude.h | 9 +++++---- src/cc/CCtokens.cpp | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/cc/CCinclude.h b/src/cc/CCinclude.h index 6018114a0..a2d782c3f 100644 --- a/src/cc/CCinclude.h +++ b/src/cc/CCinclude.h @@ -266,6 +266,7 @@ UniValue ValueFromAmount(const CAmount& amount); #define CCLOG_DEBUG1 1 #define CCLOG_DEBUG2 2 #define CCLOG_DEBUG3 3 +#define CCLOG_MAXLEVEL 3 template inline void CCLogPrintStream(const char *category, int level, T print_to_stream) { @@ -273,10 +274,10 @@ inline void CCLogPrintStream(const char *category, int level, T print_to_stream) print_to_stream(stream); if (level < 0) level = 0; - if (level > 3) - level = 3; - for (int i = 0; i < level; i++) - if (LogAcceptCategory((std::string(category) + (level > 0 ? std::string("-") + std::to_string(level) : std::string(""))).c_str())) { + if (level > CCLOG_MAXLEVEL) + level = CCLOG_MAXLEVEL; + for (int i = level; i <= CCLOG_MAXLEVEL; i++) + if (LogAcceptCategory((std::string(category) + (i > 0 ? std::string("-") + std::to_string(i) : std::string(""))).c_str())) { LogPrintStr(stream.str()); break; } diff --git a/src/cc/CCtokens.cpp b/src/cc/CCtokens.cpp index 6e1afbf33..c6e2498cf 100644 --- a/src/cc/CCtokens.cpp +++ b/src/cc/CCtokens.cpp @@ -178,7 +178,7 @@ uint8_t DecodeTokenOpRet(const CScript scriptPubKey, uint8_t &evalCodeTokens, ui return (uint8_t)0; funcId = script[1]; - LOGSTREAM((char *)"cctokens", CCLOG_DEBUG2, stream << "DecodeTokenOpRet decoded funcId=" << (char)(funcId?funcId:' ')); + LOGSTREAM((char *)"cctokens", CCLOG_DEBUG2, stream << "DecodeTokenOpRet decoded funcId=" << (char)(funcId?funcId:' ') << std::endl); switch( funcId ) {