Skip to content

Commit

Permalink
Merge pull request #12 from dimxy/tokel-ccv2-coinbase-fix
Browse files Browse the repository at this point in the history
code refactoring on review notes
  • Loading branch information
NutellaLicka authored Jul 22, 2021
2 parents de936b8 + dfe82e8 commit d46519b
Show file tree
Hide file tree
Showing 10 changed files with 64 additions and 72 deletions.
2 changes: 1 addition & 1 deletion src/cc/CCTokelData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ static tklPropId FindTokelDataIdByName(const std::string &name)

static tklPropDesc_t GetTokelDataDesc(tklPropId id)
{
tklPropDesc_t empty = std::make_tuple( TKLTYP_INVALID, std::string(), nullptr, nullptr );
static const tklPropDesc_t empty = std::make_tuple( TKLTYP_INVALID, std::string(), nullptr, nullptr );
auto found = tklPropDesc.find(id);
if (found != tklPropDesc.end())
return found->second;
Expand Down
33 changes: 16 additions & 17 deletions src/cc/CCassetstx_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ UniValue AssetOrders(uint256 refassetid, CPubKey pk)
cpAssets = CCinit(&assetsC, A::EvalCode());
cpTokens = CCinit(&tokensC, T::EvalCode());

auto addOrders = [&](struct CCcontract_info *cp, std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> >::const_iterator it)
auto addOrders = [&](struct CCcontract_info *cp, const CAddressUnspentKey &key)
{
uint256 txid, hashBlock, assetid;
CAmount unit_price;
Expand All @@ -42,7 +42,7 @@ UniValue AssetOrders(uint256 refassetid, CPubKey pk)
char origaddr[KOMODO_ADDRESS_BUFSIZE], origtokenaddr[KOMODO_ADDRESS_BUFSIZE];
int32_t expiryHeight;

txid = it->first.txhash;
txid = key.txhash;
LOGSTREAM(ccassets_log, CCLOG_DEBUG2, stream << funcname << " checking txid=" << txid.GetHex() << std::endl);
if (!myGetTransaction(txid, ordertx, hashBlock)) {
LOGSTREAM(ccassets_log, CCLOG_DEBUG2, stream << funcname <<" could not load order txid=" << txid.GetHex() << std::endl);
Expand Down Expand Up @@ -121,7 +121,7 @@ UniValue AssetOrders(uint256 refassetid, CPubKey pk)
item.push_back(Pair("ExpiryHeight", expiryHeight));

result.push_back(item);
LOGSTREAM(ccassets_log, CCLOG_DEBUG1, stream << funcname << " added order funcId=" << (char)(funcid ? funcid : ' ') << " it->first.index=" << it->first.index << " ordertx.vout[it->first.index].nValue=" << ordertx.vout[it->first.index].nValue << " tokenid=" << assetid.GetHex() << std::endl);
LOGSTREAM(ccassets_log, CCLOG_DEBUG1, stream << funcname << " added order funcId=" << (char)(funcid ? funcid : ' ') << " key.index=" << key.index << " ordertx.vout[key.index].nValue=" << ordertx.vout[key.index].nValue << " tokenid=" << assetid.GetHex() << std::endl);
}
}
};
Expand All @@ -136,7 +136,7 @@ UniValue AssetOrders(uint256 refassetid, CPubKey pk)
for (std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> >::const_iterator itCoins = unspentOutputsCoins.begin();
itCoins != unspentOutputsCoins.end();
itCoins++)
addOrders(cpAssets, itCoins);
addOrders(cpAssets, itCoins->first);

// tokenasks:
std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> > unspentOutputsTokens;
Expand All @@ -146,7 +146,7 @@ UniValue AssetOrders(uint256 refassetid, CPubKey pk)
for (std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> >::const_iterator itTokens = unspentOutputsTokens.begin();
itTokens != unspentOutputsTokens.end();
itTokens++)
addOrders(cpAssets, itTokens);
addOrders(cpAssets, itTokens->first);
}
else
{
Expand All @@ -158,7 +158,7 @@ UniValue AssetOrders(uint256 refassetid, CPubKey pk)
for (std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> >::const_iterator itOrders = unspentsMyAddr.begin();
itOrders != unspentsMyAddr.end();
itOrders++)
addOrders(cpAssets, itOrders);
addOrders(cpAssets, itOrders->first);
}
return(result);
}
Expand All @@ -180,6 +180,11 @@ UniValue CreateBuyOffer(const CPubKey &mypk, CAmount txfee, CAmount bidamount, u
CCerror = "invalid bidamount or numtokens";
return("");
}
CAmount unit_price = bidamount / numtokens;
if (unit_price <= 0) {
CCerror = "invalid bid params";
return ("");
}

// check if valid token
if (myGetTransaction(assetid, vintx, hashBlock) == 0) {
Expand All @@ -203,11 +208,6 @@ UniValue CreateBuyOffer(const CPubKey &mypk, CAmount txfee, CAmount bidamount, u
return ("");
}

CAmount unit_price = bidamount / numtokens;
if (unit_price <= 0) {
CCerror = "invalid bid params";
return ("");
}
CPubKey unspendableAssetsPubkey = GetUnspendable(cpAssets, 0);
mtx.vout.push_back(T::MakeCC1vout(A::EvalCode(), bidamount, unspendableAssetsPubkey));
mtx.vout.push_back(T::MakeCC1of2vout(A::EvalCode(), ASSETS_MARKER_AMOUNT, mypk, unspendableAssetsPubkey)); // 1of2 marker for my orders
Expand Down Expand Up @@ -296,7 +296,7 @@ template<class T, class A>
std::string CreateSwap(int64_t txfee,int64_t askamount,uint256 assetid,uint256 assetid2,int64_t pricetotal)
{
CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight());
CPubKey mypk; uint64_t mask; int64_t inputs,CCchange; CScript opret; struct CCcontract_info *cp,C;
CPubKey mypk; int64_t inputs,CCchange; CScript opret; struct CCcontract_info *cp,C;
////////////////////////// NOT IMPLEMENTED YET/////////////////////////////////
fprintf(stderr,"asset swaps disabled\n");
Expand All @@ -317,7 +317,6 @@ std::string CreateSwap(int64_t txfee,int64_t askamount,uint256 assetid,uint256 a
if (AddNormalinputs(mtx, mypk, txfee, 0x10000) > 0)
{
mask = ~((1LL << mtx.vin.size()) - 1);
if ((inputs = AddAssetInputs(cp, mtx, mypk, assetid, askamount, 60)) > 0)
{
////////////////////////// NOT IMPLEMENTED YET/////////////////////////////////
Expand Down Expand Up @@ -417,7 +416,7 @@ UniValue CancelBuyOffer(const CPubKey &mypk, CAmount txfee, uint256 assetid, uin
else {
// send dust back to global addr
mtx.vout.push_back(T::MakeCC1vout(A::EvalCode(), bidamount, unspendableAssetsPk));
std::cerr << __func__ << " dust detected bidamount=" << bidamount << std::endl;
LOGSTREAMFN(ccassets_log, CCLOG_DEBUG1, stream << "dust detected bidamount=" << bidamount << std::endl);
}

// probe to spend marker:
Expand Down Expand Up @@ -607,14 +606,14 @@ UniValue FillBuyOffer(const CPubKey &mypk, CAmount txfee, uint256 assetid, uint2
if (orig_units - fill_units > 0 || bid_amount - paid_amount <= ASSETS_NORMAL_DUST) { // bidder has coins for more tokens or only dust is sent back to global address
mtx.vout.push_back(T::MakeCC1vout(A::EvalCode(), bid_amount - paid_amount, unspendableAssetsPk)); // vout0 coins remainder or the dust is sent back to cc global addr
if (bid_amount - paid_amount <= ASSETS_NORMAL_DUST)
std::cerr << __func__ << " dust detected (bid_amount - paid_amount)=" << (bid_amount - paid_amount) << std::endl;
LOGSTREAMFN(ccassets_log, CCLOG_DEBUG1, stream << "dust detected (bid_amount - paid_amount)=" << (bid_amount - paid_amount) << std::endl);
}
else
mtx.vout.push_back(CTxOut(bid_amount - paid_amount, CScript() << ParseHex(HexStr(origpubkey)) << OP_CHECKSIG)); // vout0 if no more tokens to buy, send the remainder to originator
mtx.vout.push_back(CTxOut(paid_amount - royaltyValue, CScript() << ParseHex(HexStr(mypk)) << OP_CHECKSIG)); // vout1 coins to mypk normal
if (royaltyValue > 0) { // note it makes vout even if roaltyValue is 0
mtx.vout.push_back(CTxOut(royaltyValue, CScript() << ParseHex(HexStr(ownerpubkey)) << OP_CHECKSIG)); // vout2 trade royalty to token owner
std::cerr << __func__ << " royaltyFract=" << royaltyFract << " royaltyValue=" << royaltyValue << " paid_amount - royaltyValue=" << paid_amount - royaltyValue << std::endl;
LOGSTREAMFN(ccassets_log, CCLOG_DEBUG1, stream << "royaltyFract=" << royaltyFract << " royaltyValue=" << royaltyValue << " paid_amount - royaltyValue=" << paid_amount - royaltyValue << std::endl);
}
mtx.vout.push_back(T::MakeTokensCC1vout(T::EvalCode(), fill_units, pubkey2pk(origpubkey))); // vout2(3) single-eval tokens sent to the originator
if (orig_units - fill_units > 0) // order is not finished yet
Expand Down Expand Up @@ -751,7 +750,7 @@ UniValue FillSell(const CPubKey &mypk, CAmount txfee, uint256 assetid, uint256 a
mtx.vout.push_back(CTxOut(paid_nValue - royaltyValue, CScript() << origpubkey << OP_CHECKSIG)); //vout.2 coins to ask originator's normal addr
if (royaltyValue > 0) { // note it makes the vout even if roaltyValue is 0
mtx.vout.push_back(CTxOut(royaltyValue, CScript() << ownerpubkey << OP_CHECKSIG)); // vout.3 royalty to token owner
std::cerr << __func__ << " royaltyFract=" << royaltyFract << " royaltyValue=" << royaltyValue << " paid_nValue - royaltyValue=" << paid_nValue - royaltyValue << std::endl;
LOGSTREAMFN(ccassets_log, CCLOG_DEBUG1, stream << "royaltyFract=" << royaltyFract << " royaltyValue=" << royaltyValue << " paid_nValue - royaltyValue=" << paid_nValue - royaltyValue << std::endl);
}

if (orig_assetoshis - fillunits > 0) // we dont need the marker if order is filled
Expand Down
2 changes: 0 additions & 2 deletions src/cc/CCtokens.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -313,8 +313,6 @@ CAmount TokensV1::CheckTokensvout(struct CCcontract_info *cp, Eval* eval, const

// test vouts for possible token use-cases:
std::vector<std::pair<CTxOut, std::string>> testVouts;

uint8_t version;
std::vector<vscript_t> vdatas;
DecodeTokenOpRetV1(tx.vout.back().scriptPubKey, tokenIdOpret, voutPubkeysInOpret, vdatas);
LOGSTREAM(cctokens_log, CCLOG_DEBUG2, stream << funcname << "()" << " vdatas.size()=" << vdatas.size() << std::endl);
Expand Down
2 changes: 1 addition & 1 deletion src/cc/CCtokens_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ UniValue CreateTokenExt(const CPubKey &remotepk, CAmount txfee, CAmount tokensup
// This what the AddNormalinputsRemote does (and it is not necessary that this is done only for nspv calls):
if ((totalInputs = AddNormalinputsRemote(mtx, mypk, tokensupply + txfee + markerCount * TOKENS_MARKER_VALUE, 0x10000, useMempool)) > 0)
{
CAmount mypkInputs = TotalPubkeyNormalInputs(NULL, mtx, mypk);
CAmount mypkInputs = TotalPubkeyNormalInputs(nullptr, mtx, mypk);
if (mypkInputs < tokensupply) { // check that the token amount is really issued with mypk (because in the wallet there may be some other privkeys)
CCerror = "some inputs signed not with mypubkey (-pubkey=pk)";
return NullUniValue;
Expand Down
23 changes: 9 additions & 14 deletions src/cc/CCutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1017,19 +1017,15 @@ CAmount TotalPubkeyCCInputs(Eval *eval, const CTransaction &tx, const CPubKey &p

bool ProcessCC(struct CCcontract_info* cp, Eval* eval, std::vector<uint8_t> paramsNull, const CTransaction& ctx, unsigned int nIn, std::shared_ptr<CCheckCCEvalCodes> evalcodeChecker)
{
//CTransaction createTx;
//uint256 assetid, assetid2, hashBlock;
//uint8_t funcid;
int32_t height, from_mempool = 0;
//int64_t amount;
std::vector<uint8_t> origpubkey;
int32_t height;
//int32_t from_mempool = 0;
height = KOMODO_CONNECTING;
if (KOMODO_CONNECTING < 0) // always comes back with > 0 for final confirmation
return (true);
return true;
if (ASSETCHAINS_CC == 0 || (height & ~(1 << 30)) < KOMODO_CCACTIVATE)
return eval->Invalid("CC are disabled or not active yet");
if ((KOMODO_CONNECTING & (1 << 30)) != 0) {
from_mempool = 1;
//from_mempool = 1;
height &= ((1 << 30) - 1);
}
if (cp->validate == NULL)
Expand All @@ -1043,18 +1039,18 @@ bool ProcessCC(struct CCcontract_info* cp, Eval* eval, std::vector<uint8_t> para
//fprintf(stderr,"process CC %02x\n",cp->evalcode);
CCclearvars(cp);
if (paramsNull.size() != 0) // Don't expect params
return eval->Invalid("Cannot have params");
return eval->Invalid("eval conds cannot have params yet");
//else if ( ctx.vout.size() == 0 ) // spend can go to z-addresses
// return eval->Invalid("no-vouts");
else if ((*cp->validate)(cp, eval, ctx, nIn) != 0) {
//fprintf(stderr,"done CC %02x\n",cp->evalcode);
//cp->prevtxid = txid;
if (evalcodeChecker.get() != NULL)
evalcodeChecker->MarkEvalCode(ctx.GetHash(), cp->evalcode);
return (true);
return true;
}
//fprintf(stderr,"invalid CC %02x\n",cp->evalcode);
return (false);
return false;
}

bool SubcallCCValidate(Eval* eval, uint8_t evalcode, const CTransaction& ctx, int32_t nIn)
Expand Down Expand Up @@ -1667,16 +1663,15 @@ UniValue CCaddress(struct CCcontract_info *cp, const char *name, const std::vect
// return funcid, version and creationid
bool CCDecodeTxVout(const CTransaction &tx, int32_t n, uint8_t &evalcode, uint8_t &funcid, uint8_t &version, uint256 &creationId)
{
CScript opdrop;
vscript_t vccdata;

if (tx.vout.size() > 0)
{
// note: assumes that this is a cc vout (does not check this)

// first try if OP_DROP data exists
bool usedOpreturn;
CScript opdrop;
vscript_t vccdata;

if (!(opdrop = GetCCDropAsOpret(tx.vout[n].scriptPubKey)).empty()) {
GetOpReturnData(opdrop, vccdata);
usedOpreturn = false;
Expand Down
19 changes: 9 additions & 10 deletions src/cc/heir.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ bool HeirValidate(struct CCcontract_info* cpHeir, Eval* eval, const CTransaction
if (fundingTxidInOpret == zeroid) {
return eval->Invalid("incorrect tx opreturn: no fundingtxid present");
}
latestTxid = FindLatestFundingTx(fundingTxidInOpret, tokenid, fundingTxOpRetScript, hasHeirSpendingBegun);
latestTxid = FindLatestFundingTx(eval, fundingTxidInOpret, tokenid, fundingTxOpRetScript, hasHeirSpendingBegun);

if( tokenid != zeroid && tokenid != tokenidThis )
return eval->Invalid("incorrect tx tokenid");
Expand Down Expand Up @@ -360,7 +360,7 @@ uint8_t DecodeHeirEitherOpRetV1(CScript scriptPubKey, uint256 &tokenid, uint256
* find the latest funding tx: it may be the first F tx or one of A or C tx's
* Note: this function is also called from validation code (use non-locking calls)
*/
uint256 _FindLatestFundingTx(uint256 fundingtxid, uint8_t& funcId, uint256 &tokenid, CPubKey& ownerPubkey, CPubKey& heirPubkey, int64_t& inactivityTime, std::string& heirName, std::string& memo, CScript& fundingOpretScript, uint8_t &hasHeirSpendingBegun)
uint256 _FindLatestFundingTx(Eval *eval, uint256 fundingtxid, uint8_t& funcId, uint256 &tokenid, CPubKey& ownerPubkey, CPubKey& heirPubkey, int64_t& inactivityTime, std::string& heirName, std::string& memo, CScript& fundingOpretScript, uint8_t &hasHeirSpendingBegun)
{
CTransaction fundingtx;
uint256 hashBlock;
Expand Down Expand Up @@ -414,7 +414,7 @@ uint256 _FindLatestFundingTx(uint256 fundingtxid, uint8_t& funcId, uint256 &toke
int32_t blockHeight = (int32_t)it->second.blockHeight;

//NOTE: maybe called from validation code:
if (myGetTransaction(txid, regtx, hash)) {
if (GetTxUnconfirmedOpt(eval, txid, regtx, hash)) {
// std::cerr << __func__ << " found tx for txid=" << txid.GetHex() << " blockHeight=" << blockHeight << " maxBlockHeight=" << maxBlockHeight << '\n';
uint256 fundingTxidInOpret;
uint256 tokenidInOpret; // not to contaminate the tokenid from the params!
Expand All @@ -432,8 +432,7 @@ uint256 _FindLatestFundingTx(uint256 fundingtxid, uint8_t& funcId, uint256 &toke
bool isNonOwner = false;

// we ignore 'donations' tx (with non-owner inputs) for calculating if heir is allowed to spend:
// TODO: pass Eval instead of NULL
if (TotalPubkeyNormalInputs(NULL, regtx, ownerPubkey) > 0 || TotalPubkeyCCInputs(NULL, regtx, ownerPubkey) > 0)
if (TotalPubkeyNormalInputs(eval, regtx, ownerPubkey) > 0 || TotalPubkeyCCInputs(eval, regtx, ownerPubkey) > 0)
{
// CheckVinPubkey(regtx.vin, ownerPubkey, isOwner, isNonOwner);
// if (isOwner && !isNonOwner) {
Expand All @@ -452,23 +451,23 @@ uint256 _FindLatestFundingTx(uint256 fundingtxid, uint8_t& funcId, uint256 &toke
}

// overload for validation code
uint256 FindLatestFundingTx(uint256 fundingtxid, uint256 &tokenid, CScript& opRetScript, uint8_t &hasHeirSpendingBegun)
uint256 FindLatestFundingTx(Eval *eval, uint256 fundingtxid, uint256 &tokenid, CScript& opRetScript, uint8_t &hasHeirSpendingBegun)
{
uint8_t funcId;
CPubKey ownerPubkey;
CPubKey heirPubkey;
int64_t inactivityTime;
std::string heirName, memo;

return _FindLatestFundingTx(fundingtxid, funcId, tokenid, ownerPubkey, heirPubkey, inactivityTime, heirName, memo, opRetScript, hasHeirSpendingBegun);
return _FindLatestFundingTx(eval, fundingtxid, funcId, tokenid, ownerPubkey, heirPubkey, inactivityTime, heirName, memo, opRetScript, hasHeirSpendingBegun);
}

// overload for transaction creation code
uint256 FindLatestFundingTx(uint256 fundingtxid, uint8_t& funcId, uint256 &tokenid, CPubKey& ownerPubkey, CPubKey& heirPubkey, int64_t& inactivityTime, std::string& heirName, std::string& memo, uint8_t &hasHeirSpendingBegun)
{
CScript opRetScript;

return _FindLatestFundingTx(fundingtxid, funcId, tokenid, ownerPubkey, heirPubkey, inactivityTime, heirName, memo, opRetScript, hasHeirSpendingBegun);
return _FindLatestFundingTx(nullptr, fundingtxid, funcId, tokenid, ownerPubkey, heirPubkey, inactivityTime, heirName, memo, opRetScript, hasHeirSpendingBegun);
}

// add inputs of 1 of 2 cc address
Expand Down Expand Up @@ -629,7 +628,7 @@ template <typename Helper> UniValue _HeirFund(int64_t txfee, int64_t amount, std

// for initial funding do not allow to sign by non-owner key:
// if (hasNotMypubkey) {
if (TotalPubkeyNormalInputs(NULL, mtx, myPubkey) < amount && TotalPubkeyCCInputs(NULL, mtx, myPubkey) < amount)
if (TotalPubkeyNormalInputs(nullptr, mtx, myPubkey) < amount && TotalPubkeyCCInputs(NULL, mtx, myPubkey) < amount)
{
result.push_back(Pair("result", "error"));
result.push_back(Pair("error", "using non-owner inputs not allowed"));
Expand Down Expand Up @@ -756,7 +755,7 @@ template <class Helper> UniValue _HeirAdd(uint256 fundingtxid, int64_t txfee, in
// warn the user he's making a donation if this is all non-owner keys:
// if (hasNotMypubkey) {
// TODO: change to Eval instead of NULL
if (TotalPubkeyNormalInputs(NULL, mtx, myPubkey) < amount && TotalPubkeyCCInputs(NULL, mtx, myPubkey) < amount) {
if (TotalPubkeyNormalInputs(nullptr, mtx, myPubkey) < amount && TotalPubkeyCCInputs(NULL, mtx, myPubkey) < amount) {
result.push_back(Pair("result", "warning"));
result.push_back(Pair("warning", "you are about to make a donation to heir fund"));
}
Expand Down
2 changes: 1 addition & 1 deletion src/cc/heir_validate.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
vscript_t EncodeHeirCreateOpRetV1(uint8_t funcid, CPubKey ownerPubkey, CPubKey heirPubkey, int64_t inactivityTimeSec, std::string heirName, std::string memo);
vscript_t EncodeHeirOpRetV1(uint8_t funcid, uint256 fundingtxid, uint8_t isHeirSpendingBegan);

uint256 FindLatestFundingTx(uint256 fundingtxid, uint256 &tokenid, CScript& opRetScript, uint8_t &isHeirSpendingBegan);
uint256 FindLatestFundingTx(Eval *eval, uint256 fundingtxid, uint256 &tokenid, CScript& opRetScript, uint8_t &isHeirSpendingBegan);
uint8_t DecodeHeirEitherOpRetV1(CScript scriptPubKey, uint256 &tokenid, CPubKey& ownerPubkey, CPubKey& heirPubkey, int64_t& inactivityTime, std::string& heirName, std::string& memo, bool noLogging = false);
uint8_t DecodeHeirEitherOpRetV1(CScript scriptPubKey, uint256 &tokenid, uint256 &fundingTxidInOpret, uint8_t &hasHeirSpendingBegun, bool noLogging = false);

Expand Down
2 changes: 1 addition & 1 deletion src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2113,7 +2113,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
threadGroup.create_thread(boost::bind(ThreadSendAlert));

if (KOMODO_NSPV_FULLNODE)
fprintf(stderr,"nLocalServices %llx %d, %d\n",(long long)nLocalServices,GetBoolArg("-addressindex", DEFAULT_ADDRESSINDEX),GetBoolArg("-spentindex", DEFAULT_SPENTINDEX));
LogPrintf("nLocalServices %llx %d, %d\n", (long long)nLocalServices, GetBoolArg("-addressindex", DEFAULT_ADDRESSINDEX), GetBoolArg("-spentindex", DEFAULT_SPENTINDEX));

return !fRequestShutdown;
}
Loading

0 comments on commit d46519b

Please sign in to comment.