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 e15cdbeb9..5b6c148e8 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 ) { @@ -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; if (strcmp(ASSETCHAINS_SYMBOL, "ROGUE") == 0 && chainActive.Height() <= 12500) return true; @@ -258,7 +258,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) @@ -271,6 +271,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 @@ -446,8 +458,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 @@ -490,7 +501,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 @@ -552,7 +563,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"))); @@ -768,7 +779,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 } @@ -791,6 +801,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() total burned=" << burnedAmount << " 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) { @@ -828,7 +910,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/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); diff --git a/src/cc/rogue_rpc.cpp b/src/cc/rogue_rpc.cpp index 3b615aabf..75966ce70 100644 --- a/src/cc/rogue_rpc.cpp +++ b/src/cc/rogue_rpc.cpp @@ -747,6 +747,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); + 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); 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..51d7cb6ad 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -7818,3 +7818,45 @@ 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, 0)); + 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); + + 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); + GetCCaddress(cp, unspendableTokenAddr, unspPk); + CCaddr2set(cp, EVAL_TOKENS, unspPk, tokenpriv, unspendableTokenAddr); + return(FinalizeCCTx(0, cp, mtx, myPubkey, 10000, EncodeTokenOpRet(tokenid, voutPubkeys, CScript()))); +}