Skip to content

Commit

Permalink
Merge pull request #416 from dimxy/nspv-refactor-assets-fix
Browse files Browse the repository at this point in the history
NSPV code refactor, assets cc several fixes
  • Loading branch information
dimxy authored May 4, 2021
2 parents 44fbe6c + 6a7d60d commit 9829b8b
Show file tree
Hide file tree
Showing 25 changed files with 940 additions and 518 deletions.
2 changes: 1 addition & 1 deletion src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,6 @@ libbitcoin_server_a_SOURCES = \
cc/betprotocol.cpp \
cc/pricesfeed.cpp \
cc/priceslibs/cjsonpointer.cpp \
cc/kogs.cpp \
chain.cpp \
checkpoints.cpp \
fs.cpp \
Expand Down Expand Up @@ -773,6 +772,7 @@ clean-local:
-$(MAKE) -C cryptoconditions clean
rm -f leveldb/*/*.gcno leveldb/helpers/memenv/*.gcno
-rm -f config.h
rm -f cc/customcc.so

.rc.o:
@test -f $(WINDRES)
Expand Down
8 changes: 0 additions & 8 deletions src/cc/CCKogs.h

This file was deleted.

2 changes: 1 addition & 1 deletion src/cc/CCassetsUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@ CAmount AssetsGetCCInputs(struct CCcontract_info *cp, const char *addr, const CT
{
char scriptaddr[KOMODO_ADDRESS_BUFSIZE];
if (addr == NULL || Getscriptaddress(scriptaddr, vinTx.vout[tx.vin[i].prevout.n].scriptPubKey) && strcmp(scriptaddr, addr) == 0) {
std::cerr << __func__ << " adding amount=" << vinTx.vout[tx.vin[i].prevout.n].nValue << " for vin i=" << i << " eval=" << std::hex << (int)cp->evalcode << std::resetiosflags(std::ios::hex) << std::endl;
//std::cerr << __func__ << " adding amount=" << vinTx.vout[tx.vin[i].prevout.n].nValue << " for vin i=" << i << " eval=" << std::hex << (int)cp->evalcode << std::resetiosflags(std::ios::hex) << std::endl;
inputs += vinTx.vout[tx.vin[i].prevout.n].nValue;
}
}
Expand Down
40 changes: 35 additions & 5 deletions src/cc/CCassetstx_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,25 @@
#include "CCtokens.h"
#include "CCassets.h"


static bool IsTxidInActiveChain(uint256 txid)
{
CTransaction tx;
uint256 hashBlock;
AssertLockHeld(cs_main);

if (myGetTransaction(txid, tx, hashBlock))
{
BlockMap::iterator mi = mapBlockIndex.find(hashBlock);
if (mi != mapBlockIndex.end() && (*mi).second) {
CBlockIndex* pindex = (*mi).second;
if (chainActive.Contains(pindex))
return true;
}
}
return false;
}

template<class T, class A>
UniValue AssetOrders(uint256 refassetid, CPubKey pk, uint8_t evalCodeNFT)
{
Expand Down Expand Up @@ -370,14 +389,19 @@ UniValue CancelBuyOffer(const CPubKey &mypk, int64_t txfee,uint256 assetid,uint2
int32_t spendingvin, h;

mask = ~((1LL << mtx.vin.size()) - 1);
if (CCgetspenttxid(spendingtxid, spendingvin, h, bidtxid, ASSETS_GLOBALADDR_VOUT) != 0 && myGetTransaction(bidtxid, vintx, hashBlock) && vintx.vout.size() > ASSETS_GLOBALADDR_VOUT)
LOCK(cs_main);
if ((CCgetspenttxid(spendingtxid, spendingvin, h, bidtxid, ASSETS_GLOBALADDR_VOUT) != 0 || !IsTxidInActiveChain(spendingtxid)) && myGetTransaction(bidtxid, vintx, hashBlock) && vintx.vout.size() > ASSETS_GLOBALADDR_VOUT)
{
uint8_t dummyEvalCode; uint256 dummyAssetid, dummyAssetid2; int64_t dummyPrice; std::vector<uint8_t> dummyOrigpubkey;

//std::vector<uint8_t> vopretNonfungible;
//GetNonfungibleData<T>(assetid, vopretNonfungible);

bidamount = vintx.vout[ASSETS_GLOBALADDR_VOUT].nValue;
if (bidamount == 0) {
CCerror = "bid is empty";
return "";
}
mtx.vin.push_back(CTxIn(bidtxid, ASSETS_GLOBALADDR_VOUT, CScript())); // coins in Assets

uint8_t funcid = A::DecodeAssetTokenOpRet(vintx.vout.back().scriptPubKey, dummyEvalCode, dummyAssetid, dummyAssetid2, dummyPrice, dummyOrigpubkey);
Expand Down Expand Up @@ -429,14 +453,19 @@ UniValue CancelSell(const CPubKey &mypk, int64_t txfee,uint256 assetid,uint256 a
int32_t spendingvin, h;

mask = ~((1LL << mtx.vin.size()) - 1);
if (CCgetspenttxid(spendingtxid, spendingvin, h, asktxid, ASSETS_GLOBALADDR_VOUT) != 0 && myGetTransaction(asktxid, vintx, hashBlock) != 0 && vintx.vout.size() > 0)
LOCK(cs_main);
if ((CCgetspenttxid(spendingtxid, spendingvin, h, asktxid, ASSETS_GLOBALADDR_VOUT) != 0 || !IsTxidInActiveChain(spendingtxid)) && myGetTransaction(asktxid, vintx, hashBlock) != 0 && vintx.vout.size() > 0)
{
uint8_t dummyEvalCode;
uint256 dummyAssetid, dummyAssetid2;
int64_t dummyPrice;
std::vector<uint8_t> dummyOrigpubkey;

askamount = vintx.vout[ASSETS_GLOBALADDR_VOUT].nValue;
if (askamount == 0) {
CCerror = "ask is empty";
return "";
}
mtx.vin.push_back(CTxIn(asktxid, ASSETS_GLOBALADDR_VOUT, CScript()));

uint8_t funcid = A::DecodeAssetTokenOpRet(vintx.vout.back().scriptPubKey, dummyEvalCode, dummyAssetid, dummyAssetid2, dummyPrice, dummyOrigpubkey);
Expand Down Expand Up @@ -516,7 +545,8 @@ UniValue FillBuyOffer(const CPubKey &mypk, int64_t txfee, uint256 assetid, uint2
int32_t spendingvin, h;

mask = ~((1LL << mtx.vin.size()) - 1);
if (CCgetspenttxid(spendingtxid, spendingvin, h, bidtxid, bidvout) != 0 && myGetTransaction(bidtxid, vintx, hashBlock) != 0 && vintx.vout.size() > bidvout)
LOCK(cs_main);
if ((CCgetspenttxid(spendingtxid, spendingvin, h, bidtxid, bidvout) != 0 || !IsTxidInActiveChain(spendingtxid)) && myGetTransaction(bidtxid, vintx, hashBlock) != 0 && vintx.vout.size() > bidvout)
{
bid_amount = vintx.vout[bidvout].nValue;
uint8_t funcid = SetAssetOrigpubkey<A>(origpubkey, unit_price, vintx); // get orig pk, orig units
Expand Down Expand Up @@ -594,7 +624,6 @@ UniValue FillBuyOffer(const CPubKey &mypk, int64_t txfee, uint256 assetid, uint2
return "";
}


// send coins, receive tokens
template<class T, class A>
UniValue FillSell(const CPubKey &mypk, int64_t txfee, uint256 assetid, uint256 assetid2, uint256 asktxid, int64_t fillunits, CAmount paid_unit_price)
Expand Down Expand Up @@ -633,7 +662,8 @@ UniValue FillSell(const CPubKey &mypk, int64_t txfee, uint256 assetid, uint256 a
uint256 spendingtxid;
int32_t spendingvin, h;

if (CCgetspenttxid(spendingtxid, spendingvin, h, asktxid, askvout) != 0 && myGetTransaction(asktxid, vintx, hashBlock) != 0 && vintx.vout.size() > askvout)
LOCK(cs_main);
if ((CCgetspenttxid(spendingtxid, spendingvin, h, asktxid, askvout) != 0 || !IsTxidInActiveChain(spendingtxid)) && myGetTransaction(asktxid, vintx, hashBlock) && vintx.vout.size() > askvout)
{
orig_assetoshis = vintx.vout[askvout].nValue;
uint8_t funcid = SetAssetOrigpubkey<A>(origpubkey, unit_price, vintx); // get orig pk, orig value
Expand Down
20 changes: 0 additions & 20 deletions src/cc/CCcustom.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
#include "CCGateways.h"
#include "CCtokens.h"
#include "CCImportGateway.h"
#include "CCKogs.h"

/*
CCcustom has most of the functions that need to be extended to create a new CC contract.
Expand Down Expand Up @@ -254,17 +253,6 @@ uint8_t ImportGatewayCCpriv[32] = { 0x65, 0xef, 0x27, 0xeb, 0x3d, 0xb0, 0xb4, 0x
#undef FUNCNAME
#undef EVALCODE

// Kogs
#define FUNCNAME IsKogsInput
#define EVALCODE EVAL_KOGS
const char *KogsCCaddr = "RD3UQofnS7uqa9Z3cKC8cb9c95VvoxnPyo";
const char *KogsNormaladdr = "RVH1M8ZmT2nPB7MW6726RRsxjY7D5FKQHa";
char KogsCChexstr[67] = { "03c27db737b92826d37fb43f3fda3d1b1d258cd28b68fe4be605457bf9dd9e0218" };
uint8_t KogsCCpriv[32] = { 0x9f, 0x9a, 0x85, 0x6d, 0xd9, 0x2b, 0xfe, 0xcb, 0xa1, 0x18, 0xca, 0x51, 0x06, 0x80, 0x87, 0x7f, 0x87, 0xaa, 0xef, 0x9c, 0x6e, 0xa0, 0x21, 0x21, 0xed, 0x1c, 0x89, 0x96, 0xc6, 0xe6, 0x93, 0x21 };
#include "CCcustom.inc"
#undef FUNCNAME
#undef EVALCODE

// OraclesV2
#define FUNCNAME IsOraclesV2Input
#define EVALCODE EVAL_ORACLESV2
Expand Down Expand Up @@ -491,14 +479,6 @@ struct CCcontract_info *CCinit(struct CCcontract_info *cp, uint8_t evalcode)
cp->validate = ImportGatewayValidate;
cp->ismyvin = IsImportGatewayInput;
break;
case EVAL_KOGS:
strcpy(cp->unspendableCCaddr, KogsCCaddr);
strcpy(cp->normaladdr, KogsNormaladdr);
strcpy(cp->CChexstr, KogsCChexstr);
memcpy(cp->CCpriv, KogsCCpriv, 32);
cp->validate = KogsValidate;
cp->ismyvin = IsKogsInput;
break;
case EVAL_ORACLESV2:
strcpy(cp->unspendableCCaddr,OraclesV2CCaddr);
strcpy(cp->normaladdr,OraclesV2Normaladdr);
Expand Down
7 changes: 6 additions & 1 deletion src/cc/CCinclude.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ Details.
#define bits256_nonz(a) (((a).ulongs[0] | (a).ulongs[1] | (a).ulongs[2] | (a).ulongs[3]) != 0)

#define MAY2020_NNELECTION_HARDFORK 1592146800 //(Sunday, June 14th, 2020 03:00:00 PM UTC)
#define JUNE2021_NNELECTION_HARDFORK 1623682800 // dPoW Season 5 Monday, June 14th, 2021 (03:00:00 PM UTC)

/* moved to komodo_cJSON.h
#ifndef _BITS256
Expand Down Expand Up @@ -230,7 +231,7 @@ struct CCcontract_info
{
uint8_t evalcode; //!< cc contract eval code, set by CCinit function
uint8_t evalcodeNFT; //!< additional eval code for spending from three-eval-code vouts with EVAL_TOKENS, cc evalcode, cc evalcode NFT
//!< or vouts with two evalcodes: EVAL_TOKENS, additionalTokensEvalcode2.
//!< or vouts with two evalcodes: EVAL_TOKENS, evalcodeNFT.
//!< Set by AddTokenCCInputs function

char unspendableCCaddr[64]; //!< global contract cryptocondition address, set by CCinit function
Expand Down Expand Up @@ -418,6 +419,8 @@ int32_t myGet_mempool_txs(std::vector<CTransaction> &txs,uint8_t evalcode,uint8_
/// \endcond

/// \cond INTERNAL
#define IGUANA_READ 0
#define IGUANA_WRITE 1
int32_t iguana_rwnum(int32_t rwflag,uint8_t *serialized,int32_t len,void *endianedp);
int32_t iguana_rwbignum(int32_t rwflag,uint8_t *serialized,int32_t len,uint8_t *endianedp);
/// \endcond
Expand Down Expand Up @@ -1089,6 +1092,8 @@ void AddSigData2UniValue(UniValue &result, int32_t vini, UniValue& ccjson, std::
/// @returns 0 if okay or -1
int32_t ensure_CCrequirements(uint8_t evalcode);

extern bool fUnspentCCIndex; // if unspent cc index enabled

/// @private forward decl
struct CLockedInMemoryUtxos;

Expand Down
35 changes: 26 additions & 9 deletions src/cc/CCtokens.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -610,10 +610,6 @@ bool TokensValidate(struct CCcontract_info *cp, Eval* eval, const CTransaction &

if (strcmp(ASSETCHAINS_SYMBOL, "ROGUE") == 0 && chainActive.Height() <= 12500)
return true;

// my test chains:
if (strcmp(ASSETCHAINS_SYMBOL, "TOK3") == 0 /*&& eval->GetCurrentHeight() < 100*/)
return true;

// check boundaries:
if (tx.vout.size() < 1)
Expand Down Expand Up @@ -706,6 +702,9 @@ static bool CheckTokensV2CreateTx(struct CCcontract_info *cp, Eval* eval, const
// token 2 cc validation entry point
bool Tokensv2Validate(struct CCcontract_info *cp, Eval* eval, const CTransaction &tx, uint32_t nIn)
{
if (!TokensIsVer1Active(eval))
return eval->Invalid("tokens v2 not active yet");

// check boundaries:
if (tx.vout.size() < 1)
return report_validation_error(__func__, eval, tx, "no vouts");
Expand Down Expand Up @@ -816,7 +815,6 @@ UniValue TokenList()
UniValue TokenV2List()
{
UniValue result(UniValue::VARR);
std::vector<std::pair<CUnspentCCIndexKey, CUnspentCCIndexValue> > unspentOutputs;

struct CCcontract_info *cp, C;
cp = CCinit(&C, EVAL_TOKENSV2);
Expand All @@ -834,10 +832,29 @@ UniValue TokenV2List()
}
};

SetCCunspentsCCIndex(unspentOutputs, cp->unspendableCCaddr, zeroid); // find by burnable validated cc addr marker
LOGSTREAMFN(cctokens_log, CCLOG_DEBUG1, stream << "unspentOutputs.size()=" << unspentOutputs.size() << std::endl);
for (std::vector<std::pair<CUnspentCCIndexKey, CUnspentCCIndexValue> >::const_iterator it = unspentOutputs.begin(); it != unspentOutputs.end(); it++) {
addTokenId(it->first.creationid, it->second.opreturn);
if (fUnspentCCIndex)
{
std::vector<std::pair<CUnspentCCIndexKey, CUnspentCCIndexValue> > unspentOutputs;

SetCCunspentsCCIndex(unspentOutputs, cp->unspendableCCaddr, zeroid); // find by burnable validated cc addr marker
LOGSTREAMFN(cctokens_log, CCLOG_DEBUG1, stream << "unspentOutputs.size()=" << unspentOutputs.size() << std::endl);
for (std::vector<std::pair<CUnspentCCIndexKey, CUnspentCCIndexValue> >::const_iterator it = unspentOutputs.begin(); it != unspentOutputs.end(); it++) {
addTokenId(it->first.creationid, it->second.opreturn);
}
}
else
{
bool CC_INPUTS_TRUE = true;
std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> > unspentOutputs;

SetCCunspents(unspentOutputs, cp->unspendableCCaddr, CC_INPUTS_TRUE);
LOGSTREAMFN(cctokens_log, CCLOG_DEBUG1, stream << "unspentOutputs.size()=" << unspentOutputs.size() << std::endl);
for (std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> >::const_iterator it = unspentOutputs.begin(); it != unspentOutputs.end(); it++) {
CTransaction creationtx;
uint256 hashBlock;
if (myGetTransaction(it->first.txhash, creationtx, hashBlock) && creationtx.vout.size() > 0)
addTokenId(it->first.txhash, creationtx.vout.back().scriptPubKey);
}
}

return(result);
Expand Down
2 changes: 1 addition & 1 deletion src/cc/CCtokens.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ bool Tokensv2Validate(struct CCcontract_info *cp,Eval* eval,const CTransaction &
UniValue TokenList();
UniValue TokenV2List();

/// Adds token inputs to transaction object. If tokenid is a non-fungible token then the function will set additionalTokensEvalcode2 variable in the cp object to the eval code from NFT data to spend NFT outputs properly
/// Adds token inputs to transaction object. If tokenid is a non-fungible token then the function will set evalcodeNFT variable in the cp object to the eval code from NFT data to spend NFT outputs properly
/// @param cp CCcontract_info structure
/// @param mtx mutable transaction object
/// @param pk pubkey for whose token inputs to add
Expand Down
74 changes: 47 additions & 27 deletions src/cc/CCtokens_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ CAmount AddTokenCCInputs(struct CCcontract_info *cp, CMutableTransaction &mtx, c
{
CAmount totalinputs = 0;
int32_t n = 0;
std::vector<std::pair<CUnspentCCIndexKey, CUnspentCCIndexValue> > unspentOutputs;
const bool CC_INPUTS_TRUE = true;

if (cp->evalcode != V::EvalCode())
LOGSTREAMFN(cctokens_log, CCLOG_INFO, stream << "warning: EVAL_TOKENS *cp is needed but used evalcode=" << (int)cp->evalcode << std::endl);
Expand All @@ -72,55 +72,75 @@ CAmount AddTokenCCInputs(struct CCcontract_info *cp, CMutableTransaction &mtx, c
cp->evalcodeNFT = vopretNonfungible.begin()[0]; // set evalcode of NFT, for signing
}

SetCCunspentsCCIndex(unspentOutputs, tokenaddr, tokenid);
if (useMempool) {
AddCCunspentsCCIndexMempool(unspentOutputs, tokenaddr, tokenid);
}

// threshold = total / (maxinputs != 0 ? maxinputs : CC_MAXVINS); // let's not use threshold
LOGSTREAMFN(cctokens_log, CCLOG_DEBUG1, stream << " found unspentOutputs=" << unspentOutputs.size() << std::endl);
for (std::vector<std::pair<CUnspentCCIndexKey, CUnspentCCIndexValue> >::const_iterator it = unspentOutputs.begin(); it != unspentOutputs.end(); it++)
{
// make lambda to use it for either index kind:
auto add_token_vin = [&](uint256 txhash, int32_t index, CAmount satoshis) -> void
{
CTransaction tx;
uint256 hashBlock;

//if (it->second.satoshis < threshold) // this should work also for non-fungible tokens (there should be only 1 satoshi for non-fungible token issue)
// continue;

if (it->second.satoshis == 0)
continue; // skip null vins
if (satoshis == 0)
return; // skip null vins

if (std::find_if(mtx.vin.begin(), mtx.vin.end(), [&](const CTxIn &vin){ return vin.prevout.hash == it->first.txhash && vin.prevout.n == it->first.index; }) != mtx.vin.end())
continue; // vin already added
if (std::find_if(mtx.vin.begin(), mtx.vin.end(), [&](const CTxIn &vin){ return vin.prevout.hash == txhash && vin.prevout.n == index; }) != mtx.vin.end())
return; // vin already added

if (myGetTransaction(it->first.txhash, tx, hashBlock) != 0)
if (myGetTransaction(txhash, tx, hashBlock) != 0)
{
char destaddr[KOMODO_ADDRESS_BUFSIZE];
Getscriptaddress(destaddr, tx.vout[it->first.index].scriptPubKey);
Getscriptaddress(destaddr, tx.vout[index].scriptPubKey);
if (strcmp(destaddr, tokenaddr) != 0 /*&&
strcmp(destaddr, cp->unspendableCCaddr) != 0 && // TODO: check why this. Should not we add token inputs from unspendable cc addr if mypubkey is used?
strcmp(destaddr, cp->unspendableaddr2) != 0*/) // or the logic is to allow to spend all available tokens (what about unspendableaddr3)?
continue;
return;

LOGSTREAMFN(cctokens_log, CCLOG_DEBUG1, stream << "checked tx vout destaddress=" << destaddr << " amount=" << tx.vout[it->first.index].nValue << std::endl);
LOGSTREAMFN(cctokens_log, CCLOG_DEBUG1, stream << "checked tx vout destaddress=" << destaddr << " amount=" << tx.vout[index].nValue << std::endl);

if (IsTokensvout<V>(true, true, cp, NULL, tx, it->first.index, tokenid) > 0 && !myIsutxo_spentinmempool(ignoretxid, ignorevin, it->first.txhash, it->first.index))
if (IsTokensvout<V>(true, true, cp, NULL, tx, index, tokenid) > 0 && !myIsutxo_spentinmempool(ignoretxid, ignorevin, txhash, index))
{
if (total != 0 && maxinputs != 0) // if it is not just to calc amount...
mtx.vin.push_back(CTxIn(it->first.txhash, it->first.index, CScript()));
mtx.vin.push_back(CTxIn(txhash, index, CScript()));

totalinputs += it->second.satoshis;
LOGSTREAMFN(cctokens_log, CCLOG_DEBUG1, stream << "adding input nValue=" << it->second.satoshis << std::endl);
totalinputs += satoshis;
LOGSTREAMFN(cctokens_log, CCLOG_DEBUG1, stream << "adding input nValue=" << satoshis << std::endl);
n++;

if ((total > 0 && totalinputs >= total) || (maxinputs > 0 && n >= maxinputs))
break;
return;
}
}
}
}; // auto add_token_vin

if (fUnspentCCIndex)
{
std::vector<std::pair<CUnspentCCIndexKey, CUnspentCCIndexValue> > unspentOutputs;

SetCCunspentsCCIndex(unspentOutputs, tokenaddr, tokenid);
if (useMempool)
AddCCunspentsCCIndexMempool(unspentOutputs, tokenaddr, tokenid);

// threshold = total / (maxinputs != 0 ? maxinputs : CC_MAXVINS); // let's not use threshold
LOGSTREAMFN(cctokens_log, CCLOG_DEBUG1, stream << " found unspentOutputs=" << unspentOutputs.size() << std::endl);
for (std::vector<std::pair<CUnspentCCIndexKey, CUnspentCCIndexValue> >::const_iterator it = unspentOutputs.begin(); it != unspentOutputs.end(); it++)
add_token_vin(it->first.txhash, it->first.index, it->second.satoshis);
}
else
{
std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> > unspentOutputs;

if (useMempool)
SetCCunspentsWithMempool(unspentOutputs, (char*)tokenaddr, CC_INPUTS_TRUE);
else
SetCCunspents(unspentOutputs, (char*)tokenaddr, CC_INPUTS_TRUE);

LOGSTREAMFN(cctokens_log, CCLOG_DEBUG1, stream << " found unspentOutputs=" << unspentOutputs.size() << std::endl);
for (std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> >::const_iterator it = unspentOutputs.begin(); it != unspentOutputs.end(); it++)
add_token_vin(it->first.txhash, it->first.index, it->second.satoshis);
}

//std::cerr << "AddTokenCCInputs() found totalinputs=" << totalinputs << std::endl;
return(totalinputs);
return totalinputs;
}

// overload to get inputs for a pubkey
Expand All @@ -135,7 +155,7 @@ CAmount AddTokenCCInputs(struct CCcontract_info *cp, CMutableTransaction &mtx, c
if (vopretNonfungible.size() > 0)
cp->evalcodeNFT = vopretNonfungible.begin()[0]; // set evalcode of NFT

GetTokensCCaddress(cp, tokenaddr, pk, V::IsMixed()); // GetTokensCCaddress will use 'additionalTokensEvalcode2'
GetTokensCCaddress(cp, tokenaddr, pk, V::IsMixed()); // GetTokensCCaddress will use 'evalcodeNFT'
return AddTokenCCInputs<V>(cp, mtx, tokenaddr, tokenid, total, maxinputs, useMempool);
}

Expand Down
Loading

0 comments on commit 9829b8b

Please sign in to comment.