Merge pull request #1241 from dimxy/nonfungible-opret-fix
Burn support for non-fungible tokens
This commit is contained in:
@@ -266,6 +266,7 @@ UniValue ValueFromAmount(const CAmount& amount);
|
|||||||
#define CCLOG_DEBUG1 1
|
#define CCLOG_DEBUG1 1
|
||||||
#define CCLOG_DEBUG2 2
|
#define CCLOG_DEBUG2 2
|
||||||
#define CCLOG_DEBUG3 3
|
#define CCLOG_DEBUG3 3
|
||||||
|
#define CCLOG_MAXLEVEL 3
|
||||||
template <class T>
|
template <class T>
|
||||||
inline void CCLogPrintStream(const char *category, int level, T print_to_stream)
|
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);
|
print_to_stream(stream);
|
||||||
if (level < 0)
|
if (level < 0)
|
||||||
level = 0;
|
level = 0;
|
||||||
if (level > 3)
|
if (level > CCLOG_MAXLEVEL)
|
||||||
level = 3;
|
level = CCLOG_MAXLEVEL;
|
||||||
for (int i = 0; i < level; i++)
|
for (int i = level; i <= CCLOG_MAXLEVEL; i++)
|
||||||
if (LogAcceptCategory((std::string(category) + (level > 0 ? std::string("-") + std::to_string(level) : std::string(""))).c_str())) {
|
if (LogAcceptCategory((std::string(category) + (i > 0 ? std::string("-") + std::to_string(i) : std::string(""))).c_str())) {
|
||||||
LogPrintStr(stream.str());
|
LogPrintStr(stream.str());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ uint8_t DecodeTokenOpRet(const CScript scriptPubKey, uint8_t &evalCodeTokens, ui
|
|||||||
return (uint8_t)0;
|
return (uint8_t)0;
|
||||||
|
|
||||||
funcId = script[1];
|
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 )
|
switch( funcId )
|
||||||
{
|
{
|
||||||
@@ -236,7 +236,7 @@ bool TokensValidate(struct CCcontract_info *cp, Eval* eval, const CTransaction &
|
|||||||
std::vector<uint8_t> vopretExtra, tmporigpubkey, ignorepubkey;
|
std::vector<uint8_t> vopretExtra, tmporigpubkey, ignorepubkey;
|
||||||
uint8_t funcid, evalCodeInOpret;
|
uint8_t funcid, evalCodeInOpret;
|
||||||
char destaddr[64], origaddr[64], CCaddr[64];
|
char destaddr[64], origaddr[64], CCaddr[64];
|
||||||
std::vector<CPubKey> voutTokenPubkeys;
|
std::vector<CPubKey> voutTokenPubkeys, vinTokenPubkeys;
|
||||||
|
|
||||||
if (strcmp(ASSETCHAINS_SYMBOL, "ROGUE") == 0 && chainActive.Height() <= 12500)
|
if (strcmp(ASSETCHAINS_SYMBOL, "ROGUE") == 0 && chainActive.Height() <= 12500)
|
||||||
return true;
|
return true;
|
||||||
@@ -258,7 +258,7 @@ bool TokensValidate(struct CCcontract_info *cp, Eval* eval, const CTransaction &
|
|||||||
if (eval->GetTxUnconfirmed(tokenid, createTx, hashBlock) == 0)
|
if (eval->GetTxUnconfirmed(tokenid, createTx, hashBlock) == 0)
|
||||||
return eval->Invalid("cant find token create txid");
|
return eval->Invalid("cant find token create txid");
|
||||||
//else if (IsCCInput(tx.vin[0].scriptSig) != 0)
|
//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')
|
else if (funcid != 'c')
|
||||||
{
|
{
|
||||||
if (tokenid == zeroid)
|
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<uint8_t> vopretNonfungible;
|
||||||
|
GetNonfungibleData(tokenid, vopretNonfungible);
|
||||||
|
if( vopretNonfungible.empty() )
|
||||||
|
return eval->Invalid("spending cc marker not supported for fungible tokens");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch (funcid)
|
switch (funcid)
|
||||||
{
|
{
|
||||||
case 'c': // create wont be called to be verified as it has no CC inputs
|
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);
|
return(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: validate cc vouts are EVAL_TOKENS!
|
if (tx.vout[v].scriptPubKey.IsPayToCryptoCondition())
|
||||||
if (tx.vout[v].scriptPubKey.IsPayToCryptoCondition()) // maybe check address too? dimxy: possibly no, because there are too many cases with different addresses here
|
|
||||||
{
|
{
|
||||||
if (goDeeper) {
|
if (goDeeper) {
|
||||||
//validate all tx
|
//validate all tx
|
||||||
@@ -490,7 +501,7 @@ int64_t IsTokensvout(bool goDeeper, bool checkPubkeys /*<--not used, always true
|
|||||||
|
|
||||||
GetNonfungibleData(reftokenid, vopretNonfungible);
|
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:
|
// NOTE: evalcode order in vouts is important:
|
||||||
// non-fungible-eval -> EVAL_TOKENS -> assets-eval
|
// 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?
|
// maybe it is single-eval or dual/three-eval token change?
|
||||||
std::vector<CPubKey> vinPubkeys, vinPubkeysUnfiltered;
|
std::vector<CPubKey> vinPubkeys, vinPubkeysUnfiltered;
|
||||||
ExtractTokensVinPubkeys(tx, 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<CPubKey>::iterator it = vinPubkeys.begin(); it != vinPubkeys.end(); it++) {
|
for(std::vector<CPubKey>::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")));
|
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);
|
LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << "AddTokenCCInputs() found incorrect non-fungible opret payload for vintxid=" << vintxid.GetHex() << std::endl);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// non-fungible evalCode2 cc contract should also check if there exists only one non-fungible vout with amount = 1
|
// 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);
|
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<CPubKey> voutPubkeys, voutPubkeysDummy;
|
||||||
|
std::vector<uint8_t> 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<std::pair<CTxOut, std::string>> 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<CPubKey>::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<uint8_t> nonfungibleData)
|
std::string CreateToken(int64_t txfee, int64_t tokensupply, std::string name, std::string description, std::vector<uint8_t> 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(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)
|
//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():
|
// 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)));
|
return(FinalizeCCTx(0, cp, mtx, mypk, txfee, EncodeTokenCreateOpRet('c', Mypubkey(), name, description, nonfungibleData)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
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<uint8_t> nonfungibleData);
|
std::string CreateToken(int64_t txfee, int64_t assetsupply, std::string name, std::string description, std::vector<uint8_t> nonfungibleData);
|
||||||
std::string TokenTransfer(int64_t txfee, uint256 assetid, std::vector<uint8_t> destpubkey, int64_t total);
|
std::string TokenTransfer(int64_t txfee, uint256 assetid, std::vector<uint8_t> destpubkey, int64_t total);
|
||||||
|
bool ExtractTokensVinPubkeys(CTransaction tx, std::vector<CPubKey> &vinPubkeys);
|
||||||
|
int64_t HasBurnedTokensvouts(struct CCcontract_info *cp, Eval* eval, const CTransaction& tx, uint256 reftokenid);
|
||||||
|
|
||||||
int64_t GetTokenBalance(CPubKey pk, uint256 tokenid);
|
int64_t GetTokenBalance(CPubKey pk, uint256 tokenid);
|
||||||
UniValue TokenInfo(uint256 tokenid);
|
UniValue TokenInfo(uint256 tokenid);
|
||||||
|
|||||||
@@ -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"));
|
return(cclib_error(result,"couldnt find enough normal funds for buyin"));
|
||||||
if ( playertxid != zeroid )
|
if ( playertxid != zeroid )
|
||||||
AddNormalinputs2(mtx,txfee,10);
|
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));
|
mtx.vout.push_back(MakeCC1of2vout(cp->evalcode,buyin + inputsum - txfee,roguepk,mypk));
|
||||||
GetCCaddress1of2(cp,destaddr,roguepk,roguepk);
|
GetCCaddress1of2(cp,destaddr,roguepk,roguepk);
|
||||||
CCaddr1of2set(cp,roguepk,roguepk,cp->CCpriv,destaddr);
|
CCaddr1of2set(cp,roguepk,roguepk,cp->CCpriv,destaddr);
|
||||||
|
|||||||
@@ -539,6 +539,8 @@ static const CRPCCommand vRPCCommands[] =
|
|||||||
{ "hidden", "setmocktime", &setmocktime, true },
|
{ "hidden", "setmocktime", &setmocktime, true },
|
||||||
{ "hidden", "test_ac", &test_ac, true },
|
{ "hidden", "test_ac", &test_ac, true },
|
||||||
{ "hidden", "test_heirmarker", &test_heirmarker, true },
|
{ "hidden", "test_heirmarker", &test_heirmarker, true },
|
||||||
|
{ "hidden", "test_burntx", &test_burntx, true },
|
||||||
|
|
||||||
|
|
||||||
#ifdef ENABLE_WALLET
|
#ifdef ENABLE_WALLET
|
||||||
/* Wallet */
|
/* Wallet */
|
||||||
|
|||||||
@@ -474,5 +474,7 @@ extern UniValue paxwithdraw(const UniValue& params, bool fHelp);
|
|||||||
// test rpc:
|
// test rpc:
|
||||||
extern UniValue test_ac(const UniValue& params, bool fHelp);
|
extern UniValue test_ac(const UniValue& params, bool fHelp);
|
||||||
extern UniValue test_heirmarker(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
|
#endif // BITCOIN_RPCSERVER_H
|
||||||
|
|||||||
@@ -7818,3 +7818,45 @@ UniValue test_heirmarker(const UniValue& params, bool fHelp)
|
|||||||
return(FinalizeCCTx(0, cp, mtx, myPubkey, 10000, opret));
|
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<CPubKey> voutPubkeys;
|
||||||
|
voutPubkeys.push_back(burnpk);
|
||||||
|
|
||||||
|
cp = CCinit(&C, EVAL_TOKENS);
|
||||||
|
|
||||||
|
std::vector<uint8_t> 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())));
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user