diff --git a/src/Makefile.am b/src/Makefile.am index e6cc5d756..9588cfe44 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -290,6 +290,7 @@ libbitcoin_server_a_SOURCES = \ cc/CCcustom.cpp \ cc/CCtx.cpp \ cc/CCutils.cpp \ + cc/CCtokens.cpp \ cc/assets.cpp \ cc/faucet.cpp \ cc/rewards.cpp \ diff --git a/src/cc/CCHeir.h b/src/cc/CCHeir.h index 214b566c7..18d1f08bb 100644 --- a/src/cc/CCHeir.h +++ b/src/cc/CCHeir.h @@ -18,12 +18,33 @@ #define CC_HEIR_H #include "CCinclude.h" +#include "CCtokens.h" -#define EVAL_HEIR 0xea +//#define EVAL_HEIR 0xea bool HeirValidate(struct CCcontract_info *cp,Eval* eval,const CTransaction &tx, uint32_t nIn); +class CoinHelper; +class TokenHelper; + // CCcustom -UniValue HeirInfo(); + +// this would not link +//template std::string HeirFund(uint64_t txfee,int64_t funds, std::string heirName, CPubKey heirPubkey, int64_t inactivityTimeSec, uint256 assetid); +//template UniValue HeirAdd(uint256 fundingtxid, uint64_t txfee, int64_t amount); +//template UniValue HeirClaim(uint256 fundingtxid, uint64_t txfee, int64_t nValue); + +std::string HeirFundCoinCaller(uint64_t txfee, int64_t funds, std::string heirName, CPubKey heirPubkey, int64_t inactivityTimeSec, uint256 assetid); +std::string HeirFundTokenCaller(uint64_t txfee, int64_t funds, std::string heirName, CPubKey heirPubkey, int64_t inactivityTimeSec, uint256 assetid); +UniValue HeirClaimCoinCaller(uint256 fundingtxid, uint64_t txfee, int64_t amount); +UniValue HeirClaimTokenCaller(uint256 fundingtxid, uint64_t txfee, int64_t amount); +UniValue HeirAddCoinCaller(uint256 fundingtxid, uint64_t txfee, int64_t amount); +UniValue HeirAddTokenCaller(uint256 fundingtxid, uint64_t txfee, int64_t amount); + +UniValue HeirInfo(uint256 fundingtxid); +UniValue HeirList(); +//std::string Heir_MakeBadTx(uint256 fundingtxid, uint8_t funcId, int64_t amount, CPubKey ownerPubkey, CPubKey heirPubkey, int64_t inactivityTime, uint32_t errMask); + +//bool HeirExactTokenAmounts(bool compareTotals, struct CCcontract_info *cpHeir, Eval* eval, uint256 assetid, const CTransaction &tx); #endif diff --git a/src/cc/CCassets.h b/src/cc/CCassets.h index 5e4441646..5a5ef2c82 100644 --- a/src/cc/CCassets.h +++ b/src/cc/CCassets.h @@ -30,12 +30,12 @@ bool AssetsValidate(struct CCcontract_info *cp,Eval* eval,const CTransaction &tx // CCassetsCore //CTxOut MakeAssetsVout(CAmount nValue,CPubKey pk); -CScript EncodeAssetCreateOpRet(uint8_t funcid,std::vector origpubkey,std::string name,std::string description); -CScript EncodeAssetOpRet(uint8_t funcid,uint256 assetid,uint256 assetid2,int64_t price,std::vector origpubkey); -bool DecodeAssetCreateOpRet(const CScript &scriptPubKey,std::vector &origpubkey,std::string &name,std::string &description); -uint8_t DecodeAssetOpRet(const CScript &scriptPubKey,uint256 &assetid,uint256 &assetid2,int64_t &price,std::vector &origpubkey); +//CScript EncodeAssetCreateOpRet(uint8_t funcid,std::vector origpubkey,std::string name,std::string description); +//CScript EncodeAssetOpRet(uint8_t assetFuncId, uint256 tokenid, uint256 assetid2, int64_t price, std::vector origpubkey); +//bool DecodeAssetCreateOpRet(const CScript &scriptPubKey,std::vector &origpubkey,std::string &name,std::string &description); +//uint8_t DecodeAssetOpRet(const CScript &scriptPubKey, uint8_t &evalCode, uint256 &assetid, uint256 &assetid2, int64_t &price, std::vector &origpubkey); bool SetAssetOrigpubkey(std::vector &origpubkey,int64_t &price,const CTransaction &tx); -int64_t IsAssetvout(int32_t maxAssetExactAmountDepth, struct CCcontract_info *cp, Eval* eval, int64_t &price,std::vector &origpubkey,const CTransaction& tx,int32_t v,uint256 refassetid); +int64_t IsAssetvout(struct CCcontract_info *cp, int64_t &price, std::vector &origpubkey, const CTransaction& tx, int32_t v, uint256 refassetid); bool ValidateBidRemainder(int64_t remaining_price,int64_t remaining_nValue,int64_t orig_nValue,int64_t received_nValue,int64_t paidprice,int64_t totalprice); bool ValidateAskRemainder(int64_t remaining_price,int64_t remaining_nValue,int64_t orig_nValue,int64_t received_nValue,int64_t paidprice,int64_t totalprice); bool ValidateSwapRemainder(int64_t remaining_price,int64_t remaining_nValue,int64_t orig_nValue,int64_t received_nValue,int64_t paidprice,int64_t totalprice); @@ -44,18 +44,18 @@ bool SetAskFillamounts(int64_t &paid,int64_t &remaining_price,int64_t orig_nValu bool SetSwapFillamounts(int64_t &paid,int64_t &remaining_price,int64_t orig_nValue,int64_t &received,int64_t totalprice); int64_t AssetValidateBuyvin(struct CCcontract_info *cp,Eval* eval,int64_t &tmpprice,std::vector &tmporigpubkey,char *CCaddr,char *origaddr,const CTransaction &tx,uint256 refassetid); int64_t AssetValidateSellvin(struct CCcontract_info *cp,Eval* eval,int64_t &tmpprice,std::vector &tmporigpubkey,char *CCaddr,char *origaddr,const CTransaction &tx,uint256 assetid); -bool AssetExactAmounts(int32_t maxDepth, struct CCcontract_info *cp,int64_t &inputs,int32_t starti,int64_t &outputs,Eval* eval,const CTransaction &tx,uint256 assetid); -//bool AssetExactAmounts(bool doValidateTx, struct CCcontract_info *cp, int64_t &inputs, int32_t starti, int64_t &outputs, Eval* eval, const CTransaction &tx, uint256 assetid, std::vector &ccVinsTxs); +bool AssetExactAmounts(struct CCcontract_info *cpAssets, int64_t &inputs, int64_t &outputs, Eval* eval, const CTransaction &tx, uint256 assetid); // CCassetstx int64_t GetAssetBalance(CPubKey pk,uint256 tokenid); -int64_t AddAssetInputs(CMutableTransaction &mtx,CPubKey pk,uint256 assetid,int64_t total,int32_t maxinputs); +int64_t AddAssetInputs(struct CCcontract_info *cp, CMutableTransaction &mtx, CPubKey pk, uint256 assetid, int64_t total, int32_t maxinputs); + UniValue AssetOrders(uint256 tokenid); UniValue AssetInfo(uint256 tokenid); UniValue AssetList(); -std::string CreateAsset(int64_t txfee,int64_t assetsupply,std::string name,std::string description); -std::string AssetTransfer(int64_t txfee,uint256 assetid,std::vector destpubkey,int64_t total); -std::string AssetConvert(int64_t txfee,uint256 assetid,std::vector destpubkey,int64_t total,int32_t evalcode); +//std::string CreateAsset(int64_t txfee,int64_t assetsupply,std::string name,std::string description); +//std::string AssetTransfer(int64_t txfee,uint256 assetid,std::vector destpubkey,int64_t total); +//std::string AssetConvert(int64_t txfee,uint256 assetid,std::vector destpubkey,int64_t total,int32_t evalcode); std::string CreateBuyOffer(int64_t txfee,int64_t bidamount,uint256 assetid,int64_t pricetotal); std::string CancelBuyOffer(int64_t txfee,uint256 assetid,uint256 bidtxid); diff --git a/src/cc/CCassetsCore.cpp b/src/cc/CCassetsCore.cpp index 52f1ed28c..52c73d47f 100644 --- a/src/cc/CCassetsCore.cpp +++ b/src/cc/CCassetsCore.cpp @@ -1,5 +1,5 @@ /****************************************************************************** - * Copyright © 2014-2019 The SuperNET Developers. * + * Copyright © 2014-2018 The SuperNET Developers. * * * * See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at * * the top-level directory of this distribution for the individual copyright * @@ -33,7 +33,7 @@ Yes, this is quite confusing... - In ValudateAssetRemainder the naming convention is nValue is the coin/asset with the offer on the books and "units" is what it is being paid in. The high level check is to make sure we didnt lose any coins or assets, the harder to validate is the actual price paid as the "orderbook" is in terms of the combined nValue for the combined totalunits. + In ValidateAssetRemainder the naming convention is nValue is the coin/asset with the offer on the books and "units" is what it is being paid in. The high level check is to make sure we didnt lose any coins or assets, the harder to validate is the actual price paid as the "orderbook" is in terms of the combined nValue for the combined totalunits. We assume that the effective unit cost in the orderbook is valid and that that amount was paid and also that any remainder will be close enough in effective unit cost to not matter. At the edge cases, this will probably be not true and maybe some orders wont be practically fillable when reduced to fractional state. However, the original pubkey that created the offer can always reclaim it. */ @@ -230,38 +230,45 @@ bool ValidateSwapRemainder(int64_t remaining_price,int64_t remaining_nValue,int6 return(true); } +/* use EncodeTokenCreateOpRet instead: CScript EncodeAssetCreateOpRet(uint8_t funcid,std::vector origpubkey,std::string name,std::string description) { CScript opret; uint8_t evalcode = EVAL_ASSETS; opret << OP_RETURN << E_MARSHAL(ss << evalcode << funcid << origpubkey << name << description); return(opret); } +*/ -CScript EncodeAssetOpRet(uint8_t funcid,uint256 assetid,uint256 assetid2,int64_t price,std::vector origpubkey) +CScript EncodeAssetOpRet(uint8_t assetFuncId, uint256 tokenid, uint256 assetid2, int64_t price, std::vector origpubkey) { - CScript opret; uint8_t evalcode = EVAL_ASSETS; - assetid = revuint256(assetid); - switch ( funcid ) + CScript opret; + uint8_t evalcode = EVAL_ASSETS; + uint8_t funcId = (uint8_t)'t'; + + tokenid = revuint256(tokenid); + switch ( assetFuncId ) { - case 't': case 'x': case 'o': - opret << OP_RETURN << E_MARSHAL(ss << evalcode << funcid << assetid); + //case 't': this cannot be here + case 'x': case 'o': + opret << OP_RETURN << E_MARSHAL(ss << evalcode << funcId << tokenid << assetFuncId); break; case 's': case 'b': case 'S': case 'B': - opret << OP_RETURN << E_MARSHAL(ss << evalcode << funcid << assetid << price << origpubkey); + opret << OP_RETURN << E_MARSHAL(ss << evalcode << funcId << tokenid << assetFuncId << price << origpubkey); break; case 'E': case 'e': assetid2 = revuint256(assetid2); - opret << OP_RETURN << E_MARSHAL(ss << evalcode << funcid << assetid << assetid2 << price << origpubkey); + opret << OP_RETURN << E_MARSHAL(ss << evalcode << funcId << tokenid << assetFuncId << assetid2 << price << origpubkey); break; default: - fprintf(stderr,"EncodeOpRet: illegal funcid.%02x\n",funcid); + fprintf(stderr,"EncodeAssetOpRet: illegal funcid.%02x\n", assetFuncId); opret << OP_RETURN; break; } return(opret); } -bool DecodeAssetCreateOpRet(const CScript &scriptPubKey,std::vector &origpubkey,std::string &name,std::string &description) +// it is for compatibility, do not use this for new contracts (use DecodeTokenCreateOpRet) +bool DecodeAssetCreateOpRet(const CScript &scriptPubKey, std::vector &origpubkey, std::string &name, std::string &description) { std::vector vopret; uint8_t evalcode,funcid,*script; GetOpReturnData(scriptPubKey, vopret); @@ -274,59 +281,87 @@ bool DecodeAssetCreateOpRet(const CScript &scriptPubKey,std::vector &or return(0); } -uint8_t DecodeAssetOpRet(const CScript &scriptPubKey,uint256 &assetid,uint256 &assetid2,int64_t &price,std::vector &origpubkey) +uint8_t DecodeAssetOpRet(const CScript &scriptPubKey, uint8_t &evalCodeInOpret, uint256 &tokenid, uint256 &assetid2, int64_t &price, std::vector &origpubkey) { - std::vector vopret; uint8_t funcid=0,*script,e,f; - GetOpReturnData(scriptPubKey, vopret); + std::vector vopret; + uint8_t *script, funcId = 0, assetFuncId = 0, dummyEvalCode, dummyFuncId; + uint256 dummyTokenid; + + GetOpReturnData(scriptPubKey, vopret); + script = (uint8_t *)vopret.data(); - memset(&assetid,0,sizeof(assetid)); - memset(&assetid2,0,sizeof(assetid2)); - price = 0; - if ( script != 0 && script[0] == EVAL_ASSETS ) - { - funcid = script[1]; - //fprintf(stderr,"decode.[%c]\n",funcid); - switch ( funcid ) + if (script == 0) { + std::cerr << "DecodeAssetOpRet() script is empty" << std::endl; + return (uint8_t)0; + } + + tokenid = zeroid; + assetid2 = zeroid; + price = 0; + + bool isEof = true; // NOTE: if parse error occures, parse might not be able to set error. It is safer to treat that it was eof if it is not set! + bool result = E_UNMARSHAL(vopret, ss >> evalCodeInOpret; ss >> funcId; ss >> tokenid; ss >> assetFuncId; isEof = ss.eof()); + + if (!result && isEof) { // NOTE: 'result==false' means 'parse error' OR 'not eof state'. Consequently, 'result==false' but 'isEof==true' means just 'parse error' + std::cerr << "DecodeAssetOpRet() incorrect opret or no asset's payload" << std::endl; + return (uint8_t)0; + } + + tokenid = revuint256(tokenid); + + std::cerr << "DecodeAssetOpRet() evalCodeInOpret=" << (int)evalCodeInOpret << " funcId=" << (char)(funcId ? funcId : ' ') << " assetFuncId=" << (char)(assetFuncId ? assetFuncId : ' ') << std::endl; + + if(evalCodeInOpret == EVAL_ASSETS) + { + //fprintf(stderr,"decode.[%c] assetFuncId.[%c]\n", funcId, assetFuncId); + switch( assetFuncId ) { - case 'c': return(funcid); - break; - case 't': case 'x': case 'o': - if ( E_UNMARSHAL(vopret,ss >> e; ss >> f; ss >> assetid) != 0 ) + /*case 'c': + return(funcid); + break; */ + /*case 't': + if (E_UNMARSHAL(vopret, ss >> evalCode; ss >> funcId; ss >> tokenid; isEof = ss.eof()) || !isEof) + { + assetid = revuint256(assetid); + return(funcid); + } + break; */ + + case 'x': case 'o': + if (isEof) // no data after 'assetFuncId' allowed { - assetid = revuint256(assetid); - return(funcid); + return(assetFuncId); } break; case 's': case 'b': case 'S': case 'B': - if ( E_UNMARSHAL(vopret,ss >> e; ss >> f; ss >> assetid; ss >> price; ss >> origpubkey) != 0 ) + if (E_UNMARSHAL(vopret, ss >> dummyEvalCode; ss >> dummyFuncId; ss >> dummyTokenid; ss >> dummyFuncId; ss >> price; ss >> origpubkey) != 0) { - assetid = revuint256(assetid); //fprintf(stderr,"got price %llu\n",(long long)price); - return(funcid); + return(assetFuncId); } break; case 'E': case 'e': - if ( E_UNMARSHAL(vopret,ss >> e; ss >> f; ss >> assetid; ss >> assetid2; ss >> price; ss >> origpubkey) != 0 ) + if ( E_UNMARSHAL(vopret,ss >> dummyEvalCode; ss >> dummyFuncId; ss >> dummyTokenid; ss >> dummyFuncId; ss >> assetid2; ss >> price; ss >> origpubkey) != 0 ) { //fprintf(stderr,"got price %llu\n",(long long)price); - assetid = revuint256(assetid); assetid2 = revuint256(assetid2); - return(funcid); + return(assetFuncId); } break; default: - fprintf(stderr,"DecodeAssetOpRet: illegal funcid.%02x\n",funcid); - funcid = 0; + fprintf(stderr,"DecodeAssetOpRet: illegal funcid.%02x\n", funcId); + funcId = 0; break; } } - return(funcid); + return(funcId); } bool SetAssetOrigpubkey(std::vector &origpubkey,int64_t &price,const CTransaction &tx) { uint256 assetid,assetid2; - if ( tx.vout.size() > 0 && DecodeAssetOpRet(tx.vout[tx.vout.size()-1].scriptPubKey,assetid,assetid2,price,origpubkey) != 0 ) + uint8_t evalCode; + if ( tx.vout.size() > 0 && DecodeAssetOpRet(tx.vout[tx.vout.size()-1].scriptPubKey, evalCode,assetid,assetid2,price,origpubkey) != 0 ) return(true); else return(false); } @@ -334,8 +369,10 @@ bool SetAssetOrigpubkey(std::vector &origpubkey,int64_t &price,const CT bool GetAssetorigaddrs(struct CCcontract_info *cp,char *CCaddr,char *destaddr,const CTransaction& tx) { uint256 assetid,assetid2; int64_t price,nValue=0; int32_t n; uint8_t funcid; std::vector origpubkey; CScript script; + uint8_t evalCode; + n = tx.vout.size(); - if ( n == 0 || (funcid= DecodeAssetOpRet(tx.vout[n-1].scriptPubKey,assetid,assetid2,price,origpubkey)) == 0 ) + if ( n == 0 || (funcid= DecodeAssetOpRet(tx.vout[n-1].scriptPubKey, evalCode,assetid,assetid2,price,origpubkey)) == 0 ) return(false); if ( GetCCaddress(cp,CCaddr,pubkey2pk(origpubkey)) != 0 && Getscriptaddress(destaddr,CScript() << origpubkey << OP_CHECKSIG) != 0 ) return(true); @@ -343,76 +380,6 @@ bool GetAssetorigaddrs(struct CCcontract_info *cp,char *CCaddr,char *destaddr,co } -// Checks if the vout is a really Asset CC vout -// if maxAssetExactAmountDepth > 0, it also validates the vin transaction itself: -// it should be either sum(cc vins) == sum(cc vouts) or the transaction is the 'tokenbase' ('c') tx -int64_t IsAssetvout(int32_t maxAssetExactAmountDepth, struct CCcontract_info *cp, Eval* eval, int64_t &price,std::vector &origpubkey,const CTransaction& tx,int32_t v,uint256 refassetid) -{ - uint256 assetid,assetid2; int64_t nValue=0; int32_t n; uint8_t funcid; - - if ( tx.vout[v].scriptPubKey.IsPayToCryptoCondition() != 0 ) // maybe check address too? - { - - if (maxAssetExactAmountDepth > 0) { - //validate all tx - int64_t myCCVinsAmount = 0, myCCVoutsAmount = 0; - std::vector ccVinsTxs; - - //std::cerr << "IsAssetvout() validate=yes" << std::endl; - const bool validateVinTxs = false; - bool isEqualAmounts = AssetExactAmounts(maxAssetExactAmountDepth, cp, myCCVinsAmount, 0, myCCVoutsAmount, eval, tx, refassetid); - - // if ccInputs != ccOutputs and it is not the tokenbase tx means it is possibly fake tx (dimxy): - if (!isEqualAmounts && refassetid != tx.GetHash()) { // checking that this is the true tokenbase tx, by verifying that funcid=c, is done further in this function (dimxy) - std::cerr << "IsAssetvout() detected bad tx=" << tx.GetHash().GetHex() << ": cc inputs != cc outputs and not the 'tokenbase' tx" << std::endl; - return 0; - } - } - - - n = tx.vout.size(); - if (v >= n - 1) { // just moved this up (dimxy) - std::cerr << "isAssetVout() internal err: (v >= n - 1), returning 0" << std::endl; - return(0); - } - nValue = tx.vout[v].nValue; - - // fprintf(stderr,"IsAssetvout() CC vout v.%d of n=%d amount=%.8f\n",v,n,(double)nValue/COIN); - - if ( (funcid= DecodeAssetOpRet(tx.vout[n-1].scriptPubKey,assetid,assetid2,price,origpubkey)) == 0 ) - { - fprintf(stderr,"IsAssetvout() null decodeopret v.%d\n",v); - return(0); - } - else if ( funcid == 'c' ) - { - if (refassetid == tx.GetHash() && v == 0) { - std::cerr << "isAssetVout() this is the tokenbase 'c' tx, txid=" << tx.GetHash().GetHex() << " returning nValue=" << nValue << std::endl; - return(nValue); - } - } - else if ( (funcid == 'b' || funcid == 'B') && v == 0 ) // critical! 'b'/'B' vout0 is NOT asset - return(0); - else if ( funcid != 'E' ) - { - if ( assetid == refassetid ) - { - fprintf(stderr,"IsAssetvout() returning %.8f\n",(double)nValue/COIN); - return(nValue); - } - } - else if ( funcid == 'E' ) - { - if ( v < 2 && assetid == refassetid ) - return(nValue); - else if ( v == 2 && assetid2 == refassetid ) - return(nValue); - } - } - //fprintf(stderr,"Isassetvout: normal output v.%d %.8f\n",v,(double)tx.vout[v].nValue/COIN); - return(0); -} - int64_t AssetValidateCCvin(struct CCcontract_info *cp,Eval* eval,char *CCaddr,char *origaddr,const CTransaction &tx,int32_t vini,CTransaction &vinTx) { uint256 hashBlock; char destaddr[64]; @@ -446,16 +413,16 @@ int64_t AssetValidateCCvin(struct CCcontract_info *cp,Eval* eval,char *CCaddr,ch int64_t AssetValidateBuyvin(struct CCcontract_info *cp,Eval* eval,int64_t &tmpprice,std::vector &tmporigpubkey,char *CCaddr,char *origaddr,const CTransaction &tx,uint256 refassetid) { - CTransaction vinTx; int64_t nValue; uint256 assetid,assetid2; uint8_t funcid; + CTransaction vinTx; int64_t nValue; uint256 assetid,assetid2; uint8_t funcid, evalCode; CCaddr[0] = origaddr[0] = 0; if ( (nValue= AssetValidateCCvin(cp,eval,CCaddr,origaddr,tx,1,vinTx)) == 0 ) - return(0); + return(0); else if ( vinTx.vout[0].scriptPubKey.IsPayToCryptoCondition() == 0 ) return eval->Invalid("invalid normal vout0 for buyvin"); else { //fprintf(stderr,"have %.8f checking assetid origaddr.(%s)\n",(double)nValue/COIN,origaddr); - if ( vinTx.vout.size() > 0 && (funcid= DecodeAssetOpRet(vinTx.vout[vinTx.vout.size()-1].scriptPubKey,assetid,assetid2,tmpprice,tmporigpubkey)) != 'b' && funcid != 'B' ) + if ( vinTx.vout.size() > 0 && (funcid= DecodeAssetOpRet(vinTx.vout[vinTx.vout.size()-1].scriptPubKey, evalCode, assetid,assetid2,tmpprice,tmporigpubkey)) != 'b' && funcid != 'B' ) return eval->Invalid("invalid opreturn for buyvin"); else if ( refassetid != assetid ) return eval->Invalid("invalid assetid for buyvin"); @@ -469,88 +436,162 @@ int64_t AssetValidateBuyvin(struct CCcontract_info *cp,Eval* eval,int64_t &tmppr int64_t AssetValidateSellvin(struct CCcontract_info *cp,Eval* eval,int64_t &tmpprice,std::vector &tmporigpubkey,char *CCaddr,char *origaddr,const CTransaction &tx,uint256 assetid) { CTransaction vinTx; int64_t nValue,assetoshis; - fprintf(stderr,"AssetValidateSellvin\n"); + //fprintf(stderr,"AssetValidateSellvin\n"); if ( (nValue= AssetValidateCCvin(cp,eval,CCaddr,origaddr,tx,1,vinTx)) == 0 ) return(0); - if ( (assetoshis= IsAssetvout(1, cp, NULL, tmpprice,tmporigpubkey,vinTx,0,assetid)) == 0 ) + if ( (assetoshis= IsAssetvout(cp, tmpprice, tmporigpubkey,vinTx,0,assetid)) == 0 ) return eval->Invalid("invalid missing CC vout0 for sellvin"); else return(assetoshis); } -// overload with additional params for deep tx validation (dimxy) -bool AssetExactAmounts(int maxDepth, struct CCcontract_info *cp, int64_t &inputs, int32_t starti, int64_t &outputs, Eval* eval, const CTransaction &tx, uint256 assetid) +// validates opret for asset tx: +bool ValidateAssetOpret(CTransaction tx, int32_t v, uint256 assetid, int64_t &price, std::vector &origpubkey) { + + uint256 assetidOpret, assetidOpret2; + uint8_t funcid, evalCode; + + // this is just for log messages indentation fur debugging recursive calls: + int32_t n = tx.vout.size(); + + if ((funcid = DecodeAssetOpRet(tx.vout[n - 1].scriptPubKey, evalCode, assetidOpret, assetidOpret2, price, origpubkey)) == 0) + { + std::cerr << "ValidateAssetOpret() DecodeOpret returned null for n-1=" << n - 1 << " txid=" << tx.GetHash().GetHex() << std::endl; + return(false); + } +/* it is now on token level: + else if (funcid == 'c') + { + if (assetid != zeroid && assetid == tx.GetHash() && v == 0) { + //std::cerr << "ValidateAssetOpret() this is the tokenbase 'c' tx, txid=" << tx.GetHash().GetHex() << " vout=" << v << " returning true" << std::endl; + return(true); + } + } + else if (funcid == 't') // TODO: check if this new block does not influence IsAssetVout + { + //std::cerr << "ValidateAssetOpret() assetid=" << assetid.GetHex() << " assetIdOpret=" << assetidOpret.GetHex() << " txid=" << tx.GetHash().GetHex() << std::endl; + if (assetid != zeroid && assetid == assetidOpret) { + //std::cerr << "ValidateAssetOpret() this is a transfer 't' tx, txid=" << tx.GetHash().GetHex() << " vout=" << v << " returning true" << std::endl; + return(true); + } + } */ + // TODO: hope this was unneeded!!! (dimxy) + else if ((funcid == 'b' || funcid == 'B') && v == 0) // critical! 'b'/'B' vout0 is NOT asset + return(false); + else if (funcid != 'E') + { + if (assetid != zeroid && assetidOpret == assetid) + { + //std::cerr << "ValidateAssetOpret() returns true for not 'E', funcid=" << (char)funcid << std::endl; + return(true); + } + } + else if (funcid == 'E') // NOTE: not implemented yet! + { + if (v < 2 && assetid != zeroid && assetidOpret == assetid) + return(true); + else if (v == 2 && assetid != zeroid && assetidOpret2 == assetid) + return(true); + } + + //std::cerr << "ValidateAssetOpret() return false funcid=" << (char)funcid << " assetid=" << assetid.GetHex() << " assetIdOpret=" << assetidOpret.GetHex() << " txid=" << tx.GetHash().GetHex() << std::endl; + return false; +} + +// Checks if the vout is a really Asset CC vout +// compareTotals == true, the func also validates the passed transaction itself: +// it should be either sum(cc vins) == sum(cc vouts) or the transaction is the 'tokenbase' ('c') tx +int64_t IsAssetvout(struct CCcontract_info *cp, int64_t &price, std::vector &origpubkey, const CTransaction& tx, int32_t v, uint256 refassetid) { - CTransaction vinTx; uint256 hashBlock,id,id2; int32_t i,flag,numvins,numvouts; int64_t assetoshis; std::vector tmporigpubkey; int64_t tmpprice; - numvins = tx.vin.size(); - numvouts = tx.vout.size(); - inputs = outputs = 0; - maxDepth--; + //std::cerr << "IsAssetvout() entered for txid=" << tx.GetHash().GetHex() << " v=" << v << " for assetid=" << refassetid.GetHex() << std::endl; - for (i=starti; iismyvin)(tx.vin[i].scriptSig) != 0 ) - { - //std::cerr << "AssetExactAmounts() eval is true=" << (eval != NULL) << " ismyvin=ok for_i=" << i << std::endl; - // we are really not inside validation! -- dimxy - if ( (eval && eval->GetTxUnconfirmed(tx.vin[i].prevout.hash,vinTx,hashBlock) == 0) || (!eval && !myGetTransaction(tx.vin[i].prevout.hash, vinTx, hashBlock)) ) - { - fprintf(stderr,"AssetExactAmounts() cannot read vintx i.%d starti.%d numvins.%d\n", i,starti,numvins); - return (!eval) ? false : eval->Invalid("always should find vin, but didnt"); + if (tx.vout[v].scriptPubKey.IsPayToCryptoCondition() != 0) // maybe check address too? dimxy: possibly no, because there are too many cases with different addresses here + { + int32_t n = tx.vout.size(); + // just check boundaries: + if (v >= n - 1) { // just moved this up (dimxy) + std::cerr << "isAssetVout() internal err: (v >= n - 1), returning 0" << std::endl; + return(0); + } - } // false means 'don't go deeper' -- dimxy - else if ( (assetoshis= IsAssetvout( maxDepth, cp, eval, tmpprice,tmporigpubkey,vinTx,tx.vin[i].prevout.n,assetid)) != 0 ) - { - fprintf(stderr,"AssetExactAmounts() vin%d %llu, ",i,(long long)assetoshis); - inputs += assetoshis; - } - else - { - if ( vinTx.vout[i].scriptPubKey.IsPayToCryptoCondition() != 0 && DecodeAssetOpRet(vinTx.vout[vinTx.vout.size()-1].scriptPubKey,id,id2,tmpprice,tmporigpubkey) == 't' && id == assetid ) - { - assetoshis = vinTx.vout[i].nValue; - fprintf(stderr,"AssetExactAmounts() vin%d assetoshis=%llu special case, ",i,(long long)assetoshis); - inputs += assetoshis; - } - } - } - } + // moved opret checking to this new reusable func (dimxy): + const bool valOpret = ValidateAssetOpret(tx, v, refassetid, price, origpubkey); + //std::cerr << "IsAssetvout() ValidateAssetOpret returned=" << std::boolalpha << valOpret << " for txid=" << tx.GetHash().GetHex() << " for assetid=" << refassetid.GetHex() << std::endl; + if (valOpret) { + //std::cerr << "IsAssetvout() ValidateAssetOpret returned true, returning nValue=" << tx.vout[v].nValue << " for txid=" << tx.GetHash().GetHex() << " for assetid=" << refassetid.GetHex() << std::endl; + return tx.vout[v].nValue; + } + //fprintf(stderr,"IsAssetvout() CC vout v.%d of n=%d amount=%.8f txid=%s\n",v,n,(double)0/COIN, tx.GetHash().GetHex().c_str()); + } + //fprintf(stderr,"IsAssetvout() normal output v.%d %.8f\n",v,(double)tx.vout[v].nValue/COIN); + return(0); +} - if ( DecodeAssetOpRet(tx.vout[tx.vout.size()-1].scriptPubKey,id,id2,tmpprice,tmporigpubkey) == 't' && id == assetid ) - flag = 1; - else flag = 0; +// sets cc inputs vs cc outputs and ensures they are equal: +bool AssetExactAmounts(struct CCcontract_info *cpAssets, int64_t &inputs, int64_t &outputs, Eval* eval, const CTransaction &tx, uint256 assetid) +{ + CTransaction vinTx; uint256 hashBlock, id, id2; int32_t flag; int64_t assetoshis; std::vector tmporigpubkey; int64_t tmpprice; + int32_t numvins = tx.vin.size(); + int32_t numvouts = tx.vout.size(); + inputs = outputs = 0; - for (i=0; iismyvin)(tx.vin[i].scriptSig) || (*cpTokens->ismyvin)(tx.vin[i].scriptSig)) // || IsVinAllowed(tx.vin[i].scriptSig) != 0) + { + //std::cerr << indentStr << "AssetExactAmounts() eval is true=" << (eval != NULL) << " ismyvin=ok for_i=" << i << std::endl; + // we are not inside the validation code -- dimxy + if ((eval && eval->GetTxUnconfirmed(tx.vin[i].prevout.hash, vinTx, hashBlock) == 0) || (!eval && !myGetTransaction(tx.vin[i].prevout.hash, vinTx, hashBlock))) + { + std::cerr << "AssetExactAmounts() cannot read vintx for i." << i << " numvins." << numvins << std::endl; + return (!eval) ? false : eval->Invalid("always should find vin tx, but didnt"); + } + else { + // validate vouts of vintx + //std::cerr << indentStr << "AssetExactAmounts() check vin i=" << i << " nValue=" << vinTx.vout[tx.vin[i].prevout.n].nValue << std::endl; + assetoshis = IsAssetvout(cpAssets, tmpprice, tmporigpubkey, vinTx, tx.vin[i].prevout.n, assetid); + if (assetoshis != 0) + { + std::cerr << "AssetExactAmounts() vin i=" << i << " assetoshis=" << assetoshis << std::endl; + inputs += assetoshis; + } + } + } + } + + // we do not use this flag anymore + //if ( DecodeAssetOpRet(tx.vout[tx.vout.size()-1].scriptPubKey,id,id2,tmpprice,tmporigpubkey) == 't' && id == assetid ) + //flag = 1; + //else + //flag = 0; + + for (int32_t i = 0; i ccVinsTxs; - - return AssetExactAmounts(true, cp, inputs, starti, outputs, eval, tx, assetid); -}*/ diff --git a/src/cc/CCassetstx.cpp b/src/cc/CCassetstx.cpp index fbc53406b..cae4b42bb 100644 --- a/src/cc/CCassetstx.cpp +++ b/src/cc/CCassetstx.cpp @@ -14,6 +14,8 @@ ******************************************************************************/ #include "CCassets.h" +//#include "CCtokens.h" + int64_t AddAssetInputs(struct CCcontract_info *cp,CMutableTransaction &mtx,CPubKey pk,uint256 assetid,int64_t total,int32_t maxinputs) { @@ -21,25 +23,31 @@ int64_t AddAssetInputs(struct CCcontract_info *cp,CMutableTransaction &mtx,CPubK std::vector > unspentOutputs; GetCCaddress(cp,coinaddr,pk); SetCCunspents(unspentOutputs,coinaddr); - threshold = total/(maxinputs!=0?maxinputs:64); + + threshold = total/(maxinputs!=0?maxinputs:64); // TODO: is maxinputs really not over 64, what if i want to calc total balance? + for (std::vector >::const_iterator it=unspentOutputs.begin(); it!=unspentOutputs.end(); it++) { txid = it->first.txhash; vout = (int32_t)it->first.index; - if ( it->second.satoshis < threshold ) + + if (it->second.satoshis < threshold) continue; + for (j=0; junspendableCCaddr) != 0 && strcmp(destaddr,cp->unspendableaddr2) != 0 ) + if( strcmp(destaddr,coinaddr) != 0 && strcmp(destaddr,cp->unspendableCCaddr) != 0 && strcmp(destaddr,cp->unspendableaddr2) != 0 ) continue; fprintf(stderr,"AddAssetInputs() check destaddress=%s vout amount=%.8f\n",destaddr,(double)vintx.vout[vout].nValue/COIN); - if ( (nValue= IsAssetvout(1, cp, NULL, price,origpubkey,vintx,vout,assetid)) > 0 && myIsutxo_spentinmempool(txid,vout) == 0 ) + if( (nValue = IsAssetvout(cp, price, origpubkey, vintx, vout, assetid)) > 0 && myIsutxo_spentinmempool(txid,vout) == 0 ) { if ( total != 0 && maxinputs != 0 ) mtx.vin.push_back(CTxIn(txid,vout,CScript())); @@ -57,12 +65,13 @@ int64_t AddAssetInputs(struct CCcontract_info *cp,CMutableTransaction &mtx,CPubK return(totalinputs); } + int64_t GetAssetBalance(CPubKey pk,uint256 tokenid) { CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); struct CCcontract_info *cp,C; - cp = CCinit(&C,EVAL_ASSETS); - return(AddAssetInputs(cp,mtx,pk,tokenid,0,0)); + cp = CCinit(&C,EVAL_TOKENS); + return(AddTokenCCInputs(cp,mtx,pk,tokenid,0,0)); } UniValue AssetInfo(uint256 assetid) @@ -75,11 +84,11 @@ UniValue AssetInfo(uint256 assetid) result.push_back(Pair("error","cant find assetid")); return(result); } - if ( vintx.vout.size() > 0 && DecodeAssetCreateOpRet(vintx.vout[vintx.vout.size()-1].scriptPubKey,origpubkey,name,description) == 0 ) + if ( vintx.vout.size() > 0 && DecodeTokenCreateOpRet(vintx.vout[vintx.vout.size()-1].scriptPubKey,origpubkey,name,description) == 0 ) { - fprintf(stderr,"assetid isnt assetcreation txid\n"); + fprintf(stderr,"assetid isnt token creation txid\n"); result.push_back(Pair("result","error")); - result.push_back(Pair("error","assetid isnt assetcreation txid")); + result.push_back(Pair("error","assetid isnt token creation txid")); } result.push_back(Pair("result","success")); result.push_back(Pair("tokenid",uint256_str(str,assetid))); @@ -92,15 +101,20 @@ UniValue AssetInfo(uint256 assetid) UniValue AssetList() { - UniValue result(UniValue::VARR); std::vector > addressIndex; struct CCcontract_info *cp,C; uint256 txid,hashBlock; CTransaction vintx; std::vector origpubkey; std::string name,description; char str[65]; - cp = CCinit(&C,EVAL_ASSETS); + UniValue result(UniValue::VARR); + std::vector > addressIndex; + struct CCcontract_info *cp,C; uint256 txid,hashBlock; + CTransaction vintx; std::vector origpubkey; + std::string name,description; char str[65]; + + cp = CCinit(&C,EVAL_TOKENS); SetCCtxids(addressIndex,cp->normaladdr); for (std::vector >::const_iterator it=addressIndex.begin(); it!=addressIndex.end(); it++) { txid = it->first.txhash; if ( GetTransaction(txid,vintx,hashBlock,false) != 0 ) { - if ( vintx.vout.size() > 0 && DecodeAssetCreateOpRet(vintx.vout[vintx.vout.size()-1].scriptPubKey,origpubkey,name,description) != 0 ) + if ( vintx.vout.size() > 0 && DecodeTokenCreateOpRet(vintx.vout[vintx.vout.size()-1].scriptPubKey,origpubkey,name,description) != 0 ) { result.push_back(uint256_str(str,txid)); } @@ -112,17 +126,33 @@ UniValue AssetList() UniValue AssetOrders(uint256 refassetid) { static uint256 zero; - int64_t price; uint256 txid,hashBlock,assetid,assetid2; std::vector origpubkey; CTransaction vintx; UniValue result(UniValue::VARR); std::vector > unspentOutputs; uint8_t funcid; char numstr[32],funcidstr[16],origaddr[64],assetidstr[65]; struct CCcontract_info *cp,C; - cp = CCinit(&C,EVAL_ASSETS); - SetCCunspents(unspentOutputs,(char *)cp->unspendableCCaddr); - for (std::vector >::const_iterator it=unspentOutputs.begin(); it!=unspentOutputs.end(); it++) - { + UniValue result(UniValue::VARR); + std::vector > unspentOutputsTokens, unspentOutputsAssets; + + struct CCcontract_info *cpTokens, tokensC; + struct CCcontract_info *cpAssets, assetsC; + + cpTokens = CCinit(&tokensC, EVAL_TOKENS); + cpAssets = CCinit(&assetsC, EVAL_ASSETS); + + auto addOrders = [&](struct CCcontract_info *cp, std::vector >::const_iterator it) + { + uint256 txid, hashBlock, assetid, assetid2; + int64_t price; + std::vector origpubkey; + CTransaction vintx; + uint8_t funcid, evalCode; + char numstr[32], funcidstr[16], origaddr[64], assetidstr[65]; + txid = it->first.txhash; - if ( GetTransaction(txid,vintx,hashBlock,false) != 0 ) + //std::cerr << "addOrders txid" << txid.GetHex() << std::endl; + if ( GetTransaction(txid,vintx,hashBlock,false) != 0 ) { - if ( vintx.vout.size() > 0 && (funcid= DecodeAssetOpRet(vintx.vout[vintx.vout.size()-1].scriptPubKey,assetid,assetid2,price,origpubkey)) != 0 ) + funcid = DecodeAssetOpRet(vintx.vout[vintx.vout.size() - 1].scriptPubKey, evalCode, assetid, assetid2, price, origpubkey); + //std::cerr << "addOrders vintx.vout.size()=" << vintx.vout.size() << " funcid=" << (char)(funcid ? funcid : ' ') << std::endl; + if (vintx.vout.size() > 0 && (funcid = DecodeAssetOpRet(vintx.vout[vintx.vout.size()-1].scriptPubKey, evalCode, assetid, assetid2, price, origpubkey)) != 0) { - if ( refassetid != zero && assetid != refassetid ) + if (refassetid != zero && assetid != refassetid) { //int32_t z; //for (z=31; z>=0; z--) fprintf(stderr,"%02x",((uint8_t *)&txid)[z]); @@ -131,11 +161,13 @@ UniValue AssetOrders(uint256 refassetid) //fprintf(stderr," assetid\n"); //for (z=31; z>=0; z--) fprintf(stderr,"%02x",((uint8_t *)&refassetid)[z]); //fprintf(stderr," refassetid\n"); - continue; + return; } - if ( vintx.vout[it->first.index].nValue == 0 ) - continue; + if (vintx.vout[it->first.index].nValue == 0) + return; + UniValue item(UniValue::VOBJ); + funcidstr[0] = funcid; funcidstr[1] = 0; item.push_back(Pair("funcid", funcidstr)); @@ -157,8 +189,8 @@ UniValue AssetOrders(uint256 refassetid) } if ( origpubkey.size() == 33 ) { - GetCCaddress(cp,origaddr,pubkey2pk(origpubkey)); - item.push_back(Pair("origaddress",origaddr)); + GetCCaddress(cp, origaddr, pubkey2pk(origpubkey)); // TODO: what is this? is it asset or token?? + item.push_back(Pair("origaddress", origaddr)); } if ( assetid != zeroid ) item.push_back(Pair("tokenid",uint256_str(assetidstr,assetid))); @@ -184,11 +216,27 @@ UniValue AssetOrders(uint256 refassetid) //fprintf(stderr,"func.(%c) %s/v%d %.8f\n",funcid,uint256_str(assetidstr,txid),(int32_t)it->first.index,(double)vintx.vout[it->first.index].nValue/COIN); } } - } + }; + + + SetCCunspents(unspentOutputsTokens, (char *)cpTokens->unspendableCCaddr); + SetCCunspents(unspentOutputsAssets, (char *)cpAssets->unspendableCCaddr); + + for (std::vector >::const_iterator itTokens = unspentOutputsTokens.begin(); + itTokens != unspentOutputsTokens.end(); + itTokens++) + addOrders(cpTokens, itTokens); + + for (std::vector >::const_iterator itAssets = unspentOutputsAssets.begin(); + itAssets != unspentOutputsAssets.end(); + itAssets++) + addOrders(cpAssets, itAssets); + return(result); } -std::string CreateAsset(int64_t txfee,int64_t assetsupply,std::string name,std::string description) +// not used (use TokenCreate instead) +/* std::string CreateAsset(int64_t txfee,int64_t assetsupply,std::string name,std::string description) { CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); CPubKey mypk; struct CCcontract_info *cp,C; @@ -213,9 +261,10 @@ std::string CreateAsset(int64_t txfee,int64_t assetsupply,std::string name,std:: return(FinalizeCCTx(0,cp,mtx,mypk,txfee,EncodeAssetCreateOpRet('c',Mypubkey(),name,description))); } return(""); -} - -std::string AssetTransfer(int64_t txfee,uint256 assetid,std::vector destpubkey,int64_t total) +} */ + +// not used (use TokenTransfer instead) +/* std::string AssetTransfer(int64_t txfee,uint256 assetid,std::vector destpubkey,int64_t total) { CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); CPubKey mypk; uint64_t mask; int64_t CCchange=0,inputs=0; struct CCcontract_info *cp,C; @@ -230,11 +279,11 @@ std::string AssetTransfer(int64_t txfee,uint256 assetid,std::vector des mypk = pubkey2pk(Mypubkey()); if ( AddNormalinputs(mtx,mypk,txfee,3) > 0 ) { - /*n = outputs.size(); - if ( n == amounts.size() ) - { - for (i=0; i 0 ) { @@ -254,9 +303,10 @@ std::string AssetTransfer(int64_t txfee,uint256 assetid,std::vector des //} else fprintf(stderr,"numoutputs.%d != numamounts.%d\n",n,(int32_t)amounts.size()); } return(""); -} +} */ -std::string AssetConvert(int64_t txfee,uint256 assetid,std::vector destpubkey,int64_t total,int32_t evalcode) +// deprecated +/* std::string AssetConvert(int64_t txfee,uint256 assetid,std::vector destpubkey,int64_t total,int32_t evalcode) { CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); CPubKey mypk; int64_t CCchange=0,inputs=0; struct CCcontract_info *cp,C; @@ -281,76 +331,106 @@ std::string AssetConvert(int64_t txfee,uint256 assetid,std::vector dest } else fprintf(stderr,"not enough CC asset inputs for %.8f\n",(double)total/COIN); } return(""); -} +} */ -std::string CreateBuyOffer(int64_t txfee,int64_t bidamount,uint256 assetid,int64_t pricetotal) +// rpc tokenbid implementation, locks 'bidamount' coins for the 'pricetotal' of tokens +std::string CreateBuyOffer(int64_t txfee, int64_t bidamount, uint256 assetid, int64_t pricetotal) { CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); - CPubKey mypk; struct CCcontract_info *cp,C; uint256 hashBlock; CTransaction vintx; std::vector origpubkey; std::string name,description; - if ( bidamount < 0 || pricetotal < 0 ) + CPubKey mypk; + struct CCcontract_info *cpAssets, C; + uint256 hashBlock; + CTransaction vintx; + std::vector origpubkey; + std::string name,description; + int64_t inputs; + + std::cerr << "CreateBuyOffer() bidamount=" << bidamount << " numtokens(pricetotal)=" << pricetotal << std::endl; + + if (bidamount < 0 || pricetotal < 0) { - fprintf(stderr,"negative bidamount %lld, pricetotal %lld\n",(long long)bidamount,(long long)pricetotal); + fprintf(stderr,"negative bidamount %lld, pricetotal %lld\n", (long long)bidamount, (long long)pricetotal); return(""); } - if ( GetTransaction(assetid,vintx,hashBlock,false) == 0 ) + if (GetTransaction(assetid, vintx, hashBlock, false) == 0) { fprintf(stderr,"cant find assetid\n"); return(""); } - if ( vintx.vout.size() > 0 && DecodeAssetCreateOpRet(vintx.vout[vintx.vout.size()-1].scriptPubKey,origpubkey,name,description) == 0 ) + if (vintx.vout.size() > 0 && DecodeTokenCreateOpRet(vintx.vout[vintx.vout.size()-1].scriptPubKey, origpubkey, name, description) == 0) { fprintf(stderr,"assetid isnt assetcreation txid\n"); return(""); } - cp = CCinit(&C,EVAL_ASSETS); - if ( txfee == 0 ) + + cpAssets = CCinit(&C,EVAL_ASSETS); // NOTE: assets here! + if (txfee == 0) txfee = 10000; + mypk = pubkey2pk(Mypubkey()); - if ( AddNormalinputs(mtx,mypk,bidamount+txfee,64) > 0 ) + + if ((inputs = AddNormalinputs(mtx, mypk, bidamount+txfee, 64)) > 0) { - mtx.vout.push_back(MakeCC1vout(EVAL_ASSETS,bidamount,GetUnspendable(cp,0))); - return(FinalizeCCTx(0,cp,mtx,mypk,txfee,EncodeAssetOpRet('b',assetid,zeroid,pricetotal,Mypubkey()))); + std::cerr << "CreateBuyOffer() inputs=" << inputs << std::endl; + if (inputs < bidamount+txfee) { + std::cerr << "CreateBuyOffer(): insufficient coins to make buy offer" << std::endl; + CCerror = strprintf("insufficient coins to make buy offer"); + return (""); + } + + mtx.vout.push_back(MakeCC1vout(EVAL_ASSETS, bidamount, GetUnspendable(cpAssets,0))); + return(FinalizeCCTx(0, cpAssets, mtx, mypk, txfee, EncodeAssetOpRet('b', assetid, zeroid, pricetotal, Mypubkey()))); } + CCerror = strprintf("no coins found to make buy offer"); return(""); } +// rpc tokenask implementation, locks 'askamount' tokens for the 'pricetotal' std::string CreateSell(int64_t txfee,int64_t askamount,uint256 assetid,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; + uint64_t mask; + int64_t inputs, CCchange; + CScript opret; + struct CCcontract_info *cp,C; //std::cerr << "CreateSell() askamount=" << askamount << " pricetotal=" << pricetotal << std::endl; - if ( askamount < 0 || pricetotal < 0 ) - { + if (askamount < 0 || pricetotal < 0) { fprintf(stderr,"negative askamount %lld, askamount %lld\n",(long long)pricetotal,(long long)askamount); return(""); } - cp = CCinit(&C,EVAL_ASSETS); + + cp = CCinit(&C, EVAL_TOKENS); // NOTE: tokens is here + if ( txfee == 0 ) txfee = 10000; + mypk = pubkey2pk(Mypubkey()); - if ( AddNormalinputs(mtx,mypk,txfee,3) > 0 ) + if (AddNormalinputs(mtx, mypk, txfee, 3) > 0) { mask = ~((1LL << mtx.vin.size()) - 1); - if ( (inputs= AddAssetInputs(cp,mtx,mypk,assetid,askamount,60)) > 0 ) + if ((inputs = AddTokenCCInputs(cp, mtx, mypk, assetid, askamount, 60)) > 0) { if (inputs < askamount) { - //askamount = inputs; + //was: askamount = inputs; std::cerr << "CreateSell(): insufficient tokens for ask" << std::endl; + CCerror = strprintf("insufficient tokens for ask"); return (""); } - mtx.vout.push_back(MakeCC1vout(EVAL_ASSETS,askamount,GetUnspendable(cp,0))); - if ( inputs > askamount ) + mtx.vout.push_back(MakeCC1vout(EVAL_TOKENS,askamount, GetUnspendable(cp,0))); + if (inputs > askamount) CCchange = (inputs - askamount); - if ( CCchange != 0 ) - mtx.vout.push_back(MakeCC1vout(EVAL_ASSETS,CCchange,mypk)); - opret = EncodeAssetOpRet('s',assetid,zeroid,pricetotal,Mypubkey()); + if (CCchange != 0) + mtx.vout.push_back(MakeCC1vout(EVAL_TOKENS, CCchange, mypk)); + + opret = EncodeAssetOpRet('s',assetid, zeroid, pricetotal, Mypubkey()); return(FinalizeCCTx(mask,cp,mtx,mypk,txfee,opret)); } else { - fprintf(stderr, "need some assets to place ask\n"); + fprintf(stderr, "need some tokens to place ask\n"); } } else { // dimxy added 'else', because it was misleading message before @@ -363,34 +443,48 @@ std::string CreateSwap(int64_t txfee,int64_t askamount,uint256 assetid,uint256 a { CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); CPubKey mypk; uint64_t mask; int64_t inputs,CCchange; CScript opret; struct CCcontract_info *cp,C; + + ////////////////////////////////////////// fprintf(stderr,"asset swaps disabled\n"); return(""); + ////////////////////////////////////////// + if ( askamount < 0 || pricetotal < 0 ) { fprintf(stderr,"negative askamount %lld, askamount %lld\n",(long long)pricetotal,(long long)askamount); return(""); } - cp = CCinit(&C,EVAL_ASSETS); + cp = CCinit(&C, EVAL_ASSETS); + if ( txfee == 0 ) txfee = 10000; + mypk = pubkey2pk(Mypubkey()); - if ( AddNormalinputs(mtx,mypk,txfee,3) > 0 ) + + if (AddNormalinputs(mtx, mypk, txfee, 3) > 0) { mask = ~((1LL << mtx.vin.size()) - 1); - if ( (inputs= AddAssetInputs(cp,mtx,mypk,assetid,askamount,60)) > 0 ) + if ((inputs = AddAssetInputs(cp, mtx, mypk, assetid, askamount, 60)) > 0) { - if ( inputs < askamount ) - askamount = inputs; - mtx.vout.push_back(MakeCC1vout(EVAL_ASSETS,askamount,GetUnspendable(cp,0))); - if ( inputs > askamount ) + if (inputs < askamount) { + //was: askamount = inputs; + std::cerr << "CreateSwap(): insufficient tokens for ask" << std::endl; + CCerror = strprintf("insufficient tokens for ask"); + return (""); + } + + mtx.vout.push_back(MakeCC1vout(EVAL_ASSETS, askamount, GetUnspendable(cp, 0))); + + if (inputs > askamount) CCchange = (inputs - askamount); - if ( CCchange != 0 ) - mtx.vout.push_back(MakeCC1vout(EVAL_ASSETS,CCchange,mypk)); - if ( assetid2 == zeroid ) - opret = EncodeAssetOpRet('s',assetid,zeroid,pricetotal,Mypubkey()); + if (CCchange != 0) + mtx.vout.push_back(MakeCC1vout(EVAL_ASSETS, CCchange, mypk)); + + if (assetid2 == zeroid) + opret = EncodeAssetOpRet('s', assetid, zeroid, pricetotal, Mypubkey()); else { - opret = EncodeAssetOpRet('e',assetid,assetid2,pricetotal,Mypubkey()); + opret = EncodeAssetOpRet('e', assetid, assetid2, pricetotal, Mypubkey()); } return(FinalizeCCTx(mask,cp,mtx,mypk,txfee,opret)); } @@ -405,146 +499,213 @@ std::string CreateSwap(int64_t txfee,int64_t askamount,uint256 assetid,uint256 a return(""); } +// unlocks coins std::string CancelBuyOffer(int64_t txfee,uint256 assetid,uint256 bidtxid) { CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); - CTransaction vintx; uint64_t mask; uint256 hashBlock; int64_t bidamount; CPubKey mypk; struct CCcontract_info *cp,C; - cp = CCinit(&C,EVAL_ASSETS); + CTransaction vintx; + uint64_t mask; + uint256 hashBlock; + int64_t bidamount; + CPubKey mypk; + struct CCcontract_info *cp,C; + + cp = CCinit(&C, EVAL_ASSETS); + if ( txfee == 0 ) txfee = 10000; + mypk = pubkey2pk(Mypubkey()); - if ( AddNormalinputs(mtx,mypk,txfee,3) > 0 ) + + if (AddNormalinputs(mtx, mypk, txfee, 3) > 0) { mask = ~((1LL << mtx.vin.size()) - 1); - if ( GetTransaction(bidtxid,vintx,hashBlock,false) != 0 ) + if (GetTransaction(bidtxid, vintx, hashBlock, false) != 0) { bidamount = vintx.vout[0].nValue; - mtx.vin.push_back(CTxIn(bidtxid,0,CScript())); + mtx.vin.push_back(CTxIn(bidtxid, 0, CScript())); mtx.vout.push_back(CTxOut(bidamount,CScript() << ParseHex(HexStr(mypk)) << OP_CHECKSIG)); - return(FinalizeCCTx(mask,cp,mtx,mypk,txfee,EncodeAssetOpRet('o',assetid,zeroid,0,Mypubkey()))); + return(FinalizeCCTx(mask, cp, mtx, mypk, txfee, EncodeAssetOpRet('o', assetid, zeroid, 0, Mypubkey()))); } } return(""); } +//unlocks tokens std::string CancelSell(int64_t txfee,uint256 assetid,uint256 asktxid) { CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); - CTransaction vintx; uint64_t mask; uint256 hashBlock; int64_t askamount; CPubKey mypk; struct CCcontract_info *cp,C; - cp = CCinit(&C,EVAL_ASSETS); + CTransaction vintx; uint64_t mask; uint256 hashBlock; int64_t askamount; CPubKey mypk; + struct CCcontract_info *cp,C; + + cp = CCinit(&C, EVAL_TOKENS); + if ( txfee == 0 ) txfee = 10000; + mypk = pubkey2pk(Mypubkey()); - if ( AddNormalinputs(mtx,mypk,txfee,3) > 0 ) + + if (AddNormalinputs(mtx, mypk, txfee, 3) > 0) { mask = ~((1LL << mtx.vin.size()) - 1); - if ( GetTransaction(asktxid,vintx,hashBlock,false) != 0 ) + if (GetTransaction(asktxid, vintx, hashBlock, false) != 0) { askamount = vintx.vout[0].nValue; - mtx.vin.push_back(CTxIn(asktxid,0,CScript())); - mtx.vout.push_back(MakeCC1vout(EVAL_ASSETS,askamount,mypk)); - return(FinalizeCCTx(mask,cp,mtx,mypk,txfee,EncodeAssetOpRet('x',assetid,zeroid,0,Mypubkey()))); + mtx.vin.push_back(CTxIn(asktxid, 0, CScript())); + mtx.vout.push_back(MakeCC1vout(EVAL_TOKENS, askamount, mypk)); + return(FinalizeCCTx(mask, cp, mtx, mypk, txfee, EncodeAssetOpRet('x', assetid, zeroid, 0, Mypubkey()))); } } return(""); } +//send tokens, receive coins: std::string FillBuyOffer(int64_t txfee,uint256 assetid,uint256 bidtxid,int64_t fillamount) { CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); - CTransaction vintx; uint256 hashBlock; CPubKey mypk; std::vector origpubkey; int32_t bidvout=0; uint64_t mask; int64_t origprice,bidamount,paid_amount,remaining_required,inputs,CCchange=0; struct CCcontract_info *cp,C; - if ( fillamount < 0 ) + CTransaction vintx; + uint256 hashBlock; + CPubKey mypk; + std::vector origpubkey; + int32_t bidvout=0; + uint64_t mask; + int64_t origprice, bidamount, paid_amount, remaining_required, inputs, CCchange=0; + struct CCcontract_info *cp,C; + + if (fillamount < 0) { - fprintf(stderr,"negative fillamount %lld\n",(long long)fillamount); + fprintf(stderr,"negative fillamount %lld\n", (long long)fillamount); return(""); } - cp = CCinit(&C,EVAL_ASSETS); - if ( txfee == 0 ) + cp = CCinit(&C, EVAL_TOKENS); + + if ( txfee == 0 ) txfee = 10000; + mypk = pubkey2pk(Mypubkey()); - if ( AddNormalinputs(mtx,mypk,txfee,3) > 0 ) + + if (AddNormalinputs(mtx, mypk, txfee, 3) > 0) { mask = ~((1LL << mtx.vin.size()) - 1); - if ( GetTransaction(bidtxid,vintx,hashBlock,false) != 0 ) + if (GetTransaction(bidtxid, vintx, hashBlock, false) != 0) { bidamount = vintx.vout[bidvout].nValue; - SetAssetOrigpubkey(origpubkey,origprice,vintx); - mtx.vin.push_back(CTxIn(bidtxid,bidvout,CScript())); - if ( (inputs= AddAssetInputs(cp,mtx,mypk,assetid,fillamount,60)) > 0 ) + SetAssetOrigpubkey(origpubkey, origprice, vintx); + + mtx.vin.push_back(CTxIn(bidtxid, bidvout, CScript())); + + if ((inputs = AddTokenCCInputs(cp, mtx, mypk, assetid, fillamount, 60)) > 0) { - if ( inputs < fillamount ) - fillamount = inputs; - SetBidFillamounts(paid_amount,remaining_required,bidamount,fillamount,origprice); - if ( inputs > fillamount ) + if (inputs < fillamount) { + std::cerr << "FillBuyOffer(): insufficient tokens to fill buy offer" << std::endl; + CCerror = strprintf("insufficient tokens to fill buy offer"); + return (""); + } + + SetBidFillamounts(paid_amount, remaining_required, bidamount, fillamount, origprice); + + if (inputs > fillamount) CCchange = (inputs - fillamount); - mtx.vout.push_back(MakeCC1vout(EVAL_ASSETS,bidamount - paid_amount,GetUnspendable(cp,0))); + + mtx.vout.push_back(MakeCC1vout(EVAL_TOKENS, bidamount - paid_amount, GetUnspendable(cp,0))); // tokens mtx.vout.push_back(CTxOut(paid_amount,CScript() << ParseHex(HexStr(mypk)) << OP_CHECKSIG)); - mtx.vout.push_back(MakeCC1vout(EVAL_ASSETS,fillamount,pubkey2pk(origpubkey))); - if ( CCchange != 0 ) - mtx.vout.push_back(MakeCC1vout(EVAL_ASSETS,CCchange,mypk)); - fprintf(stderr,"remaining %llu -> origpubkey\n",(long long)remaining_required); - return(FinalizeCCTx(mask,cp,mtx,mypk,txfee,EncodeAssetOpRet('B',assetid,zeroid,remaining_required,origpubkey))); + mtx.vout.push_back(MakeCC1vout(EVAL_ASSETS, fillamount, pubkey2pk(origpubkey))); // coins + + if (CCchange != 0) + mtx.vout.push_back(MakeCC1vout(EVAL_ASSETS, CCchange, mypk)); // coins + + fprintf(stderr,"remaining %llu -> origpubkey\n", (long long)remaining_required); + + return(FinalizeCCTx(mask,cp,mtx,mypk,txfee, EncodeAssetOpRet('B', assetid, zeroid, remaining_required, origpubkey))); } else return("dont have any assets to fill bid\n"); } } return("no normal coins left"); } + +// send coins, receive tokens std::string FillSell(int64_t txfee,uint256 assetid,uint256 assetid2,uint256 asktxid,int64_t fillunits) { CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); - CTransaction vintx,filltx; uint256 hashBlock; CPubKey mypk; std::vector origpubkey; double dprice; uint64_t mask; int32_t askvout=0; int64_t received_assetoshis,total_nValue,orig_assetoshis,paid_nValue,remaining_nValue,inputs,CCchange=0; struct CCcontract_info *cp,C; - if ( fillunits < 0 ) + CTransaction vintx,filltx; + uint256 hashBlock; + CPubKey mypk; + std::vector origpubkey; + double dprice; + uint64_t mask; + int32_t askvout=0; + int64_t received_assetoshis, total_nValue, orig_assetoshis, paid_nValue, remaining_nValue, inputs, CCchange=0; + struct CCcontract_info *cpTokens, tokensC; + struct CCcontract_info *cpAssets, assetsC; + + if (fillunits < 0) { CCerror = strprintf("negative fillunits %lld\n",(long long)fillunits); fprintf(stderr,"%s\n",CCerror.c_str()); return(""); } - if ( assetid2 != zeroid ) + if (assetid2 != zeroid) { CCerror = "asset swaps disabled"; fprintf(stderr,"%s\n",CCerror.c_str()); return(""); } - cp = CCinit(&C,EVAL_ASSETS); - if ( txfee == 0 ) + cpTokens = CCinit(&tokensC, EVAL_TOKENS); + cpAssets = CCinit(&assetsC, EVAL_ASSETS); + + if (txfee == 0) txfee = 10000; + mypk = pubkey2pk(Mypubkey()); - if ( AddNormalinputs(mtx,mypk,txfee,3) > 0 ) + if (AddNormalinputs(mtx,mypk,txfee,3) > 0) { mask = ~((1LL << mtx.vin.size()) - 1); - if ( GetTransaction(asktxid,vintx,hashBlock,false) != 0 ) + if (GetTransaction(asktxid, vintx, hashBlock, false) != 0) { orig_assetoshis = vintx.vout[askvout].nValue; - SetAssetOrigpubkey(origpubkey,total_nValue,vintx); + SetAssetOrigpubkey(origpubkey, total_nValue, vintx); dprice = (double)total_nValue / orig_assetoshis; paid_nValue = dprice * fillunits; - mtx.vin.push_back(CTxIn(asktxid,askvout,CScript())); - if ( assetid2 != zeroid ) - inputs = AddAssetInputs(cp,mtx,mypk,assetid2,paid_nValue,60); + + mtx.vin.push_back(CTxIn(asktxid, askvout, CScript())); // NOTE: this is the reference to tokens -> send cpTokens for signing into FinalizeCCTx! + + if (assetid2 != zeroid) + inputs = AddAssetInputs(cpTokens, mtx, mypk, assetid2, paid_nValue, 60); else { - inputs = AddNormalinputs(mtx,mypk,paid_nValue,60); + inputs = AddNormalinputs(mtx, mypk, paid_nValue, 60); mask = ~((1LL << mtx.vin.size()) - 1); } - if ( inputs > 0 ) + if (inputs > 0) { - if ( inputs < paid_nValue ) - paid_nValue = inputs; - if ( assetid2 != zeroid ) - SetSwapFillamounts(received_assetoshis,remaining_nValue,orig_assetoshis,paid_nValue,total_nValue); - else SetAskFillamounts(received_assetoshis,remaining_nValue,orig_assetoshis,paid_nValue,total_nValue); - if ( assetid2 != zeroid && inputs > paid_nValue ) + if (inputs < paid_nValue) { + std::cerr << "FillSell(): insufficient coins to fill sell" << std::endl; + CCerror = strprintf("insufficient coins to fill sell"); + return (""); + } + + if (assetid2 != zeroid) + SetSwapFillamounts(received_assetoshis, remaining_nValue, orig_assetoshis, paid_nValue, total_nValue); + else + SetAskFillamounts(received_assetoshis, remaining_nValue, orig_assetoshis, paid_nValue, total_nValue); + + if (assetid2 != zeroid && inputs > paid_nValue) CCchange = (inputs - paid_nValue); - mtx.vout.push_back(MakeCC1vout(EVAL_ASSETS,orig_assetoshis - received_assetoshis,GetUnspendable(cp,0))); - mtx.vout.push_back(MakeCC1vout(EVAL_ASSETS,received_assetoshis,mypk)); - if ( assetid2 != zeroid ) - mtx.vout.push_back(MakeCC1vout(EVAL_ASSETS,paid_nValue,origpubkey)); - else mtx.vout.push_back(CTxOut(paid_nValue,CScript() << origpubkey << OP_CHECKSIG)); - if ( CCchange != 0 ) - mtx.vout.push_back(MakeCC1vout(EVAL_ASSETS,CCchange,mypk)); - return(FinalizeCCTx(mask,cp,mtx,mypk,txfee,EncodeAssetOpRet(assetid2!=zeroid?'E':'S',assetid,assetid2,remaining_nValue,origpubkey))); + + mtx.vout.push_back(MakeCC1vout(EVAL_ASSETS, orig_assetoshis - received_assetoshis, GetUnspendable(cpAssets, NULL))); // coins + mtx.vout.push_back(MakeCC1vout(EVAL_TOKENS, received_assetoshis, mypk)); // tokens + + if (assetid2 != zeroid) + mtx.vout.push_back(MakeCC1vout(EVAL_TOKENS, paid_nValue, origpubkey)); // tokens (not implemented correctly) + else + mtx.vout.push_back(CTxOut(paid_nValue, CScript() << origpubkey << OP_CHECKSIG)); // coins + + if (CCchange != 0) + mtx.vout.push_back(MakeCC1vout(EVAL_ASSETS, CCchange, mypk)); // coins + + return(FinalizeCCTx(mask,cpTokens,mtx,mypk,txfee,EncodeAssetOpRet(assetid2 != zeroid ? 'E' : 'S', assetid, assetid2, remaining_nValue, origpubkey))); } else { CCerror = strprintf("filltx not enough utxos"); fprintf(stderr,"%s\n", CCerror.c_str()); diff --git a/src/cc/CCcustom.cpp b/src/cc/CCcustom.cpp index 7fdd1b920..918ceb24a 100644 --- a/src/cc/CCcustom.cpp +++ b/src/cc/CCcustom.cpp @@ -30,6 +30,7 @@ #include "CCMarmara.h" #include "CCPayments.h" #include "CCGateways.h" +#include "CCtokens.h" /* CCcustom has most of the functions that need to be extended to create a new CC contract. @@ -222,6 +223,18 @@ uint8_t GatewaysCCpriv[32] = { 0xf7, 0x4b, 0x5b, 0xa2, 0x7a, 0x5e, 0x9c, 0xda, 0 #undef FUNCNAME #undef EVALCODE +// Tokens +#define FUNCNAME IsTokensInput +#define EVALCODE EVAL_TOKENS +const char *TokensCCaddr = "RAMvUfoyURBRxAdVeTMHxn3giJZCFWeha2"; +const char *TokensNormaladdr = "RCNgAngYAdrfzujYyPgfbjCGNVQZzCgTad"; +char TokensCChexstr[67] = { "03e6191c70c9c9a28f9fd87089b9488d0e6c02fb629df64979c9cdb6b2b4a68d95" }; +uint8_t TokensCCpriv[32] = { 0x1d, 0x0d, 0x0d, 0xce, 0x2d, 0xd2, 0xe1, 0x9d, 0xf5, 0xb6, 0x26, 0xd5, 0xad, 0xa0, 0xf0, 0x0a, 0xdd, 0x7a, 0x72, 0x7d, 0x17, 0x35, 0xb5, 0xe3, 0x2c, 0x6c, 0xa9, 0xa2, 0x03, 0x16, 0x4b, 0xcf }; +#include "CCcustom.inc" +#undef FUNCNAME +#undef EVALCODE + + struct CCcontract_info *CCinit(struct CCcontract_info *cp, uint8_t evalcode) { cp->evalcode = evalcode; @@ -347,6 +360,15 @@ struct CCcontract_info *CCinit(struct CCcontract_info *cp, uint8_t evalcode) cp->validate = GatewaysValidate; cp->ismyvin = IsGatewaysInput; break; + + case EVAL_TOKENS: + strcpy(cp->unspendableCCaddr, TokensCCaddr); + strcpy(cp->normaladdr, TokensNormaladdr); + strcpy(cp->CChexstr, TokensCChexstr); + memcpy(cp->CCpriv, TokensCCpriv, 32); + cp->validate = TokensValidate; + cp->ismyvin = IsTokensInput; + break; } return(cp); } diff --git a/src/cc/CCinclude.h b/src/cc/CCinclude.h index 40028ee5c..56c4b27d5 100644 --- a/src/cc/CCinclude.h +++ b/src/cc/CCinclude.h @@ -133,13 +133,22 @@ int64_t OraclePrice(int32_t height,uint256 reforacletxid,char *markeraddr,char * uint8_t DecodeOraclesCreateOpRet(const CScript &scriptPubKey,std::string &name,std::string &description,std::string &format); uint256 OracleMerkle(int32_t height,uint256 reforacletxid,char *format,std::vectorpublishers); uint256 OraclesBatontxid(uint256 oracletxid,CPubKey pk); -int64_t AddAssetInputs(struct CCcontract_info *cp,CMutableTransaction &mtx,CPubKey pk,uint256 assetid,int64_t total,int32_t maxinputs); + +//int64_t AddAssetInputs(struct CCcontract_info *cp,CMutableTransaction &mtx,CPubKey pk,uint256 assetid,int64_t total,int32_t maxinputs); +int64_t AddTokenCCInputs(struct CCcontract_info *cp, CMutableTransaction &mtx, CPubKey pk, uint256 tokenid, int64_t total, int32_t maxinputs); + bool DecodeHexTx(CTransaction& tx, const std::string& strHexTx); -bool DecodeAssetCreateOpRet(const CScript &scriptPubKey,std::vector &origpubkey,std::string &name,std::string &description); -uint8_t DecodeAssetOpRet(const CScript &scriptPubKey,uint256 &assetid,uint256 &assetid2,int64_t &price,std::vector &origpubkey); +CScript EncodeAssetOpRet(uint8_t assetFuncId, uint256 tokenid, uint256 assetid2, int64_t price, std::vector origpubkey); +bool DecodeAssetCreateOpRet(const CScript &scriptPubKey, std::vector &origpubkey, std::string &name, std::string &description); +uint8_t DecodeAssetOpRet(const CScript &scriptPubKey, uint8_t &evalCodeInOpret, uint256 &tokenid, uint256 &assetid2, int64_t &price, std::vector &origpubkey); +uint8_t DecodeTokenCreateOpRet(const CScript &scriptPubKey, std::vector &origpubkey, std::string &name, std::string &description); +uint8_t DecodeTokenOpRet(const CScript scriptPubKey, uint8_t &evalCode, uint256 &tokenid, std::vector &vopretExtra); + +//uint8_t DecodeAssetOpRet(const CScript &scriptPubKey, uint8_t &evalCode, uint256 &assetid,uint256 &assetid2,int64_t &price,std::vector &origpubkey); uint8_t DecodeOraclesData(const CScript &scriptPubKey,uint256 &oracletxid,uint256 &batontxid,CPubKey &pk,std::vector &data); int32_t oracle_format(uint256 *hashp,int64_t *valp,char *str,uint8_t fmt,uint8_t *data,int32_t offset,int32_t datalen); -CScript EncodeAssetOpRet(uint8_t funcid,uint256 assetid,uint256 assetid2,int64_t price,std::vector origpubkey); + + // CCcustom CPubKey GetUnspendable(struct CCcontract_info *cp,uint8_t *unspendablepriv); diff --git a/src/cc/CCtokens.cpp b/src/cc/CCtokens.cpp new file mode 100644 index 000000000..c85297ddb --- /dev/null +++ b/src/cc/CCtokens.cpp @@ -0,0 +1,487 @@ +/****************************************************************************** + * Copyright © 2014-2018 The SuperNET Developers. * + * * + * See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at * + * the top-level directory of this distribution for the individual copyright * + * holder information and the developer policies on copyright and licensing. * + * * + * Unless otherwise agreed in a custom licensing agreement, no part of the * + * SuperNET software, including this file may be copied, modified, propagated * + * or distributed except according to the terms contained in the LICENSE file * + * * + * Removal or modification of this copyright notice is prohibited. * + * * + ******************************************************************************/ + +#include "CCtokens.h" + +/* TODO: correct this: +----------------------------- + The SetTokenFillamounts() and ValidateTokenRemainder() work in tandem to calculate the vouts for a fill and to validate the vouts, respectively. + + This pair of functions are critical to make sure the trading is correct and is the trickiest part of the tokens contract. + + //vin.0: normal input + //vin.1: unspendable.(vout.0 from buyoffer) buyTx.vout[0] + //vin.2+: valid CC output satisfies buyoffer (*tx.vin[2])->nValue + //vout.0: remaining amount of bid to unspendable + //vout.1: vin.1 value to signer of vin.2 + //vout.2: vin.2 tokenoshis to original pubkey + //vout.3: CC output for tokenoshis change (if any) + //vout.4: normal output for change (if any) + //vout.n-1: opreturn [EVAL_ASSETS] ['B'] [tokenid] [remaining token required] [origpubkey] + ValidateTokenRemainder(remaining_price,tx.vout[0].nValue,nValue,tx.vout[1].nValue,tx.vout[2].nValue,totalunits); + + Yes, this is quite confusing... + + In ValudateTokenRemainder the naming convention is nValue is the coin/token with the offer on the books and "units" is what it is being paid in. The high level check is to make sure we didnt lose any coins or tokens, the harder to validate is the actual price paid as the "orderbook" is in terms of the combined nValue for the combined totalunits. + + We assume that the effective unit cost in the orderbook is valid and that that amount was paid and also that any remainder will be close enough in effective unit cost to not matter. At the edge cases, this will probably be not true and maybe some orders wont be practically fillable when reduced to fractional state. However, the original pubkey that created the offer can always reclaim it. + ------------------------------ +*/ + + +// NOTE: this inital tx won't be used by other contract +// for tokens to be used there should be at least one 't' tx with other contract's custom opret +CScript EncodeTokenCreateOpRet(uint8_t funcid,std::vector origpubkey,std::string name,std::string description) +{ + CScript opret; uint8_t evalcode = EVAL_TOKENS; + opret << OP_RETURN << E_MARSHAL(ss << evalcode << funcid << origpubkey << name << description); + return(opret); +} + +// this is for other contracts which use tokens and build customized extra payloads to token's opret: +CScript EncodeTokenOpRet(uint8_t tokenFuncId, uint8_t evalCodeInOpret, uint256 tokenid, std::vector payload) +{ + CScript opret; + //uint8_t evalcode = EVAL_TOKENS; + tokenid = revuint256(tokenid); + //uint8_t tokenFuncId = (isTransferrable) ? (uint8_t)'t' : (uint8_t)'l'; + + opret << OP_RETURN << E_MARSHAL(ss << evalCodeInOpret << tokenFuncId << tokenid << payload); + return(opret); +} + +uint8_t DecodeTokenCreateOpRet(const CScript &scriptPubKey,std::vector &origpubkey,std::string &name,std::string &description) +{ + std::vector vopret; uint8_t dummyEvalcode, funcid, *script; + + GetOpReturnData(scriptPubKey, vopret); + script = (uint8_t *)vopret.data(); + if ( script != 0 && vopret.size() > 2 && script[0] == EVAL_TOKENS && script[1] == 'c' ) + { + if ( E_UNMARSHAL(vopret, ss >> dummyEvalcode; ss >> funcid; ss >> origpubkey; ss >> name; ss >> description) != 0 ) + return(funcid); + } + return (uint8_t)0; +} + +uint8_t DecodeTokenOpRet(const CScript scriptPubKey, uint8_t &evalCode, uint256 &tokenid, std::vector &vopretExtra) +{ + std::vector vopret, extra, dummyPubkey; + uint8_t funcid=0, *script, e, dummyFuncId; + std::string dummyName; std::string dummyDescription; + + GetOpReturnData(scriptPubKey, vopret); + script = (uint8_t *)vopret.data(); + tokenid = zeroid; + + if (script != 0 /*enable all evals: && script[0] == EVAL_TOKENS*/) + { + bool isEof = true; + evalCode = script[0]; + funcid = script[1]; + //fprintf(stderr,"decode.[%c]\n",funcid); + switch ( funcid ) + { + case 'c': + return DecodeTokenCreateOpRet(scriptPubKey, dummyPubkey, dummyName, dummyDescription); + //break; + case 't': + //not used yet: case 'l': + if (E_UNMARSHAL(vopret, ss >> e; ss >> dummyFuncId; ss >> tokenid; isEof = ss.eof(); vopretExtra = std::vector(ss.begin(), ss.end())) || !isEof) + { + tokenid = revuint256(tokenid); + return(funcid); + } + std::cerr << "DecodeTokenOpRet() isEof=" << isEof << std::endl; + fprintf(stderr, "DecodeTokenOpRet() bad opret format\n"); // this may be just check, no error logging + return (uint8_t)0; + + default: + fprintf(stderr, "DecodeTokenOpRet() illegal funcid.%02x\n", funcid); + return (uint8_t)0; + } + } + return (uint8_t)0; +} + + + +// tx validation +bool TokensValidate(struct CCcontract_info *cp, Eval* eval, const CTransaction &tx, uint32_t nIn) +{ + static uint256 zero; + CTxDestination address; CTransaction vinTx, createTx; uint256 hashBlock, tokenid, tokenid2; + int32_t i, starti, numvins, numvouts, preventCCvins, preventCCvouts; + int64_t remaining_price, nValue, tokenoshis, outputs, inputs, tmpprice, totalunits, ignore; std::vector origpubkey, tmporigpubkey, ignorepubkey; + uint8_t funcid, evalCodeInOpret; + char destaddr[64], origaddr[64], CCaddr[64]; + + numvins = tx.vin.size(); + numvouts = tx.vout.size(); + outputs = inputs = 0; + preventCCvins = preventCCvouts = -1; + + if ((funcid = DecodeTokenOpRet(tx.vout[numvouts - 1].scriptPubKey, evalCodeInOpret, tokenid, origpubkey)) == 0) + return eval->Invalid("TokenValidate: invalid opreturn payload"); + + fprintf(stderr, "TokensValidate (%c)\n", funcid); + + 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"); // why? (dimxy) + else if (numvouts < 1) + return eval->Invalid("no vouts"); + else if (funcid != 'c') + { + if (tokenid == zeroid) + return eval->Invalid("illegal tokenid"); + else if (!TokensExactAmounts(true, cp, inputs, outputs, eval, tx, tokenid)) { + if (!eval->Valid()) + return false; //TokenExactAmounts must call eval->Invalid()! + else + return eval->Invalid("tokens cc inputs != cc outputs"); + } + } + + // init for forwarding validation call + struct CCcontract_info *cpOther = NULL, C; + if (evalCodeInOpret != EVAL_TOKENS) + cpOther = CCinit(&C, evalCodeInOpret); + + switch (funcid) + { + case 'c': // create wont be called to be verified as it has no CC inputs + //vin.0: normal input + //vout.0: issuance tokenoshis to CC + //vout.1: normal output for change (if any) + //vout.n-1: opreturn EVAL_TOKENS 'c' + if (evalCodeInOpret != EVAL_TOKENS) + return eval->Invalid("unexpected TokenValidate for createtoken"); + else + return true; + + case 't': // transfer + //vin.0: normal input + //vin.1 .. vin.n-1: valid CC outputs + //vout.0 to n-2: tokenoshis output to CC + //vout.n-2: normal output for change (if any) + //vout.n-1: opreturn 't' tokenid + if (inputs == 0) + return eval->Invalid("no token inputs for transfer"); + + fprintf(stderr, "token transfer preliminarily validated %.8f -> %.8f (%d %d)\n", (double)inputs / COIN, (double)outputs / COIN, preventCCvins, preventCCvouts); + break; // breaking to other contract validation... + + default: + fprintf(stderr, "illegal tokens funcid.(%c)\n", funcid); + return eval->Invalid("unexpected token funcid"); + } + + // forward validation if evalcode in opret is not EVAL_TOKENS + if (cpOther) + return cpOther->validate(cpOther, eval, tx, nIn); + else + return eval->Invalid("unsupported evalcode in opret"); + + // what does this do? + // return(PreventCC(eval,tx,preventCCvins,numvins,preventCCvouts,numvouts)); +} + + + +// this is just for log messages indentation fur debugging recursive calls: +thread_local uint32_t tokenValIndentSize = 0; + +// validates opret for token tx: +bool ValidateTokenOpret(CTransaction tx, int32_t v, uint256 tokenid, std::vector &vopretExtra) { + + uint256 tokenidOpret, tokenidOpret2; + uint8_t funcid, evalCode; + + // this is just for log messages indentation fur debugging recursive calls: + std::string indentStr = std::string().append(tokenValIndentSize, '.'); + + int32_t n = tx.vout.size(); + + if ((funcid = DecodeTokenOpRet(tx.vout[n - 1].scriptPubKey, evalCode, tokenidOpret, vopretExtra)) == 0) + { + std::cerr << indentStr << "ValidateTokenOpret() DecodeOpret returned null for n-1=" << n - 1 << " txid=" << tx.GetHash().GetHex() << std::endl; + return(false); + } + else if (funcid == 'c') + { + if (tokenid != zeroid && tokenid == tx.GetHash() && v == 0) { + //std::cerr << indentStr << "ValidateTokenOpret() this is the tokenbase 'c' tx, txid=" << tx.GetHash().GetHex() << " vout=" << v << " returning true" << std::endl; + return(true); + } + } + else if (funcid == 't') + { + //std::cerr << indentStr << "ValidateTokenOpret() tokenid=" << tokenid.GetHex() << " tokenIdOpret=" << tokenidOpret.GetHex() << " txid=" << tx.GetHash().GetHex() << std::endl; + if (tokenid != zeroid && tokenid == tokenidOpret) { + //std::cerr << indentStr << "ValidateTokenOpret() this is a transfer 't' tx, txid=" << tx.GetHash().GetHex() << " vout=" << v << " returning true" << std::endl; + return(true); + } + } + //std::cerr << indentStr << "ValidateTokenOpret() return false funcid=" << (char)funcid << " tokenid=" << tokenid.GetHex() << " tokenIdOpret=" << tokenidOpret.GetHex() << " txid=" << tx.GetHash().GetHex() << std::endl; + return false; +} + + +// Checks if the vout is a really Tokens CC vout +// compareTotals == true, the func also validates the passed transaction itself: +// it should be either sum(cc vins) == sum(cc vouts) or the transaction is the 'tokenbase' ('c') tx +int64_t IsTokensvout(bool compareTotals, struct CCcontract_info *cp, Eval* eval, std::vector &vopretExtra, const CTransaction& tx, int32_t v, uint256 reftokenid) +{ + + // this is just for log messages indentation fur debugging recursive calls: + std::string indentStr = std::string().append(tokenValIndentSize, '.'); + //std::cerr << indentStr << "IsTokensvout() entered for txid=" << tx.GetHash().GetHex() << " v=" << v << " for tokenid=" << reftokenid.GetHex() << std::endl; + + //TODO: validate cc vouts are EVAL_TOKENS! + if (tx.vout[v].scriptPubKey.IsPayToCryptoCondition() != 0) // maybe check address too? dimxy: possibly no, because there are too many cases with different addresses here + { + int32_t n = tx.vout.size(); + // just check boundaries: + if (v >= n - 1) { // just moved this up (dimxy) + std::cerr << indentStr << "isTokensvout() internal err: (v >= n - 1), returning 0" << std::endl; + return(0); + } + + if (compareTotals) { + //std::cerr << indentStr << "IsTokensvout() maxTokenExactAmountDepth=" << maxTokenExactAmountDepth << std::endl; + //validate all tx + int64_t myCCVinsAmount = 0, myCCVoutsAmount = 0; + + tokenValIndentSize++; + // false --> because we already at the 1-st level ancestor tx and do not need to dereference ancestors of next levels + bool isEqual = TokensExactAmounts(false, cp, myCCVinsAmount, myCCVoutsAmount, eval, tx, reftokenid); + tokenValIndentSize--; + + if (!isEqual) { + // if ccInputs != ccOutputs and it is not the tokenbase tx + // this means it is possibly a fake tx (dimxy): + if (reftokenid != tx.GetHash()) { // checking that this is the true tokenbase tx, by verifying that funcid=c, is done further in this function (dimxy) + std::cerr << indentStr << "IsTokensvout() warning: for the verified tx detected a bad vintx=" << tx.GetHash().GetHex() << ": cc inputs != cc outputs and not the 'tokenbase' tx, skipping the verified tx" << std::endl; + return 0; + } + } + } + + // moved opret checking to this new reusable func (dimxy): + const bool valOpret = ValidateTokenOpret(tx, v, reftokenid, vopretExtra); + //std::cerr << indentStr << "IsTokensvout() ValidateTokenOpret returned=" << std::boolalpha << valOpret << " for txid=" << tx.GetHash().GetHex() << " for tokenid=" << reftokenid.GetHex() << std::endl; + if (valOpret) { + //std::cerr << indentStr << "IsTokensvout() ValidateTokenOpret returned true, returning nValue=" << tx.vout[v].nValue << " for txid=" << tx.GetHash().GetHex() << " for tokenid=" << reftokenid.GetHex() << std::endl; + return tx.vout[v].nValue; + } + + //std::cerr << indentStr; fprintf(stderr,"IsTokensvout() CC vout v.%d of n=%d amount=%.8f txid=%s\n",v,n,(double)0/COIN, tx.GetHash().GetHex().c_str()); + } + //std::cerr << indentStr; fprintf(stderr,"IsTokensvout() normal output v.%d %.8f\n",v,(double)tx.vout[v].nValue/COIN); + return(0); +} + +// compares cc inputs vs cc outputs (to prevent feeding vouts from normal inputs) +bool TokensExactAmounts(bool compareTotals, struct CCcontract_info *cpTokens, int64_t &inputs, int64_t &outputs, Eval* eval, const CTransaction &tx, uint256 tokenid) +{ + CTransaction vinTx; uint256 hashBlock, id, id2; int32_t flag; int64_t tokenoshis; std::vector tmporigpubkey; int64_t tmpprice; + int32_t numvins = tx.vin.size(); + int32_t numvouts = tx.vout.size(); + inputs = outputs = 0; + + // this is just for log messages indentation for debugging recursive calls: + std::string indentStr = std::string().append(tokenValIndentSize, '.'); + + for (int32_t i = 0; iismyvin)(tx.vin[i].scriptSig) /*|| IsVinAllowed(tx.vin[i].scriptSig) != 0*/) + { + //std::cerr << indentStr << "TokensExactAmounts() eval is true=" << (eval != NULL) << " ismyvin=ok for_i=" << i << std::endl; + // we are not inside the validation code -- dimxy + if ((eval && eval->GetTxUnconfirmed(tx.vin[i].prevout.hash, vinTx, hashBlock) == 0) || (!eval && !myGetTransaction(tx.vin[i].prevout.hash, vinTx, hashBlock))) + { + std::cerr << indentStr << "TokensExactAmounts() cannot read vintx for i." << i << " numvins." << numvins << std::endl; + return (!eval) ? false : eval->Invalid("always should find vin tx, but didnt"); + + } + else { + tokenValIndentSize++; + // validate vouts of vintx + //std::cerr << indentStr << "TokenExactAmounts() check vin i=" << i << " nValue=" << vinTx.vout[tx.vin[i].prevout.n].nValue << std::endl; + tokenoshis = IsTokensvout(compareTotals, cpTokens, eval, tmporigpubkey, vinTx, tx.vin[i].prevout.n, tokenid); + tokenValIndentSize--; + if (tokenoshis != 0) + { + std::cerr << indentStr << "TokensExactAmounts() vin i=" << i << " tokenoshis=" << tokenoshis << std::endl; + inputs += tokenoshis; + } + } + } + } + + + for (int32_t i = 0; iInvalid() here! + } + else + return true; +} + +// add inputs from token cc addr +int64_t AddTokenCCInputs(struct CCcontract_info *cp, CMutableTransaction &mtx, CPubKey pk, uint256 tokenid, int64_t total, int32_t maxinputs) +{ + char coinaddr[64], destaddr[64]; + int64_t threshold, nValue, price, totalinputs = 0; + uint256 txid, hashBlock; + std::vector vopretExtra; + CTransaction vintx; + int32_t j, vout, n = 0; + std::vector > unspentOutputs; + + GetCCaddress(cp, coinaddr, pk); + SetCCunspents(unspentOutputs, coinaddr); + + threshold = total / (maxinputs != 0 ? maxinputs : 64); // TODO: is maxinputs really could not be over 64? what if i want to calc total balance? + + for (std::vector >::const_iterator it = unspentOutputs.begin(); it != unspentOutputs.end(); it++) + { + txid = it->first.txhash; + vout = (int32_t)it->first.index; + if (it->second.satoshis < threshold) + continue; + for (j = 0; junspendableCCaddr) != 0 && strcmp(destaddr, cp->unspendableaddr2) != 0) + continue; + fprintf(stderr, "AddTokenCCInputs() check destaddress=%s vout amount=%.8f\n", destaddr, (double)vintx.vout[vout].nValue / COIN); + if ((nValue = IsTokensvout(true, cp, NULL, vopretExtra, vintx, vout, tokenid)) > 0 && myIsutxo_spentinmempool(txid, vout) == 0) + { + if (total != 0 && maxinputs != 0) + mtx.vin.push_back(CTxIn(txid, vout, CScript())); + nValue = it->second.satoshis; + totalinputs += nValue; + //std::cerr << "AddTokenInputs() adding input nValue=" << nValue << std::endl; + n++; + if ((total > 0 && totalinputs >= total) || (maxinputs > 0 && n >= maxinputs)) + break; + } + } + } + + //std::cerr << "AddTokenInputs() found totalinputs=" << totalinputs << std::endl; + return(totalinputs); +} + + +std::string CreateToken(int64_t txfee, int64_t assetsupply, std::string name, std::string description) +{ + CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); + CPubKey mypk; struct CCcontract_info *cp, C; + if (assetsupply < 0) + { + fprintf(stderr, "negative assetsupply %lld\n", (long long)assetsupply); + return(""); + } + + cp = CCinit(&C, EVAL_TOKENS); + if (name.size() > 32 || description.size() > 4096) + { + fprintf(stderr, "name.%d or description.%d is too big\n", (int32_t)name.size(), (int32_t)description.size()); + return(""); + } + if (txfee == 0) + txfee = 10000; + mypk = pubkey2pk(Mypubkey()); + + if (AddNormalinputs(mtx, mypk, assetsupply + 2 * txfee, 64) > 0) + { + mtx.vout.push_back(MakeCC1vout(EVAL_TOKENS, assetsupply, mypk)); + mtx.vout.push_back(CTxOut(txfee, CScript() << ParseHex(cp->CChexstr) << OP_CHECKSIG)); + return(FinalizeCCTx(0, cp, mtx, mypk, txfee, EncodeTokenCreateOpRet('c', Mypubkey(), name, description))); + } + return(""); +} + + +std::string TokenTransfer(int64_t txfee, uint256 assetid, std::vector destpubkey, int64_t total) +{ + CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); + CPubKey mypk; uint64_t mask; int64_t CCchange = 0, inputs = 0; struct CCcontract_info *cp, C; + std::vector emptyExtraOpret; + + if (total < 0) + { + fprintf(stderr, "negative total %lld\n", (long long)total); + return(""); + } + cp = CCinit(&C, EVAL_TOKENS); + if (txfee == 0) + txfee = 10000; + mypk = pubkey2pk(Mypubkey()); + if (AddNormalinputs(mtx, mypk, txfee, 3) > 0) + { + //n = outputs.size(); + //if ( n == amounts.size() ) + //{ + // for (i=0; i 0) + { + + if (inputs < total) { //added dimxy + std::cerr << "AssetTransfer(): insufficient funds" << std::endl; + return (""); + } + if (inputs > total) + CCchange = (inputs - total); + //for (i=0; i &origpubkey, const CTransaction& tx, int32_t v, uint256 reftokenid); +std::string CreateToken(int64_t txfee, int64_t assetsupply, std::string name, std::string description); +std::string TokenTransfer(int64_t txfee, uint256 assetid, std::vector destpubkey, int64_t total); + +//this is in CCinclude.h int64_t AddTokenInputs(struct CCcontract_info *cp, CMutableTransaction &mtx, CPubKey pk, uint256 tokenid, int64_t total, int32_t maxinputs); + +//this is in CCinclude.h uint8_t DecodeTokenCreateOpRet(const CScript &scriptPubKey,std::vector &origpubkey,std::string &name,std::string &description); + +/* +// CCassetsCore +CScript EncodeAssetCreateOpRet(uint8_t funcid,std::vector origpubkey,std::string name,std::string description); +CScript EncodeAssetOpRet(uint8_t funcid,uint256 assetid,uint256 assetid2,int64_t price,std::vector origpubkey); +bool DecodeAssetCreateOpRet(const CScript &scriptPubKey,std::vector &origpubkey,std::string &name,std::string &description); +//uint8_t DecodeAssetOpRet(const CScript &scriptPubKey, uint8_t &evalCode, uint256 &assetid, uint256 &assetid2, int64_t &price, std::vector &origpubkey); +bool SetAssetOrigpubkey(std::vector &origpubkey,int64_t &price,const CTransaction &tx); +int64_t IsAssetvout(bool compareTotals, struct CCcontract_info *cp, Eval* eval, int64_t &price, std::vector &origpubkey, const CTransaction& tx, int32_t v, uint256 refassetid); +bool ValidateBidRemainder(int64_t remaining_price,int64_t remaining_nValue,int64_t orig_nValue,int64_t received_nValue,int64_t paidprice,int64_t totalprice); +bool ValidateAskRemainder(int64_t remaining_price,int64_t remaining_nValue,int64_t orig_nValue,int64_t received_nValue,int64_t paidprice,int64_t totalprice); +bool ValidateSwapRemainder(int64_t remaining_price,int64_t remaining_nValue,int64_t orig_nValue,int64_t received_nValue,int64_t paidprice,int64_t totalprice); +bool SetBidFillamounts(int64_t &paid,int64_t &remaining_price,int64_t orig_nValue,int64_t &received,int64_t totalprice); +bool SetAskFillamounts(int64_t &paid,int64_t &remaining_price,int64_t orig_nValue,int64_t &received,int64_t totalprice); +bool SetSwapFillamounts(int64_t &paid,int64_t &remaining_price,int64_t orig_nValue,int64_t &received,int64_t totalprice); +int64_t AssetValidateBuyvin(struct CCcontract_info *cp,Eval* eval,int64_t &tmpprice,std::vector &tmporigpubkey,char *CCaddr,char *origaddr,const CTransaction &tx,uint256 refassetid); +int64_t AssetValidateSellvin(struct CCcontract_info *cp,Eval* eval,int64_t &tmpprice,std::vector &tmporigpubkey,char *CCaddr,char *origaddr,const CTransaction &tx,uint256 assetid); +bool AssetExactAmounts(bool compareTotals, struct CCcontract_info *cpAssets, int64_t &inputs, int64_t &outputs, Eval* eval, const CTransaction &tx, uint256 assetid); + +// CCassetstx +int64_t GetAssetBalance(CPubKey pk,uint256 tokenid); +int64_t AddAssetInputs(CMutableTransaction &mtx,CPubKey pk,uint256 assetid,int64_t total,int32_t maxinputs); +UniValue AssetOrders(uint256 tokenid); +UniValue AssetInfo(uint256 tokenid); +UniValue AssetList(); +std::string CreateAsset(int64_t txfee,int64_t assetsupply,std::string name,std::string description); +std::string AssetTransfer(int64_t txfee,uint256 assetid,std::vector destpubkey,int64_t total); +std::string AssetConvert(int64_t txfee,uint256 assetid,std::vector destpubkey,int64_t total,int32_t evalcode); + +std::string CreateBuyOffer(int64_t txfee,int64_t bidamount,uint256 assetid,int64_t pricetotal); +std::string CancelBuyOffer(int64_t txfee,uint256 assetid,uint256 bidtxid); +std::string FillBuyOffer(int64_t txfee,uint256 assetid,uint256 bidtxid,int64_t fillamount); +std::string CreateSell(int64_t txfee,int64_t askamount,uint256 assetid,int64_t pricetotal); +std::string CreateSwap(int64_t txfee,int64_t askamount,uint256 assetid,uint256 assetid2,int64_t pricetotal); +std::string CancelSell(int64_t txfee,uint256 assetid,uint256 asktxid); +std::string FillSell(int64_t txfee,uint256 assetid,uint256 assetid2,uint256 asktxid,int64_t fillamount); +*/ + +#endif diff --git a/src/cc/CCtx.cpp b/src/cc/CCtx.cpp index a80b3b3cf..57a2a327b 100644 --- a/src/cc/CCtx.cpp +++ b/src/cc/CCtx.cpp @@ -144,9 +144,9 @@ std::string FinalizeCCTx(uint64_t CCmask,struct CCcontract_info *cp,CMutableTran { //fprintf(stderr,"matched %s unspendable2!\n",cp->unspendableaddr2); privkey = cp->unspendablepriv2; - if ( othercond2 == 0 && cp->evalcode != EVAL_CHANNELS && cp->evalcode != EVAL_HEIR ) + if ( othercond2 == 0 && cp->evalcode != EVAL_CHANNELS && cp->evalcode != EVAL_HEIR && cp->evalcode != EVAL_ASSETS && cp->evalcode != EVAL_TOKENS) othercond2 = MakeCCcond1(cp->evalcode2,cp->unspendablepk2); - else if ( othercond2 == 0 && (cp->evalcode == EVAL_CHANNELS || cp->evalcode == EVAL_HEIR) ) + else if ( othercond2 == 0 && (cp->evalcode == EVAL_CHANNELS || cp->evalcode == EVAL_HEIR || cp->evalcode == EVAL_ASSETS || cp->evalcode == EVAL_TOKENS) ) othercond2 = MakeCCcond1of2(cp->evalcode2,cp->unspendablepk2,cp->unspendablepk3); cond = othercond2; } @@ -297,7 +297,7 @@ int64_t CCfullsupply(uint256 tokenid) uint256 hashBlock; int32_t numvouts; CTransaction tx; std::vector origpubkey; std::string name,description; if ( GetTransaction(tokenid,tx,hashBlock,false) != 0 && (numvouts= tx.vout.size()) > 0 ) { - if ( DecodeAssetCreateOpRet(tx.vout[numvouts-1].scriptPubKey,origpubkey,name,description) > 0 ) + if (DecodeTokenCreateOpRet(tx.vout[numvouts-1].scriptPubKey,origpubkey,name,description)) { return(tx.vout[0].nValue); } @@ -307,8 +307,11 @@ int64_t CCfullsupply(uint256 tokenid) int64_t CCtoken_balance(char *coinaddr,uint256 tokenid) { - int64_t price,sum = 0; int32_t numvouts; CTransaction tx; uint256 assetid,assetid2,txid,hashBlock; std::vector origpubkey; + int64_t price,sum = 0; int32_t numvouts; CTransaction tx; uint256 assetid,assetid2,txid,hashBlock; + std::vector vopretExtra; std::vector > unspentOutputs; + uint8_t evalCode; + SetCCunspents(unspentOutputs,coinaddr); for (std::vector >::const_iterator it=unspentOutputs.begin(); it!=unspentOutputs.end(); it++) { @@ -316,7 +319,7 @@ int64_t CCtoken_balance(char *coinaddr,uint256 tokenid) if ( GetTransaction(txid,tx,hashBlock,false) != 0 && (numvouts= tx.vout.size()) > 0 ) { char str[65]; fprintf(stderr,"check %s %.8f\n",uint256_str(str,txid),(double)it->second.satoshis/COIN); - if ( DecodeAssetOpRet(tx.vout[numvouts-1].scriptPubKey,assetid,assetid2,price,origpubkey) != 0 && assetid == tokenid ) + if ( DecodeTokenOpRet(tx.vout[numvouts-1].scriptPubKey, evalCode, assetid, vopretExtra) != 0 && assetid == tokenid ) { sum += it->second.satoshis; } diff --git a/src/cc/assets.cpp b/src/cc/assets.cpp index a1fa39192..47b9488c8 100644 --- a/src/cc/assets.cpp +++ b/src/cc/assets.cpp @@ -133,45 +133,63 @@ // tx validation -bool AssetsValidate(struct CCcontract_info *cp,Eval* eval,const CTransaction &tx, uint32_t nIn) +bool AssetsValidate(struct CCcontract_info *cpAssets,Eval* eval,const CTransaction &tx, uint32_t nIn) { static uint256 zero; - CTxDestination address; CTransaction vinTx,createTx; uint256 hashBlock,assetid,assetid2; int32_t i,starti,numvins,numvouts,preventCCvins,preventCCvouts; int64_t remaining_price,nValue,assetoshis,outputs,inputs,tmpprice,totalunits,ignore; std::vector origpubkey,tmporigpubkey,ignorepubkey; uint8_t funcid; char destaddr[64],origaddr[64],CCaddr[64]; + CTxDestination address; CTransaction vinTx,createTx; uint256 hashBlock,assetid,assetid2; + int32_t i,starti,numvins,numvouts,preventCCvins,preventCCvouts; + int64_t remaining_price,nValue,assetoshis,outputs,inputs,tmpprice,totalunits,ignore; std::vector origpubkey,tmporigpubkey,ignorepubkey; + uint8_t funcid, evalCodeInOpret; + char destaddr[64],origaddr[64],CCaddr[64]; + + // we need this for validating tokens' vins/vous: + struct CCcontract_info *cpTokens, tokensC; + cpTokens = CCinit(&tokensC, EVAL_TOKENS); + numvins = tx.vin.size(); numvouts = tx.vout.size(); outputs = inputs = 0; preventCCvins = preventCCvouts = -1; - if ( (funcid= DecodeAssetOpRet(tx.vout[numvouts-1].scriptPubKey,assetid,assetid2,remaining_price,origpubkey)) == 0 ) - return eval->Invalid("Invalid opreturn payload"); + + if((funcid = DecodeAssetOpRet(tx.vout[numvouts-1].scriptPubKey, evalCodeInOpret, assetid, assetid2, remaining_price, origpubkey)) == 0 ) + return eval->Invalid("AssetValidate: invalid opreturn payload"); + fprintf(stderr,"AssetValidate (%c)\n",funcid); - if ( funcid != 'o' && funcid != 'x' && eval->GetTxUnconfirmed(assetid,createTx,hashBlock) == 0 ) + + if( funcid != 'o' && funcid != 'x' && eval->GetTxUnconfirmed(assetid, createTx, hashBlock) == 0 ) return eval->Invalid("cant find asset create txid"); - else if ( funcid != 'o' && funcid != 'x' && assetid2 != zero && eval->GetTxUnconfirmed(assetid2,createTx,hashBlock) == 0 ) + else if( funcid != 'o' && funcid != 'x' && assetid2 != zero && eval->GetTxUnconfirmed(assetid2, createTx, hashBlock) == 0 ) return eval->Invalid("cant find asset2 create txid"); - else if ( IsCCInput(tx.vin[0].scriptSig) != 0 ) + else if( IsCCInput(tx.vin[0].scriptSig) != 0 ) return eval->Invalid("illegal asset vin0"); - else if ( numvouts < 1 ) + else if( numvouts < 1 ) return eval->Invalid("no vouts"); - else if ( funcid != 'c' ) + else if( funcid != 'c' ) { - if ( funcid == 't' ) + /* if( funcid == 't' ) starti = 0; - else starti = 1; - if ( assetid == zero ) + else + starti = 1; */ + + if( assetid == zero ) return eval->Invalid("illegal assetid"); - else if ( AssetExactAmounts(2, cp,inputs,starti,outputs,eval,tx,assetid) == false ) - return eval->Invalid("asset inputs != outputs"); + + else if (!AssetExactAmounts(cpAssets, inputs, outputs, eval, tx, assetid)) { // Only set inputs and outputs. NOTE: we do not need to check cc inputs == cc outputs + return false; // returns false if some problems with reading vintxes + } } - - switch ( funcid ) + switch( funcid ) { case 'c': // create wont be called to be verified as it has no CC inputs //vin.0: normal input //vout.0: issuance assetoshis to CC //vout.1: normal output for change (if any) //vout.n-1: opreturn [EVAL_ASSETS] ['c'] [{"":""}] - return eval->Invalid("unexpected AssetValidate for createasset"); + //if (evalCodeInOpret == EVAL_ASSETS) + // return eval->Invalid("unexpected AssetValidate for createasset"); + // return + return eval->Invalid("invalid asset funcid \'c\'"); break; case 't': // transfer //vin.0: normal input @@ -179,9 +197,10 @@ bool AssetsValidate(struct CCcontract_info *cp,Eval* eval,const CTransaction &tx //vout.0 to n-2: assetoshis output to CC //vout.n-2: normal output for change (if any) //vout.n-1: opreturn [EVAL_ASSETS] ['t'] [assetid] - if ( inputs == 0 ) - return eval->Invalid("no asset inputs for transfer"); - fprintf(stderr,"transfer validated %.8f -> %.8f (%d %d)\n",(double)inputs/COIN,(double)outputs/COIN,preventCCvins,preventCCvouts); + //if (inputs == 0) + // return eval->Invalid("no asset inputs for transfer"); + //fprintf(stderr,"transfer preliminarily validated %.8f -> %.8f (%d %d)\n",(double)inputs/COIN,(double)outputs/COIN,preventCCvins,preventCCvouts); + return eval->Invalid("invalid asset funcid \'t\'"); break; case 'b': // buyoffer @@ -189,13 +208,13 @@ bool AssetsValidate(struct CCcontract_info *cp,Eval* eval,const CTransaction &tx //vout.0: amount of bid to unspendable //vout.1: normal output for change (if any) // vout.n-1: opreturn [EVAL_ASSETS] ['b'] [assetid] [amount of asset required] [origpubkey] - if ( remaining_price == 0 ) + if( remaining_price == 0 ) return eval->Invalid("illegal null amount for buyoffer"); - else if ( ConstrainVout(tx.vout[0],1,cp->unspendableCCaddr,0) == 0 ) + else if( ConstrainVout(tx.vout[0],1,cpAssets->unspendableCCaddr,0) == 0 ) return eval->Invalid("invalid vout for buyoffer"); preventCCvins = 1; preventCCvouts = 1; - fprintf(stderr,"buy offer validated to destaddr.(%s)\n",cp->unspendableCCaddr); + fprintf(stderr,"buy offer validated to destaddr.(%s)\n",cpAssets->unspendableCCaddr); break; case 'o': // cancelbuy @@ -204,9 +223,9 @@ bool AssetsValidate(struct CCcontract_info *cp,Eval* eval,const CTransaction &tx //vout.0: vin.1 value to original pubkey buyTx.vout[0].nValue -> [origpubkey] //vout.1: normal output for change (if any) //vout.n-1: opreturn [EVAL_ASSETS] ['o'] - if ( (nValue= AssetValidateBuyvin(cp,eval,tmpprice,tmporigpubkey,CCaddr,origaddr,tx,assetid)) == 0 ) + if( (nValue= AssetValidateBuyvin(cpAssets, eval, tmpprice, tmporigpubkey, CCaddr, origaddr, tx, assetid)) == 0 ) return(false); - else if ( ConstrainVout(tx.vout[0],0,origaddr,nValue) == 0 ) + else if( ConstrainVout(tx.vout[0],0, origaddr, nValue) == 0 ) return eval->Invalid("invalid refund for cancelbuy"); preventCCvins = 2; preventCCvouts = 0; @@ -224,32 +243,32 @@ bool AssetsValidate(struct CCcontract_info *cp,Eval* eval,const CTransaction &tx //vout.4: normal output for change (if any) //vout.n-1: opreturn [EVAL_ASSETS] ['B'] [assetid] [remaining asset required] [origpubkey] preventCCvouts = 4; - if ( (nValue= AssetValidateBuyvin(cp,eval,totalunits,tmporigpubkey,CCaddr,origaddr,tx,assetid)) == 0 ) + if( (nValue = AssetValidateBuyvin(cpAssets, eval, totalunits, tmporigpubkey, CCaddr, origaddr, tx, assetid)) == 0 ) return(false); - else if ( numvouts < 3 ) + else if( numvouts < 3 ) return eval->Invalid("not enough vouts for fillbuy"); - else if ( tmporigpubkey != origpubkey ) + else if( tmporigpubkey != origpubkey ) return eval->Invalid("mismatched origpubkeys for fillbuy"); else { - if ( nValue != tx.vout[0].nValue+tx.vout[1].nValue ) + if( nValue != tx.vout[0].nValue + tx.vout[1].nValue ) return eval->Invalid("locked value doesnt match vout0+1 fillbuy"); - else if ( tx.vout[3].scriptPubKey.IsPayToCryptoCondition() != 0 ) + else if( tx.vout[3].scriptPubKey.IsPayToCryptoCondition() != 0 ) { - if ( ConstrainVout(tx.vout[2],1,CCaddr,0) == 0 ) + if( ConstrainVout(tx.vout[2], 1, CCaddr, 0) == 0 ) return eval->Invalid("vout2 doesnt go to origpubkey fillbuy"); - else if ( inputs != tx.vout[2].nValue+tx.vout[3].nValue ) + else if ( inputs != tx.vout[2].nValue + tx.vout[3].nValue ) return eval->Invalid("asset inputs doesnt match vout2+3 fillbuy"); } - else if ( ConstrainVout(tx.vout[2],1,CCaddr,inputs) == 0 ) + else if( ConstrainVout(tx.vout[2], 1, CCaddr, inputs) == 0 ) return eval->Invalid("vout2 doesnt match inputs fillbuy"); - else if ( ConstrainVout(tx.vout[1],0,0,0) == 0 ) + else if( ConstrainVout(tx.vout[1],0,0,0) == 0 ) return eval->Invalid("vout1 is CC for fillbuy"); - else if ( ValidateBidRemainder(remaining_price,tx.vout[0].nValue,nValue,tx.vout[1].nValue,tx.vout[2].nValue,totalunits) == false ) + else if( ValidateBidRemainder(remaining_price, tx.vout[0].nValue, nValue, tx.vout[1].nValue, tx.vout[2].nValue, totalunits) == false ) return eval->Invalid("mismatched remainder for fillbuy"); - else if ( remaining_price != 0 ) + else if( remaining_price != 0 ) { - if ( ConstrainVout(tx.vout[0],1,cp->unspendableCCaddr,0) == 0 ) + if( ConstrainVout(tx.vout[0], 1, cpAssets->unspendableCCaddr, 0) == 0 ) return eval->Invalid("mismatched vout0 AssetsCCaddr for fillbuy"); } } @@ -266,16 +285,17 @@ bool AssetsValidate(struct CCcontract_info *cp,Eval* eval,const CTransaction &tx //'s'.vout.n-1: opreturn [EVAL_ASSETS] ['s'] [assetid] [amount of native coin required] [origpubkey] //'e'.vout.n-1: opreturn [EVAL_ASSETS] ['e'] [assetid] [assetid2] [amount of asset2 required] [origpubkey] preventCCvouts = 1; - if ( remaining_price == 0 ) + if( remaining_price == 0 ) return eval->Invalid("illegal null remaining_price for selloffer"); - if ( tx.vout[1].scriptPubKey.IsPayToCryptoCondition() != 0 ) + if( tx.vout[1].scriptPubKey.IsPayToCryptoCondition() != 0 ) { preventCCvouts++; - if ( ConstrainVout(tx.vout[0],1,(char *)cp->unspendableCCaddr,0) == 0 ) + if( ConstrainVout(tx.vout[0], 1, (char *)cpAssets->unspendableCCaddr, 0) == 0 ) return eval->Invalid("mismatched vout0 AssetsCCaddr for selloffer"); - else if ( tx.vout[0].nValue+tx.vout[1].nValue != inputs ) + else if( tx.vout[0].nValue + tx.vout[1].nValue != inputs ) return eval->Invalid("mismatched vout0+vout1 total for selloffer"); - } else if ( ConstrainVout(tx.vout[0],1,(char *)cp->unspendableCCaddr,inputs) == 0 ) + } + else if( ConstrainVout(tx.vout[0], 1, (char *)cpAssets->unspendableCCaddr, inputs) == 0 ) return eval->Invalid("mismatched vout0 AssetsCCaddr for selloffer"); //fprintf(stderr,"remaining.%d for sell\n",(int32_t)remaining_price); break; @@ -286,9 +306,9 @@ bool AssetsValidate(struct CCcontract_info *cp,Eval* eval,const CTransaction &tx //vout.0: vin.1 assetoshis to original pubkey CC sellTx/exchangeTx.vout[0].nValue -> [origpubkey] //vout.1: normal output for change (if any) //vout.n-1: opreturn [EVAL_ASSETS] ['x'] [assetid] - if ( (assetoshis= AssetValidateSellvin(cp,eval,tmpprice,tmporigpubkey,CCaddr,origaddr,tx,assetid)) == 0 ) + if( (assetoshis= AssetValidateSellvin(cpAssets, eval, tmpprice, tmporigpubkey, CCaddr, origaddr, tx, assetid)) == 0 ) return(false); - else if ( ConstrainVout(tx.vout[0],1,CCaddr,assetoshis) == 0 ) + else if( ConstrainVout(tx.vout[0], 1, CCaddr, assetoshis) == 0 ) return eval->Invalid("invalid vout for cancel"); preventCCvins = 2; preventCCvouts = 1; @@ -303,25 +323,25 @@ bool AssetsValidate(struct CCcontract_info *cp,Eval* eval,const CTransaction &tx //'S'.vout.2: vin.2 value to original pubkey [origpubkey] //vout.3: normal output for change (if any) //'S'.vout.n-1: opreturn [EVAL_ASSETS] ['S'] [assetid] [amount of coin still required] [origpubkey] - if ( (assetoshis= AssetValidateSellvin(cp,eval,totalunits,tmporigpubkey,CCaddr,origaddr,tx,assetid)) == 0 ) + if( (assetoshis = AssetValidateSellvin(cpTokens, eval, totalunits, tmporigpubkey, CCaddr, origaddr, tx, assetid)) == 0 ) return(false); - else if ( numvouts < 3 ) + else if( numvouts < 3 ) return eval->Invalid("not enough vouts for fillask"); - else if ( tmporigpubkey != origpubkey ) + else if( tmporigpubkey != origpubkey ) return eval->Invalid("mismatched origpubkeys for fillask"); else { - if ( assetoshis != tx.vout[0].nValue+tx.vout[1].nValue ) + if( assetoshis != tx.vout[0].nValue + tx.vout[1].nValue ) return eval->Invalid("locked value doesnt match vout0+1 fillask"); - if ( ValidateAskRemainder(remaining_price,tx.vout[0].nValue,assetoshis,tx.vout[1].nValue,tx.vout[2].nValue,totalunits) == false ) + if( ValidateAskRemainder(remaining_price, tx.vout[0].nValue, assetoshis, tx.vout[1].nValue, tx.vout[2].nValue, totalunits) == false ) return eval->Invalid("mismatched remainder for fillask"); - else if ( ConstrainVout(tx.vout[1],1,0,0) == 0 ) + else if( ConstrainVout(tx.vout[1], 1, 0, 0) == 0 ) return eval->Invalid("normal vout1 for fillask"); - else if ( ConstrainVout(tx.vout[2],0,origaddr,0) == 0 ) + else if( ConstrainVout(tx.vout[2], 0, origaddr, 0) == 0 ) return eval->Invalid("normal vout1 for fillask"); - else if ( remaining_price != 0 ) + else if( remaining_price != 0 ) { - if ( ConstrainVout(tx.vout[0],1,(char *)cp->unspendableCCaddr,0) == 0 ) + if ( ConstrainVout(tx.vout[0], 1, (char *)cpAssets->unspendableCCaddr,0) == 0 ) return eval->Invalid("mismatched vout0 AssetsCCaddr for fill"); } } @@ -339,51 +359,58 @@ bool AssetsValidate(struct CCcontract_info *cp,Eval* eval,const CTransaction &tx //vout.3: CC output for asset2 change (if any) //vout.3/4: normal output for change (if any) //vout.n-1: opreturn [EVAL_ASSETS] ['E'] [assetid vin0+1] [assetid vin2] [remaining asset2 required] [origpubkey] - if ( AssetExactAmounts(1, cp,inputs,1,outputs,eval,tx,assetid2) == false ) - eval->Invalid("asset2 inputs != outputs"); - if ( (assetoshis= AssetValidateSellvin(cp,eval,totalunits,tmporigpubkey,CCaddr,origaddr,tx,assetid)) == 0 ) + + //if ( AssetExactAmounts(false, cp,inputs,outputs,eval,tx,assetid2) == false ) + // eval->Invalid("asset2 inputs != outputs"); + + if( (assetoshis= AssetValidateSellvin(cpAssets, eval, totalunits, tmporigpubkey, CCaddr, origaddr, tx, assetid)) == 0 ) return(false); - else if ( numvouts < 3 ) + else if( numvouts < 3 ) return eval->Invalid("not enough vouts for fillex"); - else if ( tmporigpubkey != origpubkey ) + else if( tmporigpubkey != origpubkey ) return eval->Invalid("mismatched origpubkeys for fillex"); else { - if ( assetoshis != tx.vout[0].nValue+tx.vout[1].nValue ) + if( assetoshis != tx.vout[0].nValue + tx.vout[1].nValue ) return eval->Invalid("locked value doesnt match vout0+1 fillex"); - else if ( tx.vout[3].scriptPubKey.IsPayToCryptoCondition() != 0 ) + else if( tx.vout[3].scriptPubKey.IsPayToCryptoCondition() != 0 ) { - if ( ConstrainVout(tx.vout[2],1,CCaddr,0) == 0 ) + if( ConstrainVout(tx.vout[2], 1, CCaddr, 0) == 0 ) return eval->Invalid("vout2 doesnt go to origpubkey fillex"); - else if ( inputs != tx.vout[2].nValue+tx.vout[3].nValue ) + else if( inputs != tx.vout[2].nValue + tx.vout[3].nValue ) { fprintf(stderr,"inputs %.8f != %.8f + %.8f\n",(double)inputs/COIN,(double)tx.vout[2].nValue/COIN,(double)tx.vout[3].nValue/COIN); return eval->Invalid("asset inputs doesnt match vout2+3 fillex"); } } - else if ( ConstrainVout(tx.vout[2],1,CCaddr,inputs) == 0 ) + else if( ConstrainVout(tx.vout[2], 1, CCaddr, inputs) == 0 ) return eval->Invalid("vout2 doesnt match inputs fillex"); - else if ( ConstrainVout(tx.vout[1],0,0,0) == 0 ) + else if( ConstrainVout(tx.vout[1], 0, 0, 0) == 0 ) return eval->Invalid("vout1 is CC for fillex"); fprintf(stderr,"assets vout0 %llu, vin1 %llu, vout2 %llu -> orig, vout1 %llu, total %llu\n",(long long)tx.vout[0].nValue,(long long)assetoshis,(long long)tx.vout[2].nValue,(long long)tx.vout[1].nValue,(long long)totalunits); - if ( ValidateSwapRemainder(remaining_price,tx.vout[0].nValue,assetoshis,tx.vout[1].nValue,tx.vout[2].nValue,totalunits) == false ) + if( ValidateSwapRemainder(remaining_price, tx.vout[0].nValue, assetoshis,tx.vout[1].nValue, tx.vout[2].nValue, totalunits) == false ) return eval->Invalid("mismatched remainder for fillex"); - else if ( ConstrainVout(tx.vout[1],1,0,0) == 0 ) + else if( ConstrainVout(tx.vout[1], 1, 0, 0) == 0 ) return eval->Invalid("normal vout1 for fillex"); - else if ( remaining_price != 0 ) + else if( remaining_price != 0 ) { - if ( ConstrainVout(tx.vout[0],1,(char *)cp->unspendableCCaddr,0) == 0 ) + if( ConstrainVout(tx.vout[0], 1, (char *)cpAssets->unspendableCCaddr, 0) == 0 ) return eval->Invalid("mismatched vout0 AssetsCCaddr for fillex"); } } fprintf(stderr,"fill validated\n"); break; + default: fprintf(stderr,"illegal assets funcid.(%c)\n",funcid); return eval->Invalid("unexpected assets funcid"); - break; + //break; } - return(PreventCC(eval,tx,preventCCvins,numvins,preventCCvouts,numvouts)); + + // what does this do? + bool bPrevent = PreventCC(eval, tx, preventCCvins, numvins, preventCCvouts, numvouts); + std::cerr << "AssetsValidate() PreventCC returned=" << bPrevent << std::endl; + return (bPrevent); } diff --git a/src/cc/eval.h b/src/cc/eval.h index 87b98349b..de1636ab8 100644 --- a/src/cc/eval.h +++ b/src/cc/eval.h @@ -55,7 +55,8 @@ EVAL(EVAL_PEGS, 0xee) \ EVAL(EVAL_MARMARA, 0xef) \ EVAL(EVAL_PAYMENTS, 0xf0) \ - EVAL(EVAL_GATEWAYS, 0xf1) + EVAL(EVAL_GATEWAYS, 0xf1) \ + EVAL(EVAL_TOKENS, 0xf2) typedef uint8_t EvalCode; diff --git a/src/cc/heir.cpp b/src/cc/heir.cpp index 44f01a8a4..81318ad1e 100644 --- a/src/cc/heir.cpp +++ b/src/cc/heir.cpp @@ -14,203 +14,1315 @@ ******************************************************************************/ #include "CCHeir.h" +#include "CCassets.h" + +#include "heir_validate.h" + +class CoinHelper; +class TokenHelper; /* The idea of Heir CC is to allow crypto inheritance. A special 1of2 CC address is created that is freely spendable by the creator. The heir is only allowed to spend after the specified amount of idle blocks. The idea is that if the address doesnt spend any funds for a year (or whatever amount set), then it is time to allow the heir to spend. The design requires the heir to spend all the funds at once */ -// start of consensus code +// tx validation code -int64_t IsHeirvout(struct CCcontract_info *cp,const CTransaction& tx,int32_t v) +// this is for indentation of debug log messages (in recursive calls): +//extern thread_local uint32_t assetValIndentSize; + +// check if vout is cc addr and also check sum(inputs) == sum(outputs) for the passed tx, if requested +int64_t IsHeirvout(bool compareTotals, struct CCcontract_info *cpHeir, Eval* eval, uint256 tokenid, const CTransaction& tx, int32_t v) { - char destaddr[64]; - if ( tx.vout[v].scriptPubKey.IsPayToCryptoCondition() != 0 ) - { - if ( Getscriptaddress(destaddr,tx.vout[v].scriptPubKey) > 0 && strcmp(destaddr,cp->unspendableCCaddr) == 0 ) - return(tx.vout[v].nValue); - } - return(0); + //std::string indentStr = std::string().append(assetValIndentSize, '.'); + + //std::cerr << indentStr << "IsHeirvout() entered for txid=" << tx.GetHash().GetHex() << " v=" << v << std::boolalpha << " compareTotals=" << compareTotals << std::endl; + if (tx.vout[v].scriptPubKey.IsPayToCryptoCondition()) + { + //std::cerr << indentStr << "IsHeirvout() IsPayToCryptoCondition=true for txid=" << tx.GetHash().GetHex() << std::endl; + /*if (compareTotals) { // totally there are only 2 levels actually + + // call recursively HeirExactTokenAmounts and compare ccinputs = ccoutputs for this tx: + assetValIndentSize++; + const bool isEqual = HeirExactTokenAmounts(false, cpHeir, eval, tokenid, tx); + assetValIndentSize--; + + if (!isEqual) { // ccInputs != ccOutputs means a problem + //std::cerr << indentStr << "IsHeirvout() warning: detected suspicious tx=" << tx.GetHash().GetHex() << ": cc inputs != cc outputs, checking further if it is the tokenbase tx" << std::endl; + // if ccInputs != ccOutputs and it is not the 'tokenbase' tx means it is possibly fake tx (dimxy): + if (tokenid != zeroid && tokenid != tx.GetHash()) { + std::cerr << indentStr << "IsHeirvout() warning: detected bad tx=" << tx.GetHash().GetHex() << ": cc inputs != cc outputs and not the 'tokenbase' tx, skipping this tx" << std::endl; + return 0; + } + } + }*/ + + // TODO: add some validation here + + // lets check asset opreturn for this heir or assets tx (dimxy): + /* + int64_t dummyPrice; std::vector dummyOrigpubkey; + + const bool valOpret = ValidateAssetOpret(tx, v, tokenid, dummyPrice, dummyOrigpubkey); + //std::cerr << indentStr << "IsHeirvout() ValidateAssetOpret returned=" << std::boolalpha << valOpret << " for txid=" << tx.GetHash().GetHex() << std::endl; + if (valOpret) { + std::cerr << indentStr << "IsHeirvout() opret is true, return value=" << tx.vout[v].nValue << " for txid=" << tx.GetHash().GetHex() << std::endl; + return(tx.vout[v].nValue); + }*/ + + return(tx.vout[v].nValue); + } + //std::cerr << indentStr << "IsHeirvout() return value=0" << std::endl; + return(0); } -bool HeirExactAmounts(struct CCcontract_info *cp,Eval* eval,const CTransaction &tx,int32_t minage,uint64_t txfee) +// this function validates that tx cc outputs == cc inputs, +// that is there is no fake token supply from normal inputs (except the initial tokenbase tx) +// the cc inputs are allowed only from the Assets or Heir contracts +/*bool HeirExactTokenAmounts(bool compareTotals, struct CCcontract_info *cpHeir, Eval* eval, uint256 tokenid, const CTransaction &tx) { - static uint256 zerohash; - CTransaction vinTx; uint256 hashBlock,activehash; int32_t i,numvins,numvouts; int64_t inputs=0,outputs=0,assetoshis; - numvins = tx.vin.size(); - numvouts = tx.vout.size(); - for (i=0; iismyvin)(tx.vin[i].scriptSig) != 0 ) - { - //fprintf(stderr,"vini.%d check mempool\n",i); - if ( eval->GetTxUnconfirmed(tx.vin[i].prevout.hash,vinTx,hashBlock) == 0 ) - return eval->Invalid("cant find vinTx"); - else - { - //fprintf(stderr,"vini.%d check hash and vout\n",i); - if ( hashBlock == zerohash ) - return eval->Invalid("cant Heir from mempool"); - if ( (assetoshis= IsHeirvout(cp,vinTx,tx.vin[i].prevout.n)) != 0 ) - inputs += assetoshis; - } - } - } - for (i=0; iInvalid("mismatched inputs != outputs + txfee"); - } - else return(true); + static uint256 zerohash; + CTransaction vinTx; + uint256 hashBlock, activehash; + int64_t inputs = 0, outputs = 0, assetoshis; + + struct CCcontract_info *cpAssets, cAssets; + cpAssets = CCinit(&cAssets, EVAL_ASSETS); // init also tokens CC contract to check its cc addresses too + + std::string indentStr = std::string().append(assetValIndentSize, '.'); + + int32_t numvins = tx.vin.size(); + int32_t numvouts = tx.vout.size(); + for (int32_t i = 0; i < numvins; i++) + { + //std::cerr << indentStr << "HeirExactTokenAmounts() vin i=" << i << " cpHeir->ismyvin()=" << std::boolalpha << (*cpHeir->ismyvin)(tx.vin[i].scriptSig) << " cpAssets->ismyvin()=" << (*cpAssets->ismyvin)(tx.vin[i].scriptSig) << std::endl; + + // checking that vin is either from heir or assets: + if ((*cpHeir->ismyvin)(tx.vin[i].scriptSig) || (*cpAssets->ismyvin)(tx.vin[i].scriptSig)) + { + //std::cerr << indentStr; fprintf(stderr,"vini.%d check mempool\n",i); + if ((eval && !eval->GetTxUnconfirmed(tx.vin[i].prevout.hash, vinTx, hashBlock)) || !myGetTransaction(tx.vin[i].prevout.hash, vinTx, hashBlock)) { + std::cerr << indentStr << "HeirExactTokenAmounts(): can't get vintx transaction txid=" << tx.vin[i].prevout.hash.GetHex() << std::endl; + return (eval) ? eval->Invalid("cant find vinTx") : false; + } + else + { + //std::cerr << indentStr; fprintf(stderr,"vini.%d check hash and vout\n",i); + if (hashBlock == zerohash) { + std::cerr << indentStr << "HeirExactTokenAmounts(): can't get vintx from mempool, txid=" << tx.vin[i].prevout.hash.GetHex() << std::endl; + return (eval) ? eval->Invalid("cant Heir from mempool") : false; + } + + std::string dummyRefcoin; uint256 dummyBindtxid, dummyDeposittxid; CPubKey dummyDestpub; int64_t dummyAmount; + uint256 dummyAssetid2; + std::vector dummyOrigpubkey; + + // Note: if tokenid is zeroid, it may mean we are on the first level and just called from HeirValidate, validating claim 't' tx, + // then let's find the tokenid ourselves: + if (tokenid == zeroid && DecodeAssetOpRet(tx.vout[numvouts - 1].scriptPubKey, tokenid, dummyAssetid2, dummyAmount, dummyOrigpubkey) == 't') { + //std::cerr << indentStr << "HeirExactTokenAmounts() will check if this vinx is the tokenbase tokenid=" << tokenid.GetHex() << std::endl; + } + + // checking that the vout of the vintx (that is, referenced by this vin), in its turn, is fed by either from heir' or assets' cryptocondition address: + //std::cerr << indentStr << "HeirExactTokenAmounts() calling IsHeirvout for vintx i=" << i << " prevout.n=" << tx.vin[i].prevout.n << std::endl; + + assetValIndentSize++; + assetoshis = IsHeirvout(compareTotals, cpHeir, eval, tokenid, vinTx, tx.vin[i].prevout.n); + assetValIndentSize--; + if (assetoshis > 0) + inputs += assetoshis; + } + } + } + for (int32_t i = 0; iInvalid("mismatched inputs != outputs + txfee") : false; + } + else { + //std::cerr << indentStr << "HeirExactTokenAmounts() inputs=" << inputs << " vs outputs=" << outputs << " return true" << std::endl; + return(true); + } +}*/ + + +// claim coins tokens validation runner +// sadly we cannot have yet 'templatized' lambdas, if we could we could capture all these params inside HeirValidation() +template bool RunValidationPlans(uint8_t funcId, struct CCcontract_info* cp, Eval* eval, const CTransaction& tx, uint256 latestTxid, CScript fundingOpretScript, bool isHeirSpendingBegan) +{ + int32_t numvins = tx.vin.size(); + int32_t numvouts = tx.vout.size(); + + // setup validation framework (please see its description in heir_validate.h): + // validation 'plans': + CInputValidationPlan vinPlan; + COutputValidationPlan voutPlan; + + // vin 'identifiers' + CNormalInputIdentifier normalInputIdentifier(cp); + CCCInputIdentifier ccInputIdentifier(cp); + + // vin and vout 'validators' + // always check coin inputs: + CMyPubkeyVoutValidator normalInputValidator(cp, fundingOpretScript, true); // check normal input for this opret cause this is first tx + //CMyPubkeyVoutValidator normalInputValidatorLast(cp, latestTxOpRetScript, true); // check normal input for latest opret. TODO: we may also check this opret + + CCC1of2AddressValidator cc1of2ValidatorThis(cp, fundingOpretScript, "checking this tx opreturn:"); // 1of2add validator with pubkeys from this tx opreturn + //CCC1of2AddressValidator cc1of2ValidatorLast(cp, latestTxOpRetScript, "checking last tx opreturn:"); // 1of2add validator with pubkeys from last tx opreturn + CHeirSpendValidator heirSpendValidator(cp, fundingOpretScript, latestTxid, isHeirSpendingBegan); // check if heir allowed to spend + + // only for tokens: + CMyPubkeyVoutValidator ownerCCaddrValidator(cp, fundingOpretScript, false); // check if this correct owner's cc user addr corresponding to opret + //CMyPubkeyVoutValidator ownerCCaddrValidatorLast(cp, latestTxOpRetScript, false); // check if this correct owner's cc user addr corresponding to lastest opret + // TODO: we may also check with current opret + COpRetValidator opRetValidator(cp, fundingOpretScript); // compare opRets in this and last tx + + switch (funcId) { + case 'F': // fund tokens + // vin validation plan: + // we need cast here this is casted inside + vinPlan.pushValidators((CInputIdentifierBase*)&normalInputIdentifier, &normalInputValidator); // txfee - see AddNormalInput parameter + vinPlan.pushValidators((CInputIdentifierBase*)&ccInputIdentifier, &ownerCCaddrValidator); // check cc owner addr + + // vout validation plan: + voutPlan.pushValidators(0, &cc1of2ValidatorThis); // check 1of2 addr funding + // do not check change at this time + // no checking for opret yet + break; + + case 'A': // add tokens + // vin validation plan: + // we need cast here this is casted inside + vinPlan.pushValidators((CInputIdentifierBase*)&normalInputIdentifier, &normalInputValidator/*, &normalInputValidatorLast*/); // txfee - see AddNormalInput parameter + vinPlan.pushValidators((CInputIdentifierBase*)&ccInputIdentifier, &ownerCCaddrValidator); // check cc owner addr + + // vout validation plan: + voutPlan.pushValidators(0, &cc1of2ValidatorThis); // check 1of2 addr funding + // do not check change at this time + voutPlan.pushValidators(numvouts - 1, &opRetValidator); // opreturn check, NOTE: only for C or A: + break; + + case 'C': + // vin validation plan: + // we need cast here this is casted inside + vinPlan.pushValidators((CInputIdentifierBase*)&normalInputIdentifier, &normalInputValidator/*&normalInputValidatorLast*/); // txfee - see AddNormalInput parameter + vinPlan.pushValidators((CInputIdentifierBase*)&ccInputIdentifier, &cc1of2ValidatorThis /*, &cc1of2ValidatorLast*/); // cc1of2 funding addr + + // vout validation plan: + voutPlan.pushValidators(0, &heirSpendValidator); // check if heir is allowed to spend + voutPlan.pushValidators(numvouts - 1, &opRetValidator); // opreturn check, NOTE: only for C or A + break; + } + + // call vin/vout validation + if (!vinPlan.validate(tx, eval)) + return false; + if (!voutPlan.validate(tx, eval)) + return false; + + return true; } -bool HeirValidate(struct CCcontract_info *cp,Eval* eval,const CTransaction &tx, uint32_t nIn) +/** + * Tx validation entry function + */ +bool HeirValidate(struct CCcontract_info* cp, Eval* eval, const CTransaction& tx, uint32_t nIn) { - int32_t numvins,numvouts,preventCCvins,preventCCvouts,i,numblocks; bool retval; uint256 txid; uint8_t hash[32]; char str[65],destaddr[64]; - return eval->Invalid("no validation yet"); - std::vector > txids; - numvins = tx.vin.size(); - numvouts = tx.vout.size(); - preventCCvins = preventCCvouts = -1; - if ( numvouts < 1 ) + int32_t numvins = tx.vin.size(); + int32_t numvouts = tx.vout.size(); + //int32_t preventCCvins = -1; + //int32_t preventCCvouts = -1; + + if (numvouts < 1) return eval->Invalid("no vouts"); - else - { - for (i=0; iInvalid("illegal normal vini"); - } + + if (chainActive.Height() < 741) + return true; + + uint8_t funcId; + uint256 fundingTxidInOpret = zeroid, latestTxid = zeroid, tokenid = zeroid; + + //CScript opRetScript = tx.vout[numvouts - 1].scriptPubKey; + + CScript fundingTxOpRetScript; + bool isHeirSpendingBegan = false; + + int32_t heirType = NOT_HEIR; + funcId = DecodeHeirOpRet(tx.vout[numvouts - 1].scriptPubKey, tokenid, fundingTxidInOpret, true); + if(funcId != 0) + heirType = HEIR_COINS; + else { + funcId = DecodeHeirOpRet(tx.vout[numvouts - 1].scriptPubKey, tokenid, fundingTxidInOpret, false); + if (funcId != 0) + heirType = HEIR_TOKENS; + } + + if (heirType == NOT_HEIR) + return eval->Invalid("invalid opreturn format"); + + if (funcId != 'F') { + if (fundingTxidInOpret == zeroid) { + return eval->Invalid("invalid tx opreturn format: no fundingtxid present"); } - //fprintf(stderr,"check amounts\n"); - if ( HeirExactAmounts(cp,eval,tx,1,10000) == false ) - { - fprintf(stderr,"Heirget invalid amount\n"); - return false; - } - else - { - txid = tx.GetHash(); - memcpy(hash,&txid,sizeof(hash)); - retval = PreventCC(eval,tx,preventCCvins,numvins,preventCCvouts,numvouts); - if ( retval != 0 ) - fprintf(stderr,"Heirget validated\n"); - else fprintf(stderr,"Heirget invalid\n"); - return(retval); + if (heirType == HEIR_COINS) + latestTxid = FindLatestFundingTx(fundingTxidInOpret, tokenid, fundingTxOpRetScript, isHeirSpendingBegan); + else + latestTxid = FindLatestFundingTx(fundingTxidInOpret, tokenid, fundingTxOpRetScript, isHeirSpendingBegan); + + if (latestTxid == zeroid) { + return eval->Invalid("invalid heir transaction: no funding tx found"); } + } + else { + fundingTxOpRetScript = tx.vout[numvouts - 1].scriptPubKey; + } + + // validate prev tx cc inputs = outputs: + /* if (heirType == HEIR_TOKENS && funcId == 't' && !HeirExactTokenAmounts(true, cp, eval, zeroid, tx)) { + std::cerr << "HeirValidate() this tx or some of its vin tx has invalid cc amounts" << std::endl; + return eval->Invalid("this tx or some of its vin tx has invalid cc amounts"); + } */ + + switch (funcId) { + case 'F': + // fund coins: + // vins.*: normal inputs + // ----------------------------- + // vout.0: funding CC 1of2 addr for the owner and heir + // vout.1: txfee for CC addr used as a marker + // vout.2: normal change + // vout.n-1: opreturn 'F' ownerpk heirpk inactivitytime heirname + + // fund tokens: + // vin.0: normal inputs txfee + // vins.1+: user's CC addr inputs + // ----------------------- + // vout.0: funding heir CC 1of2 addr for the owner and heir + // vout.1: txfee for CC addr used as a marker + // vout.2: normal change + // vout.n-1: opreturn 't' tokenid 'F' ownerpk heirpk inactivitytime heirname tokenid + if (heirType == HEIR_TOKENS) + return RunValidationPlans(funcId, cp, eval, tx, latestTxid, fundingTxOpRetScript, isHeirSpendingBegan); + else + return eval->Invalid("unexpected HeirValidate for heirfund"); + // break; + + case 'A': + // add funding coins: + // vins.*: normal inputs + // ------------------------ + // vout.0: funding CC 1of2 addr for the owner and heir + // vout.1: normal change + // vout.n-1: opreturn 'A' ownerpk heirpk inactivitytime fundingtx + + // add funding tokens: + // vins.0: normal inputs txfee + // vins.1+: user's CC addr inputs + // ------------------------ + // vout.0: funding CC 1of2 addr for the owner and heir + // vout.1: normal change + // vout.n-1: opreturn 't' tokenid 'A' ownerpk heirpk inactivitytime fundingtx + if (heirType == HEIR_TOKENS) + return RunValidationPlans(funcId, cp, eval, tx, latestTxid, fundingTxOpRetScript, isHeirSpendingBegan); + else + return eval->Invalid("unexpected HeirValidate for heiradd"); + //break; + + case 'C': + // claim coins: + // vin.0: normal input txfee + // vin.1+: input from CC 1of2 addr + // ------------------------------------- + // vout.0: normal output to owner or heir address + // vout.1: change to CC 1of2 addr + // vout.2: change to user's addr from txfee input if any + // vout.n-1: opreturn 'C' ownerpk heirpk inactivitytime fundingtx + + // claim tokens: + // vin.0: normal input txfee + // vin.1+: input from CC 1of2 addr + // -------------------------------------------- + // vout.0: output to user's cc address + // vout.1: change to CC 1of2 addr + // vout.2: change to normal from txfee input if any + // vout.n-1: opreturn 't' tokenid 'C' ownerpk heirpk inactivitytime fundingtx + if (heirType == HEIR_TOKENS) + return RunValidationPlans(funcId, cp, eval, tx, latestTxid, fundingTxOpRetScript, isHeirSpendingBegan); + else + return RunValidationPlans(funcId, cp, eval, tx, latestTxid, fundingTxOpRetScript, isHeirSpendingBegan); + // break; + + default: + std::cerr << "HeirValidate() illegal heir funcid=" << (char)funcId << std::endl; + return eval->Invalid("unexpected HeirValidate funcid"); + // break; } + return eval->Invalid("unexpected"); // (PreventCC(eval, tx, preventCCvins, numvins, preventCCvouts, numvouts)); } // end of consensus code -// helper functions for rpc calls in rpcwallet.cpp -int64_t AddHeirInputs(struct CCcontract_info *cp,CMutableTransaction &mtx,CPubKey pk,int64_t total,int32_t maxinputs) +// helper functions used in implementations of rpc calls (in rpcwallet.cpp) or validation code + +/** +* Checks if vout is to cryptocondition address +* @return vout value in satoshis +*/ +int64_t IsHeirFundingVout(struct CCcontract_info* cp, const CTransaction& tx, int32_t voutIndex, CPubKey ownerPubkey, CPubKey heirPubkey) { - // add threshold check - char coinaddr[64]; int64_t nValue,price,totalinputs = 0; uint256 txid,hashBlock; std::vector origpubkey; CTransaction vintx; int32_t vout,n = 0; - std::vector > unspentOutputs; - GetCCaddress(cp,coinaddr,pk); - SetCCunspents(unspentOutputs,coinaddr); - for (std::vector >::const_iterator it=unspentOutputs.begin(); it!=unspentOutputs.end(); it++) - { - txid = it->first.txhash; - vout = (int32_t)it->first.index; + char destaddr[65], heirContractAddr[65]; + + GetCCaddress1of2(cp, heirContractAddr, ownerPubkey, heirPubkey); + if (tx.vout[voutIndex].scriptPubKey.IsPayToCryptoCondition() != 0) { + // NOTE: dimxy it was unsafe 'Getscriptaddress(destaddr,tx.vout[voutIndex].scriptPubKey) > 0' here: + if (Getscriptaddress(destaddr, tx.vout[voutIndex].scriptPubKey) && strcmp(destaddr, heirContractAddr) == 0) + return (tx.vout[voutIndex].nValue); + } + return (0); +} + +// not used +bool HeirExactAmounts(struct CCcontract_info* cp, Eval* eval, const CTransaction& tx, int32_t minage, uint64_t txfee) +{ + static uint256 zerohash; + CTransaction vinTx; + uint256 hashBlock, activehash; + int32_t i, numvins, numvouts; + int64_t inputs = 0, outputs = 0, assetoshis; + numvins = tx.vin.size(); + numvouts = tx.vout.size(); + for (i = 0; i < numvins; i++) { + //fprintf(stderr,"HeirExactAmounts() vini.%d\n",i); + if ((*cp->ismyvin)(tx.vin[i].scriptSig) != 0) { + //fprintf(stderr,"HeirExactAmounts() vini.%d check mempool\n",i); + if (eval->GetTxUnconfirmed(tx.vin[i].prevout.hash, vinTx, hashBlock) == 0) + return eval->Invalid("cant find vinTx"); + else { + //fprintf(stderr,"HeirExactAmounts() vini.%d check hash and vout\n",i); + if (hashBlock == zerohash) + return eval->Invalid("cant Heir from mempool"); + ////if ( (assetoshis= IsHeirCCvout(cp,vinTx,tx.vin[i].prevout.n)) != 0 ) + //// inputs += assetoshis; + } + } + } + for (i = 0; i < numvouts; i++) { + //fprintf(stderr,"i.%d of numvouts.%d\n",i,numvouts); + ////if ( (assetoshis= IsHeirvout(cp,tx,i)) != 0 ) + //// outputs += assetoshis; + } + if (inputs != outputs + txfee) { + fprintf(stderr, "HeirExactAmounts() inputs %llu vs outputs %llu\n", (long long)inputs, (long long)outputs); + return eval->Invalid("mismatched inputs != outputs + txfee"); + } + else + return (true); +} + +// makes coin initial tx opret +CScript EncodeHeirCreateOpRet(uint8_t eval, uint8_t funcid, CPubKey ownerPubkey, CPubKey heirPubkey, int64_t inactivityTimeSec, std::string heirName) +{ + return CScript() << OP_RETURN << E_MARSHAL(ss << (uint8_t)eval << (uint8_t)funcid << ownerPubkey << heirPubkey << inactivityTimeSec << heirName); +} + +// makes coin additional tx opret +CScript EncodeHeirOpRet(uint8_t eval, uint8_t funcid, uint256 fundingtxid) +{ + fundingtxid = revuint256(fundingtxid); + return CScript() << OP_RETURN << E_MARSHAL(ss << (uint8_t)eval << (uint8_t)funcid << fundingtxid); +} +// makes opret for tokens while they are inside Heir contract address space - initial funding +CScript EncodeHeirAssetsCreateOpRet(uint8_t eval, uint8_t funcid, uint256 tokenid, CPubKey ownerPubkey, CPubKey heirPubkey, int64_t inactivityTimeSec, std::string hearName) +{ + tokenid = revuint256(tokenid); + return CScript() << OP_RETURN << E_MARSHAL(ss << (uint8_t)eval << (uint8_t)'t' << tokenid << (uint8_t)funcid << ownerPubkey << heirPubkey << inactivityTimeSec << hearName); +} +// makes opret for tokens while they are inside Heir contract address space - additional funding +CScript EncodeHeirAssetsOpRet(uint8_t eval, uint8_t funcid, uint256 tokenid, uint256 fundingtxid) +{ + tokenid = revuint256(tokenid); + fundingtxid = revuint256(fundingtxid); + return CScript() << OP_RETURN << E_MARSHAL(ss << (uint8_t)eval << (uint8_t)'t' << tokenid << (uint8_t)funcid << fundingtxid); +} + +/** +* decode opret vout for Heir contract +*/ +template uint8_t _DecodeHeirOpRet(CScript scriptPubKey, uint256 &tokenid, CPubKey& ownerPubkey, CPubKey& heirPubkey, int64_t& inactivityTime, std::string& heirName, uint256& fundingTxidInOpret, bool noLogging) +{ + std::vector vopret; + uint8_t opretEval = 0; + uint8_t funcId = 0; + + fundingTxidInOpret = zeroid; //to init + + GetOpReturnData(scriptPubKey, vopret); + + if (vopret.size() > 1) { + // NOTE: it unmarshals for all F, A and C + Helper::UnmarshalOpret(vopret, opretEval, funcId, tokenid, ownerPubkey, heirPubkey, inactivityTime, heirName, fundingTxidInOpret); + + /* + std::cerr << "DecodeHeirOpRet() e=" << (int)e + << " funcId=" << (char)funcId + << " ownerPubkey=" << HexStr(ownerPubkey) + << " heirPubkey=" << HexStr(heirPubkey) + << " heirName=" << heirName << " inactivityTime=" << inactivityTime << '\n'; + */ + + //if (e == EVAL_HEIR && IS_CHARINSTR(funcId, "FAC")) + if (opretEval == EVAL_HEIR && Helper::isMyFuncId(funcId)) { + tokenid = revuint256(tokenid); + fundingTxidInOpret = revuint256(fundingTxidInOpret); + return funcId; + } + else + { + if(!noLogging) std::cerr << "DecodeHeirOpRet() warning unexpected OP_RETURN eval=" << (int)opretEval << " or field type=" << (char)(funcId ? funcId : ' ') << '\n'; + } + } + else { + std::cerr << "DecodeHeirOpRet() unmarshal error (vopret.size() == 0)" << '\n'; + } + return (uint8_t)0; +} + +/** +* overload for 'F' opret +*/ +template uint8_t DecodeHeirOpRet(CScript scriptPubKey, uint256 &tokenid, CPubKey& ownerPubkey, CPubKey& heirPubkey, int64_t& inactivityTime, std::string& heirName, bool noLogging) +{ + uint256 dummytxid; + + return _DecodeHeirOpRet(scriptPubKey, tokenid, ownerPubkey, heirPubkey, inactivityTime, heirName, dummytxid, noLogging); +} + +/** +* overload for A, C oprets and AddHeirContractInputs +*/ +template uint8_t DecodeHeirOpRet(CScript scriptPubKey, uint256 &tokenid, uint256& fundingtxidInOpret, bool noLogging) +{ + CPubKey dummyOwnerPubkey, dummyHeirPubkey; + int64_t dummyInactivityTime; + std::string dummyHeirName; + + return _DecodeHeirOpRet(scriptPubKey, tokenid, dummyOwnerPubkey, dummyHeirPubkey, dummyInactivityTime, dummyHeirName, fundingtxidInOpret, noLogging); +} + + + +/** + * 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) + */ +template uint256 _FindLatestFundingTx(uint256 fundingtxid, uint8_t& funcId, uint256 &tokenid, CPubKey& ownerPubkey, CPubKey& heirPubkey, int64_t& inactivityTime, std::string& heirName, CScript& fundingOpretScript, bool &isHeirSpendingBegan) +{ + CTransaction fundingtx; + uint256 hashBlock; + const bool allowSlow = false; + + //char markeraddr[64]; + //CCtxidaddr(markeraddr, fundingtxid); + //SetCCunspents(unspentOutputs, markeraddr); + + isHeirSpendingBegan = false; //init the var + funcId = 0; //init the var + + // get initial funding tx and set it as initial lasttx: + if (myGetTransaction(fundingtxid, fundingtx, hashBlock) && fundingtx.vout.size()) { + uint256 dummytxid; + + // set ownerPubkey and heirPubkey: + if ((funcId = DecodeHeirOpRet(fundingtx.vout[fundingtx.vout.size() - 1].scriptPubKey, tokenid, ownerPubkey, heirPubkey, inactivityTime, heirName)) != 0) { + // found at least funding tx! + std::cerr << "FindLatestFundingTx() lasttx currently is fundingtx, txid=" << fundingtxid.GetHex() << " opreturn type=" << (char)funcId << '\n'; + fundingOpretScript = fundingtx.vout[fundingtx.vout.size() - 1].scriptPubKey; + } else { + std::cerr << "FindLatestFundingTx() could not decode opreturn for fundingtxid=" << fundingtxid.GetHex() << '\n'; + return zeroid; + } + } else { + std::cerr << "FindLatestFundingTx() could not find funding tx for fundingtxid=" << fundingtxid.GetHex() << '\n'; + return zeroid; + } + + std::vector> unspentOutputs; + struct CCcontract_info *cp, C; + cp = CCinit(&C, Helper::getMyEval()); + char coinaddr[64]; + GetCCaddress1of2(cp, coinaddr, ownerPubkey, heirPubkey); // get the address of cryptocondition '1 of 2 pubkeys' + + SetCCunspents(unspentOutputs, coinaddr); // get vector with tx's with unspent vouts of 1of2pubkey address: + //std::cerr << "FindLatestFundingTx() using 1of2address=" << coinaddr << " unspentOutputs.size()=" << unspentOutputs.size() << '\n'; + + int32_t maxBlockHeight = 0; // max block height + uint256 latesttxid = fundingtxid; + + // try to find the last funding or spending tx by checking fundingtxid in 'opreturn': + for (std::vector>::const_iterator it = unspentOutputs.begin(); it != unspentOutputs.end(); it++) { + CTransaction regtx; + uint256 hash; + + uint256 txid = it->first.txhash; + //std::cerr << "FindLatestFundingTx() checking unspents for txid=" << txid.GetHex() << '\n'; + + int32_t blockHeight = (int32_t)it->second.blockHeight; + uint256 fundingTxidInOpret; + + //NOTE: maybe called from validation code: + if (myGetTransaction(txid, regtx, hash)) { + std::cerr << "FindLatestFundingTx() found tx for txid=" << txid.GetHex() << " blockHeight=" << blockHeight << " maxBlockHeight=" << maxBlockHeight << '\n'; + + { // debug code: + uint256 debAssetid; + uint8_t debfuncid = DecodeHeirOpRet(regtx.vout[regtx.vout.size() - 1].scriptPubKey, debAssetid, fundingTxidInOpret, true); + std::cerr << "FindLatestFundingTx() regtx.vout.size()=" << regtx.vout.size() << " funcId=" << (char)(debfuncid ? debfuncid : ' ') << " tokenid=" << debAssetid.GetHex() << " fundingtxidInOpret=" << fundingTxidInOpret.GetHex() << std::endl; + } + + uint256 dummyTokenid; // not to contaminate the tokenid from the params! + uint8_t tmpFuncId; + + if (regtx.vout.size() > 0 && + (tmpFuncId = DecodeHeirOpRet(regtx.vout[regtx.vout.size() - 1].scriptPubKey, dummyTokenid, fundingTxidInOpret, true)) != 0 && + fundingtxid == fundingTxidInOpret) { + + // check if heir has begun spending: + if (Helper::isSpendingTx(tmpFuncId)) { // if 'C' or 't' opret + const CScript heirScriptPubkey = CScript() << ParseHex(HexStr(heirPubkey)) << OP_CHECKSIG; + + for (int32_t v = 0; v < regtx.vout.size() - 1; v++) { // do not check opret vout + if (heirScriptPubkey == regtx.vout[v].scriptPubKey) + isHeirSpendingBegan = true; + } + } + + if (blockHeight > maxBlockHeight) { + maxBlockHeight = blockHeight; + latesttxid = txid; + ///// fundingOpretScript = regtx.vout[regtx.vout.size() - 1].scriptPubKey; + funcId = tmpFuncId; + std::cerr << "FindLatestFundingTx() txid=" << latesttxid.GetHex() << " at blockHeight=" << maxBlockHeight << " opreturn type=" << (char)(funcId ? funcId : ' ') << " set as current lasttxid" << '\n'; + } + } + } + } + + return latesttxid; +} + +// overload for validation code +template uint256 FindLatestFundingTx(uint256 fundingtxid, uint256 &tokenid, CScript& opRetScript, bool &isHeirSpendingBegan) +{ + uint8_t funcId; + CPubKey ownerPubkey; + CPubKey heirPubkey; + int64_t inactivityTime; + std::string heirName; + + return _FindLatestFundingTx(fundingtxid, funcId, tokenid, ownerPubkey, heirPubkey, inactivityTime, heirName, opRetScript, isHeirSpendingBegan); +} + +// overload for transaction creation code +template uint256 FindLatestFundingTx(uint256 fundingtxid, uint8_t& funcId, uint256 &tokenid, CPubKey& ownerPubkey, CPubKey& heirPubkey, int64_t& inactivityTime, std::string& heirName, bool &isHeirSpendingBegan) +{ + CScript opRetScript; + + return _FindLatestFundingTx(fundingtxid, funcId, tokenid, ownerPubkey, heirPubkey, inactivityTime, heirName, opRetScript, isHeirSpendingBegan); +} + +// add owner input in tokens +/*int64_t AddHeirTokenInputs(struct CCcontract_info *cp, CMutableTransaction &mtx, CPubKey pk, uint256 refassetid, int64_t total, int32_t maxinputs) +{ + char coinaddr[64], destaddr[64]; int64_t threshold, nValue, price, totalinputs = 0; uint256 tokenid, txid, hashBlock; std::vector origpubkey; std::vector vopret; CTransaction vintx; int32_t j, vout, n = 0; uint8_t evalcode, funcid; + std::vector > unspentOutputs; + GetCCaddress(cp, coinaddr, pk); + SetCCunspents(unspentOutputs, coinaddr); + threshold = total / (maxinputs + 1); + + //fprintf(stderr,"AddHeirTokenInputs() check cc addr=%s for token inputs\n",coinaddr); + for (std::vector >::const_iterator it = unspentOutputs.begin(); it != unspentOutputs.end(); it++) + { + txid = it->first.txhash; + vout = (int32_t)it->first.index; + + if (it->second.satoshis < threshold) + continue; + for (j = 0; jsecond=" << it->second.satoshis << std::endl; + + if (strcmp(destaddr, coinaddr) != 0 && strcmp(destaddr, cp->unspendableCCaddr) != 0 && strcmp(destaddr, cp->unspendableaddr2) != 0) + continue; + GetOpReturnData(vintx.vout[vintx.vout.size() - 1].scriptPubKey, vopret); + + // NOTE: special opret decode: + // we should not check for E_UNMARSHAL return code because it returns false if not EOF, + // but we have our remainder after tokenid! + bool isEof = false; + bool unmarshalResult = E_UNMARSHAL(vopret, ss >> evalcode; ss >> funcid; ss >> tokenid; isEof=ss.eof()); + if (unmarshalResult || !isEof) + { + tokenid = revuint256(tokenid); // usually DecodeOpretXXX() funcs do this + + //std::cerr << "AddHeirTokenInputs() vout=" << vout << " evalcode=" << (int)evalcode << " cp->evalcode=" << (int)cp->evalcode << " funcid=" << (char)funcid << " check for refassetid=" << refassetid.GetHex() << " vs tokenid in opret=" << tokenid.GetHex() << " coins=" << (double)vintx.vout[vout].nValue / COIN << std::endl; + if (tokenid == refassetid && + IS_CHARINSTR(funcid, "tGB") && + (nValue = vintx.vout[vout].nValue) > 0 && myIsutxo_spentinmempool(txid, vout) == 0) + { + std::cerr << "AddHeirTokenInputs() total=" << total << " maxinputs=" << maxinputs << " uxto value=" << it->second.satoshis << std::endl; + if (total != 0 && maxinputs != 0) + mtx.vin.push_back(CTxIn(txid, vout, CScript())); + //nValue = it->second.satoshis; + totalinputs += nValue; + n++; + if ((total > 0 && totalinputs >= total) || (maxinputs > 0 && n >= maxinputs)) + break; + } + } + } + } + return(totalinputs); +}*/ + +// add inputs of 1 of 2 cc address +template int64_t Add1of2AddressInputs(struct CCcontract_info* cp, uint256 fundingtxid, CMutableTransaction& mtx, CPubKey ownerPubkey, CPubKey heirPubkey, int64_t total, int32_t maxinputs) +{ + // TODO: add threshold check + int64_t nValue, voutValue, totalinputs = 0; + CTransaction vintx; + int32_t n = 0; + std::vector> unspentOutputs; + + char coinaddr[64]; + GetCCaddress1of2(cp, coinaddr, ownerPubkey, heirPubkey); // get address of cryptocondition '1 pubkey of 2 pubkeys' + SetCCunspents(unspentOutputs, coinaddr); + + // char markeraddr[64]; + // CCtxidaddr(markeraddr, fundingtxid); + // SetCCunspents(unspentOutputs, markeraddr); + //std::cerr << "Add1of2AddressInputs() using 1of2addr=" << coinaddr << " unspentOutputs.size()=" << unspentOutputs.size() << '\n'; + + for (std::vector>::const_iterator it = unspentOutputs.begin(); it != unspentOutputs.end(); it++) { + uint256 txid = it->first.txhash; + uint256 hashBlock; + int32_t voutIndex = (int32_t)it->first.index; // no need to prevent dup - if ( GetTransaction(txid,vintx,hashBlock,false) != 0 ) - { - if ( (nValue= IsHeirvout(cp,vintx,vout)) > 1000000 && myIsutxo_spentinmempool(txid,vout) == 0 ) - { - if ( total != 0 && maxinputs != 0 ) - mtx.vin.push_back(CTxIn(txid,vout,CScript())); + // dimxy: maybe it is good to put tx's in cache? + if (GetTransaction(txid, vintx, hashBlock, false) != 0) { + uint256 tokenid; + uint256 fundingTxidInOpret; + + uint8_t funcId = DecodeHeirOpRet(vintx.vout[vintx.vout.size() - 1].scriptPubKey, tokenid, fundingTxidInOpret, true); + // note: it returns in in satoshis too... + if ((txid == fundingtxid || fundingTxidInOpret == fundingtxid) && // we're spending only tx's in this funding plan + funcId != 0 && + Helper::isMyFuncId(funcId) && + // deep validation for tokens: + (typeid(Helper) == typeid(TokenHelper) && IsHeirvout(true, cp, nullptr, tokenid, vintx, voutIndex) > 0) && + (voutValue = IsHeirFundingVout(cp, vintx, voutIndex, ownerPubkey, heirPubkey)) > 0 && + !myIsutxo_spentinmempool(txid, voutIndex)) + { + std::cerr << "Add1of2AddressInputs() voutValue=" << voutValue << " satoshis=" << it->second.satoshis << '\n'; + if (total != 0 && maxinputs != 0) + mtx.vin.push_back(CTxIn(txid, voutIndex, CScript())); nValue = it->second.satoshis; totalinputs += nValue; n++; - if ( (total > 0 && totalinputs >= total) || (maxinputs > 0 && n >= maxinputs) ) + if ((total > 0 && totalinputs >= total) || (maxinputs > 0 && n >= maxinputs)) break; } } } - return(totalinputs); + return totalinputs; } -std::string HeirGet(uint64_t txfee,int64_t nValue) +/** + * enumerate all tx's sending to CCHeir 1of2address and calc total lifetime funds + */ +template int64_t LifetimeHeirContractFunds(struct CCcontract_info* cp, uint256 fundingtxid, CPubKey ownerPubkey, CPubKey heirPubkey) { - CMutableTransaction tmpmtx,mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); - CPubKey mypk,Heirpk; int64_t inputs,CCchange=0; struct CCcontract_info *cp,C; std::string rawhex; uint32_t j; int32_t i,len; uint8_t buf[32768]; bits256 hash; - cp = CCinit(&C,EVAL_HEIR); - if ( txfee == 0 ) - txfee = 10000; - Heirpk = GetUnspendable(cp,0); - mypk = pubkey2pk(Mypubkey()); - if ( (inputs= AddHeirInputs(cp,mtx,Heirpk,nValue+txfee,60)) > 0 ) - { - if ( inputs > nValue ) - CCchange = (inputs - nValue - txfee); - if ( CCchange != 0 ) - mtx.vout.push_back(MakeCC1vout(EVAL_HEIR,CCchange,Heirpk)); - mtx.vout.push_back(CTxOut(nValue,CScript() << ParseHex(HexStr(mypk)) << OP_CHECKSIG)); - fprintf(stderr,"start at %u\n",(uint32_t)time(NULL)); - j = rand() & 0xfffffff; - for (i=0; i<1000000; i++,j++) - { - tmpmtx = mtx; - rawhex = FinalizeCCTx(-1LL,cp,tmpmtx,mypk,txfee,CScript() << OP_RETURN << E_MARSHAL(ss << (uint8_t)EVAL_HEIR << (uint8_t)'G' << j)); - if ( (len= (int32_t)rawhex.size()) > 0 && len < 65536 ) + char coinaddr[64]; + GetCCaddress1of2(cp, coinaddr, ownerPubkey, heirPubkey); // get the address of cryptocondition '1 of 2 pubkeys' + + std::vector> addressIndexes; + SetCCtxids(addressIndexes, coinaddr); + + //fprintf(stderr,"LifetimeHeirContractFunds() scan lifetime of %s\n",coinaddr); + int64_t total = 0; + for (std::vector>::const_iterator it = addressIndexes.begin(); it != addressIndexes.end(); it++) { + uint256 hashBlock; + uint256 txid = it->first.txhash; + CTransaction tx; + + if (GetTransaction(txid, tx, hashBlock, false) && tx.vout.size() > 0) { + uint8_t funcId; + uint256 tokenid; + uint256 fundingTxidInOpret; + const int32_t ivout = 0; + + funcId = DecodeHeirOpRet(tx.vout[tx.vout.size() - 1].scriptPubKey, tokenid, fundingTxidInOpret, true); + + //std::cerr << "LifetimeHeirContractFunds() found tx=" << txid.GetHex() << " vout[0].nValue=" << subtx.vout[ccVoutIdx].nValue << " opreturn=" << (char)funcId << '\n'; + + if (funcId != 0 && (txid == fundingtxid || fundingTxidInOpret == fundingtxid) && Helper::isMyFuncId(funcId) && !Helper::isSpendingTx(funcId) + /* && !myIsutxo_spentinmempool(txid, ccVoutIdx) */) // include also tx in mempool { - len >>= 1; - decode_hex(buf,len,(char *)rawhex.c_str()); - hash = bits256_doublesha256(0,buf,len); - if ( (hash.bytes[0] & 0xff) == 0 && (hash.bytes[31] & 0xff) == 0 ) - { - fprintf(stderr,"found valid txid after %d iterations %u\n",i,(uint32_t)time(NULL)); - return(rawhex); - } - //fprintf(stderr,"%02x%02x ",hash.bytes[0],hash.bytes[31]); + total += it->second; // dont do this: tx.vout[ivout].nValue; // in vin[0] always is the pay to 1of2 addr (funding or change) + std::cerr << "LifetimeHeirContractFunds() added tx=" << txid.GetHex() << " it->second=" << it->second << " vout[0].nValue=" << tx.vout[ivout].nValue << " opreturn=" << (char)funcId << '\n'; } } - fprintf(stderr,"couldnt generate valid txid %u\n",(uint32_t)time(NULL)); - return(""); - } else fprintf(stderr,"cant find Heir inputs\n"); - return(""); -} - -std::string HeirFund(uint64_t txfee,int64_t funds) -{ - CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); - CPubKey mypk,Heirpk; CScript opret; struct CCcontract_info *cp,C; - cp = CCinit(&C,EVAL_HEIR); - if ( txfee == 0 ) - txfee = 10000; - mypk = pubkey2pk(Mypubkey()); - Heirpk = GetUnspendable(cp,0); - if ( AddNormalinputs(mtx,mypk,funds+txfee,64) > 0 ) - { - mtx.vout.push_back(MakeCC1vout(EVAL_HEIR,funds,Heirpk)); - return(FinalizeCCTx(0,cp,mtx,mypk,txfee,opret)); } - return(""); + return (total); } -UniValue HeirInfo() +/* rpc functions' implementation: */ + +/** + * heirfund rpc call implementation + * creates tx for initial funds deposit on cryptocondition address which locks funds for spending by either of address. + * and also for setting spending plan for the funds' owner and heir + * @return fundingtxid handle for subsequent references to this heir funding plan + */ +template std::string HeirFund(uint64_t txfee, int64_t amount, std::string heirName, CPubKey heirPubkey, int64_t inactivityTimeSec, uint256 tokenid) { - CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); - UniValue result(UniValue::VOBJ); char numstr[64]; - CPubKey Heirpk; struct CCcontract_info *cp,C; int64_t funding; - result.push_back(Pair("result","success")); - result.push_back(Pair("name","Heir")); - cp = CCinit(&C,EVAL_HEIR); - Heirpk = GetUnspendable(cp,0); - funding = AddHeirInputs(cp,mtx,Heirpk,0,0); - sprintf(numstr,"%.8f",(double)funding/COIN); - result.push_back(Pair("funding",numstr)); - return(result); + CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); + struct CCcontract_info *cp, C; + + cp = CCinit(&C, Helper::getMyEval()); + if (txfee == 0) + txfee = 10000; + + //std::cerr << "HeirFund() amount=" << amount << " txfee=" << txfee << " heirPubkey IsValid()=" << heirPubkey.IsValid() << " inactivityTime(sec)=" << inactivityTimeSec << " tokenid=" << tokenid.GetHex() << std::endl; + + if (!heirPubkey.IsValid()) { + std::cerr << "HeirFund() heirPubkey is not valid!" << std::endl; + return std::string(""); + } + + CPubKey myPubkey = pubkey2pk(Mypubkey()); + + if (AddNormalinputs(mtx, myPubkey, txfee, 3) > 0) { // txfee for miners + int64_t inputs, change; + + if ((inputs=Helper::addOwnerInputs(cp, tokenid, mtx, myPubkey, amount, (int32_t)64)) > 0) { // 2 x txfee: 1st for marker vout, 2nd to miners + //mtx.vout.push_back(MakeCC1vout(EVAL_HEIR,amount,HeirCCpk)); + mtx.vout.push_back(MakeCC1of2vout(Helper::getMyEval(), amount, myPubkey, heirPubkey)); // add cryptocondition to spend amount for either pk + + // add a marker for finding all plans in HeirList() + CPubKey HeirContractPubKey = GetUnspendable(cp, 0); + mtx.vout.push_back(CTxOut(txfee, CScript() << ParseHex(HexStr(HeirContractPubKey)) << OP_CHECKSIG)); // TODO: do we need this marker? + + // calc and add change vout: + if (inputs > amount) + change = (inputs - amount); // -txfee <-- txfee pays user + + //std::cerr << "HeirFund() inputs=" << inputs << " amount=" << amount << " txfee=" << txfee << " change=" << change << '\n'; + + if (change != 0) { // vout[1] + mtx.vout.push_back(Helper::makeUserVout(change, myPubkey)); + } + + // add change for txfee and opreturn vouts and sign tx: + return (FinalizeCCTx(0, cp, mtx, myPubkey, txfee, + // CScript() << OP_RETURN << E_MARSHAL(ss << (uint8_t)EVAL_HEIR << (uint8_t)'F' << myPubkey << heirPubkey << inactivityTimeSec << heirName))); + Helper::makeCreateOpRet(tokenid, myPubkey, heirPubkey, inactivityTimeSec, heirName))); + } + else // TODO: need result return unification with heiradd and claim + std::cerr << "HeirFund() could not find owner inputs" << std::endl; + + } + else + std::cerr << "HeirFund() could not find normal inputs" << std::endl; + return std::string(""); +} + +// if no these callers - it could not link +std::string HeirFundCoinCaller(uint64_t txfee, int64_t funds, std::string heirName, CPubKey heirPubkey, int64_t inactivityTimeSec, uint256 tokenid){ + return HeirFund(txfee, funds, heirName, heirPubkey, inactivityTimeSec, tokenid); +} + +std::string HeirFundTokenCaller(uint64_t txfee, int64_t funds, std::string heirName, CPubKey heirPubkey, int64_t inactivityTimeSec, uint256 tokenid) { + return HeirFund(txfee, funds, heirName, heirPubkey, inactivityTimeSec, tokenid); +} + +/** + * heiradd rpc call implementation + * creates tx to add more funds to cryptocondition address for spending by either funds' owner or heir + * @return result object with raw tx or error text + */ +template UniValue HeirAdd(uint256 fundingtxid, uint64_t txfee, int64_t amount) +{ + UniValue result(UniValue::VOBJ); //, a(UniValue::VARR); + CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); + CPubKey ownerPubkey, heirPubkey; + int64_t inputs, CCchange = 0; + int64_t inactivityTimeSec; + struct CCcontract_info *cp, C; + std::string rawhex; + uint256 lasttxid, tokenid; + std::string heirName; + uint8_t funcId; + bool isHeirSpendingBegan = false; + + cp = CCinit(&C, Helper::getMyEval()); + if (txfee == 0) + txfee = 10000; + + if ((lasttxid = FindLatestFundingTx(fundingtxid, funcId, tokenid, ownerPubkey, heirPubkey, inactivityTimeSec, heirName, isHeirSpendingBegan)) != zeroid) { + int32_t numblocks; + + CPubKey myPubkey = pubkey2pk(Mypubkey()); + + // check if it is the owner + if (myPubkey != ownerPubkey) { + result.push_back(Pair("result", "error")); + result.push_back(Pair("error", "adding funds is only allowed for the owner of this contract")); + return result; + } + + if (AddNormalinputs(mtx, myPubkey, txfee, 3) > 0) { // txfee for miners + + int64_t inputs, change; + + //if (AddNormalinputs(mtx, myPubkey, amount + 1 * txfee, 64) > 0) { // TODO: why 64 max inputs? + if ((inputs = Helper::addOwnerInputs(cp, tokenid, mtx, myPubkey, amount, 64)) > 0) { // TODO: why 64 max inputs? + + // we do not use markers anymore - storing data in opreturn is better + // add marker vout: + /* char markeraddr[64]; + CPubKey markerpubkey = CCtxidaddr(markeraddr, fundingtxid); + mtx.vout.push_back(CTxOut(txfee, CScript() << ParseHex(HexStr(markerpubkey)) << OP_CHECKSIG)); // txfee 1, txfee 2 - for miners + std::cerr << "HeirAdd() adding markeraddr=" << markeraddr << '\n'; */ + + // add cryptocondition to spend this funded amount for either pk + mtx.vout.push_back(MakeCC1of2vout(Helper::getMyEval(), amount, ownerPubkey, heirPubkey)); // using always pubkeys from OP_RETURN in order to not mixing them up! + + // calc and add change vout: + if (inputs > amount) + change = (inputs - amount); // -txfee <-- txfee pays user + + //std::cerr << "HeirAdd() inputs=" << inputs << " amount=" << amount << " txfee=" << txfee << " change=" << change << '\n'; + + if (change != 0) { // vout[1] + mtx.vout.push_back(Helper::makeUserVout(change, myPubkey)); + } + + // add opreturn 'A' and sign tx: // this txfee ignored + std::string rawhextx = (FinalizeCCTx(0, cp, mtx, myPubkey, txfee, + Helper::makeAddOpRet(tokenid, fundingtxid))); + + result.push_back(Pair("result", "success")); + result.push_back(Pair("hextx", rawhextx)); + } + else { + std::cerr << "HeirAdd cannot find owner inputs" << std::endl; + result.push_back(Pair("result", "error")); + result.push_back(Pair("error", "can't find owner inputs")); + } + } + else { + std::cerr << "HeirAdd cannot find normal inputs for tx fee" << std::endl; + result.push_back(Pair("result", "error")); + result.push_back(Pair("error", "can't find normal inputs for tx fee")); + } + + } else { + fprintf(stderr, "HeirAdd() can't find any heir CC funding tx's\n"); + + result.push_back(Pair("result", "error")); + result.push_back(Pair("error", "can't find any heir CC funding transactions")); + } + + return result; +} + + +UniValue HeirAddCoinCaller(uint256 fundingtxid, uint64_t txfee, int64_t amount) { + return HeirAdd(fundingtxid, txfee, amount); +} +UniValue HeirAddTokenCaller(uint256 fundingtxid, uint64_t txfee, int64_t amount) { + return HeirAdd(fundingtxid, txfee, amount); +} + + +/** + * heirclaim rpc call implementation + * creates tx to spend funds from cryptocondition address by either funds' owner or heir + * @return result object with raw tx or error text + */ +template UniValue HeirClaim(uint256 fundingtxid, uint64_t txfee, int64_t amount) +{ + UniValue result(UniValue::VOBJ); //, a(UniValue::VARR); + CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); + CPubKey myPubkey, ownerPubkey, heirPubkey; + int64_t inputs, change = 0; + int64_t inactivityTimeSec; + struct CCcontract_info *cp, C; + + uint256 latesttxid, tokenid; + uint8_t funcId; + std::string heirName; + bool isHeirSpendingBegan = false; + + + cp = CCinit(&C, Helper::getMyEval()); + if (txfee == 0) + txfee = 10000; + + if ((latesttxid = FindLatestFundingTx(fundingtxid, funcId, tokenid, ownerPubkey, heirPubkey, inactivityTimeSec, heirName, isHeirSpendingBegan)) != zeroid) { + int32_t numblocks; + uint64_t durationSec = 0; + + // we do not need to find duration if spending already has begun + if (!isHeirSpendingBegan) { + durationSec = CCduration(numblocks, latesttxid); + std::cerr << "HeirClaim() duration=" << durationSec << " inactivityTime=" << inactivityTimeSec << " numblocks=" << numblocks << std::endl; + } + + // spending is allowed if there is already spending tx or inactivity time + //bool isAllowedToHeir = (funcId == 'C' || durationSec > inactivityTimeSec) ? true : false; + bool isAllowedToHeir = (isHeirSpendingBegan || durationSec > inactivityTimeSec) ? true : false; + myPubkey = pubkey2pk(Mypubkey()); + + // if it is the heir, check if spending not allowed to heir yet + if (myPubkey == heirPubkey && !isAllowedToHeir) { + result.push_back(Pair("result", "error")); + result.push_back(Pair("error", "spending is not allowed yet for the heir")); + return result; + } + + // we do not use markers any more: + // we allow owner to spend funds at any time: + // if it is the owner, check if spending already allowed to heir + /* if (myPubkey == ownerPubkey && isAllowedToHeir) { + result.push_back(Pair("result", "spending is not already allowed for the owner")); + return result; + } */ + + // add spending txfee from the calling user + if (AddNormalinputs(mtx, myPubkey, txfee, 3) > 0) { + + // add spending from cc 1of2 address + if ((inputs = Add1of2AddressInputs(cp, fundingtxid, mtx, ownerPubkey, heirPubkey, amount, 60)) >= amount) // TODO: why only 60 inputs? + { + /*if (inputs < amount) { + std::cerr << "HeirClaim() cant find enough HeirCC 1of2 inputs, found=" << inputs << " required=" << amount << std::endl; + result.push_back(Pair("result", "error")); + result.push_back(Pair("error", "can't find heir CC funding")); + + return result; + }*/ + + // add vout with amount to claiming address + mtx.vout.push_back(Helper::makeClaimerVout(amount, myPubkey)); // vout[0] + + // calc and add change vout: + if (inputs > amount) + change = (inputs - amount); // -txfee <-- txfee pays user + + //std::cerr << "HeirClaim() inputs=" << inputs << " amount=" << amount << " txfee=" << txfee << " change=" << change << '\n'; + + // change to 1of2 funding addr: + if (change != 0) { // vout[1] + mtx.vout.push_back(MakeCC1of2vout(Helper::getMyEval(), change, ownerPubkey, heirPubkey)); // using always pubkeys from OP_RETURN in order to not mixing them up! + } + + // add marker vout: + /*char markeraddr[64]; + CPubKey markerpubkey = CCtxidaddr(markeraddr, fundingtxid); + // NOTE: amount = 0 is not working: causes error code: -26, error message : 64 : dust + mtx.vout.push_back(CTxOut(txfee, CScript() << ParseHex(HexStr(markerpubkey)) << OP_CHECKSIG)); // txfee 1, txfee 2 - for miners + std::cerr << "HeirClaim() adding markeraddr=" << markeraddr << '\n'; */ + + uint8_t myprivkey[32]; + char coinaddr[64]; + // set priv key addresses in CC structure: + GetCCaddress1of2(cp, coinaddr, ownerPubkey, heirPubkey); + Myprivkey(myprivkey); + + //fprintf(stderr,"HeirClaim() before setting unspendable CC addr2= (%s) addr3= (%s)\n", cp->unspendableaddr2, cp->unspendableaddr3); + CCaddr2set(cp, Helper::getMyEval(), ownerPubkey, myprivkey, coinaddr); + CCaddr3set(cp, Helper::getMyEval(), heirPubkey, myprivkey, coinaddr); + + fprintf(stderr, "HeirClaim() after setting unspendable CC addr2=(%s) addr3=(%s)\n", cp->unspendableaddr2, cp->unspendableaddr3); + + // add opreturn 'C' and sign tx: // this txfee will be ignored + std::string rawhextx = FinalizeCCTx(0, cp, mtx, myPubkey, txfee, + Helper::makeClaimOpRet(tokenid, fundingtxid)); + + result.push_back(Pair("result", "success")); + result.push_back(Pair("hextx", rawhextx)); + + } else { + fprintf(stderr, "HeirClaim() cant find Heir CC inputs\n"); + result.push_back(Pair("result", "error")); + result.push_back(Pair("error", "can't find heir CC funding")); + } + } else { + fprintf(stderr, "HeirClaim() cant find sufficient user inputs for tx fee\n"); + result.push_back(Pair("result", "error")); + result.push_back(Pair("error", "can't find sufficient user inputs to pay transaction fee")); + } + } else { + fprintf(stderr, "HeirClaim() can't find any heir CC funding tx's\n"); + result.push_back(Pair("result", "error")); + result.push_back(Pair("error", "can't find any heir CC funding transactions")); + } + + return result; +} + +UniValue HeirClaimCoinCaller(uint256 fundingtxid, uint64_t txfee, int64_t amount) { + return HeirClaim(fundingtxid, txfee, amount); +} +UniValue HeirClaimTokenCaller(uint256 fundingtxid, uint64_t txfee, int64_t amount) { + return HeirClaim(fundingtxid, txfee, amount); +} + + + +/** + * heirinfo rpc call implementation + * returns some information about heir CC contract plan by a handle of initial fundingtxid: + * plan name, owner and heir pubkeys, funds deposited and available, flag if spending is enabled for the heir + * @return heir info data + */ +UniValue HeirInfo(uint256 fundingtxid) +{ + UniValue result(UniValue::VOBJ); + + CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); + CPubKey ownerPubkey, heirPubkey; + uint256 latestFundingTxid; + uint256 dummyTokenid, tokenid; + + std::string heirName; + uint8_t funcId; + int64_t inactivityTimeSec; + + CTransaction fundingtx; + uint256 hashBlock; + const bool allowSlow = false; + + //char markeraddr[64]; + //CCtxidaddr(markeraddr, fundingtxid); + //SetCCunspents(unspentOutputs, markeraddr); + + // get initial funding tx and set it as initial lasttx: + if (myGetTransaction(fundingtxid, fundingtx, hashBlock) && fundingtx.vout.size()) { + + int32_t heirType = NOT_HEIR; + if (DecodeHeirOpRet(fundingtx.vout[fundingtx.vout.size() - 1].scriptPubKey, dummyTokenid, ownerPubkey, heirPubkey, inactivityTimeSec, heirName, true) == 'F') + heirType = HEIR_COINS; + else if (DecodeHeirOpRet(fundingtx.vout[fundingtx.vout.size() - 1].scriptPubKey, tokenid, ownerPubkey, heirPubkey, inactivityTimeSec, heirName, true) == 'F') + heirType = HEIR_TOKENS; + else + { + std::cerr << "HeirInfo() initial tx F not found for this fundingtx" << std::endl; + result.push_back(Pair("result", "error")); + result.push_back(Pair("error", "initial tx F not found")); + return result; + } + + struct CCcontract_info *cp, C; + if (heirType == HEIR_COINS) + cp = CCinit(&C, CoinHelper::getMyEval()); + else + cp = CCinit(&C, TokenHelper::getMyEval()); + + bool isHeirSpendingBegan = false; + + if (heirType == HEIR_COINS) + latestFundingTxid = FindLatestFundingTx(fundingtxid, funcId, tokenid, ownerPubkey, heirPubkey, inactivityTimeSec, heirName, isHeirSpendingBegan); + else + latestFundingTxid = FindLatestFundingTx(fundingtxid, funcId, tokenid, ownerPubkey, heirPubkey, inactivityTimeSec, heirName, isHeirSpendingBegan); + + if (latestFundingTxid != zeroid) { + int32_t numblocks; + uint64_t durationSec = 0; + + std::cerr << "HeirInfo() latesttxid=" << latestFundingTxid.GetHex() << '\n'; + + std::ostringstream stream; + std::string msg; + + result.push_back(Pair("fundingtxid", fundingtxid.GetHex())); + result.push_back(Pair("name", heirName.c_str())); + + if (heirType == HEIR_TOKENS) { + stream << tokenid.GetHex(); + msg = "tokenid"; + result.push_back(Pair(msg, stream.str().c_str())); + stream.str(""); + stream.clear(); + } + + char hexbuf[67]; + stream << pubkey33_str(hexbuf, (uint8_t*)ownerPubkey.begin()); + result.push_back(Pair("owner", stream.str().c_str())); + stream.str(""); + stream.clear(); + + stream << pubkey33_str(hexbuf, (uint8_t*)heirPubkey.begin()); + result.push_back(Pair("heir", stream.str().c_str())); + stream.str(""); + stream.clear(); + + int64_t total; + if (heirType == HEIR_COINS) + total = LifetimeHeirContractFunds(cp, fundingtxid, ownerPubkey, heirPubkey); + else + total = LifetimeHeirContractFunds(cp, fundingtxid, ownerPubkey, heirPubkey); + + if (heirType == HEIR_COINS) { + msg = "funding total in coins"; + stream << (double)total / COIN; + } + else { + msg = "funding total in tokens"; + stream << total; + } + result.push_back(Pair(msg, stream.str().c_str())); + stream.str(""); + stream.clear(); + + int64_t inputs; + if (heirType == HEIR_COINS) + inputs = Add1of2AddressInputs(cp, fundingtxid, mtx, ownerPubkey, heirPubkey, 0, 60); //NOTE: amount = 0 means all unspent inputs + else + inputs = Add1of2AddressInputs(cp, fundingtxid, mtx, ownerPubkey, heirPubkey, 0, 60); + + if (heirType == HEIR_COINS) { + msg = "funding available in coins"; + stream << (double)inputs / COIN; + } + else { + msg = "funding available in tokens"; + stream << inputs; + } + result.push_back(Pair(msg, stream.str().c_str())); + stream.str(""); + stream.clear(); + + if (heirType == HEIR_TOKENS) { + int64_t ownerInputs = TokenHelper::addOwnerInputs(cp, tokenid, mtx, ownerPubkey, 0, (int32_t)64); + stream << ownerInputs; + msg = "owner funding available in tokens"; + result.push_back(Pair(msg, stream.str().c_str())); + stream.str(""); + stream.clear(); + } + + stream << inactivityTimeSec; + result.push_back(Pair("inactivity time setting", stream.str().c_str())); + stream.str(""); + stream.clear(); + + if (!isHeirSpendingBegan) { // we do not need find duration if the spending already has begun + durationSec = CCduration(numblocks, latestFundingTxid); + std::cerr << "HeirInfo() duration=" << durationSec << " inactivityTime=" << inactivityTimeSec << " numblocks=" << numblocks << '\n'; + } + + stream << std::boolalpha << (isHeirSpendingBegan || durationSec > inactivityTimeSec); + result.push_back(Pair("spending allowed for the heir", stream.str().c_str())); + stream.str(""); + stream.clear(); + + result.push_back(Pair("result", "success")); + } + else { + result.push_back(Pair("result", "error")); + result.push_back(Pair("error", "could not find heir cc plan for this txid")); + } + } + else { + result.push_back(Pair("result", "error")); + result.push_back(Pair("error", "could not find heir cc plan for this txid (no initial tx)")); + } + return (result); +} + +/** + * heirlist rpc call implementation + * @return list of heir plan handles (fundingtxid) + */ + +template void _HeirList(struct CCcontract_info *cp, UniValue &result) +{ + std::vector> unspentOutputs; + char coinaddr[64]; + CPubKey ccPubKeyEmpty; + GetCCaddress(cp, coinaddr, ccPubKeyEmpty); + SetCCunspents(unspentOutputs, cp->normaladdr); + + std::cerr << "HeirList() finding heir marker from Heir contract addr=" << cp->normaladdr << " unspentOutputs.size()=" << unspentOutputs.size() << '\n'; + + // TODO: move marker to special cc addr to prevent checking all tokens + for (std::vector>::const_iterator it = unspentOutputs.begin(); it != unspentOutputs.end(); it++) { + uint256 hashBlock; + uint256 txid = it->first.txhash; + uint256 tokenid; + int32_t vout = (int32_t)it->first.index; + + std::cerr << "HeirList() checking txid=" << txid.GetHex() << " vout=" << vout << '\n'; + + CTransaction vintx; + if (GetTransaction(txid, vintx, hashBlock, false) != 0 && (vintx.vout.size() - 1) > 0) { + CPubKey ownerPubkey, heirPubkey; + std::string heirName; + int64_t inactivityTimeSec; + + uint8_t funcId = DecodeHeirOpRet(vintx.vout[vintx.vout.size() - 1].scriptPubKey, tokenid, ownerPubkey, heirPubkey, inactivityTimeSec, heirName); + + // note: if it is not Heir token funcId would be equal to 0 + if (funcId == 'F') { + result.push_back(Pair("fundingtxid heirName", txid.GetHex() + std::string(" ") + (typeid(Helper) == typeid(TokenHelper) ? std::string("token") : std::string("coin")) + std::string(" ") + heirName)); + } + else { + fprintf(stderr, "HeirList() couldnt find initial transaction\n"); + } + } + else { + fprintf(stderr, "HeirList() couldnt load transaction\n"); + } + } +} + + +UniValue HeirList() +{ + UniValue result(UniValue::VOBJ); + result.push_back(Pair("result", "success")); + + struct CCcontract_info *cpHeir, *cpTokens, C; + + cpHeir = CCinit(&C, EVAL_HEIR); + cpTokens = CCinit(&C, EVAL_TOKENS); + + _HeirList(cpHeir, result); + _HeirList(cpTokens, result); + + return result; } diff --git a/src/cc/heir_validate.h b/src/cc/heir_validate.h new file mode 100644 index 000000000..8b432e8e0 --- /dev/null +++ b/src/cc/heir_validate.h @@ -0,0 +1,622 @@ +#ifndef HEIR_VALIDATE_H +#define HEIR_VALIDATE_H + +#include "CCinclude.h" +#include "CCHeir.h" + +#define NOT_HEIR (-1) +#define HEIR_COINS 1 +#define HEIR_TOKENS 2 +#define IS_CHARINSTR(c, str) (std::string(str).find((char)(c)) != std::string::npos) + + +// makes coin initial tx opret +CScript EncodeHeirCreateOpRet(uint8_t eval, uint8_t funcid, CPubKey ownerPubkey, CPubKey heirPubkey, int64_t inactivityTimeSec, std::string heirName); +// makes coin additional tx opret +CScript EncodeHeirOpRet(uint8_t eval, uint8_t funcid, uint256 fundingtxid); + +CScript EncodeHeirAssetsCreateOpRet(uint8_t eval, uint8_t funcid, uint256 tokenid, CPubKey ownerPubkey, CPubKey heirPubkey, int64_t inactivityTimeSec, std::string hearName); +CScript EncodeHeirAssetsOpRet(uint8_t eval, uint8_t funcid, uint256 tokenid, uint256 fundingtxid); +//CScript EncodeHeirConvertedAssetsOpRet(uint8_t eval, uint8_t funcid, uint256 tokenid, CPubKey ownerPubkey, CPubKey heirPubkey, int64_t inactivityTimeSec, uint256 fundingtxid); + +template uint256 FindLatestFundingTx(uint256 fundingtxid, uint256 &tokenid, CScript& opRetScript, bool &isHeirSpendingBegan); +template uint8_t DecodeHeirOpRet(CScript scriptPubKey, uint256 &tokenid, uint256& fundingtxid, bool noLogging = false); +//template uint8_t DecodeHeirOpRet(CScript scriptPubKey, uint256 &tokenid, CPubKey& ownerPubkey, CPubKey& heirPubkey, int64_t& inactivityTime, std::string& heirName, bool noLogging = false); +template uint8_t DecodeHeirOpRet(CScript scriptPubKey, uint256 &tokenid, CPubKey& ownerPubkey, CPubKey& heirPubkey, int64_t& inactivityTime, std::string& heirName, bool noLogging = false); + +//int64_t AddHeirTokenInputs(struct CCcontract_info *cp, CMutableTransaction &mtx, CPubKey pk, uint256 reftokenid, int64_t total, int32_t maxinputs); + + +// helper class to allow polymorphic behaviour for HeirXXX() functions in case of coins +class CoinHelper { +public: + + static bool isMyFuncId(uint8_t funcid) { return IS_CHARINSTR(funcid, "FAC"); } + static uint8_t getMyEval() { return EVAL_HEIR; } + static int64_t addOwnerInputs(struct CCcontract_info* cp, uint256 dummyid, CMutableTransaction& mtx, CPubKey ownerPubkey, int64_t total, int32_t maxinputs) { + return AddNormalinputs(mtx, ownerPubkey, total, maxinputs); + } + + static CScript makeCreateOpRet(uint256 dummyid, CPubKey ownerPubkey, CPubKey heirPubkey, int64_t inactivityTimeSec, std::string heirName) { + return EncodeHeirCreateOpRet((uint8_t)EVAL_HEIR, (uint8_t)'F', ownerPubkey, heirPubkey, inactivityTimeSec, heirName); + } + static CScript makeAddOpRet(uint256 dummyid, uint256 fundingtxid) { + return EncodeHeirOpRet((uint8_t)EVAL_HEIR, (uint8_t)'A', fundingtxid); + } + static CScript makeClaimOpRet(uint256 dummyid, uint256 fundingtxid) { + return EncodeHeirOpRet((uint8_t)EVAL_HEIR, (uint8_t)'C', fundingtxid); + } + + static void UnmarshalOpret(std::vector vopret, uint8_t &e, uint8_t &funcId, uint256 &dummytokenid, CPubKey& ownerPubkey, CPubKey& heirPubkey, int64_t& inactivityTime, std::string& heirName, uint256& fundingTxidInOpret) { + E_UNMARSHAL(vopret, { ss >> e; ss >> funcId; ss >> ownerPubkey; ss >> heirPubkey; ss >> inactivityTime; if (IS_CHARINSTR(funcId, "F")) { ss >> heirName; } if (IS_CHARINSTR(funcId, "AC")) { ss >> fundingTxidInOpret; } }); + } + + static bool isSpendingTx(uint8_t funcid) { return (funcid == 'C'); } + + static CTxOut makeUserVout(int64_t amount, CPubKey myPubkey) { + return CTxOut(amount, CScript() << ParseHex(HexStr(myPubkey)) << OP_CHECKSIG); + } + static CTxOut makeClaimerVout(int64_t amount, CPubKey myPubkey) { + return CTxOut(amount, CScript() << ParseHex(HexStr(myPubkey)) << OP_CHECKSIG); + } +}; + +// helper class to allow polymorphic behaviour for HeirXXX() functions in case of tokens +class TokenHelper { +public: + + static bool isMyFuncId(uint8_t funcid) { return IS_CHARINSTR(funcid, "FAC"); } + static uint8_t getMyEval() { return EVAL_TOKENS; } + static int64_t addOwnerInputs(struct CCcontract_info* cp, uint256 tokenid, CMutableTransaction& mtx, CPubKey ownerPubkey, int64_t total, int32_t maxinputs) { + return AddTokenCCInputs(cp, mtx, ownerPubkey, tokenid, total, maxinputs); + } + + static CScript makeCreateOpRet(uint256 tokenid, CPubKey ownerPubkey, CPubKey heirPubkey, int64_t inactivityTimeSec, std::string heirName) { + return EncodeHeirAssetsCreateOpRet((uint8_t)EVAL_HEIR, (uint8_t)'F', tokenid, ownerPubkey, heirPubkey, inactivityTimeSec, heirName); + } + static CScript makeAddOpRet(uint256 tokenid, uint256 fundingtxid) { + return EncodeHeirAssetsOpRet((uint8_t)EVAL_HEIR, (uint8_t)'A', tokenid, fundingtxid); + } + static CScript makeClaimOpRet(uint256 tokenid, uint256 fundingtxid) { + return EncodeHeirAssetsOpRet((uint8_t)EVAL_HEIR, (uint8_t)'C', tokenid, fundingtxid); + } + + static void UnmarshalOpret(std::vector vopret, uint8_t &e, uint8_t &funcId, uint256 &tokenid, CPubKey& ownerPubkey, CPubKey& heirPubkey, int64_t& inactivityTime, std::string& heirName, uint256& fundingtxidInOpret) { + uint8_t assetFuncId = '\0'; + bool result = E_UNMARSHAL(vopret, { ss >> e; ss >> assetFuncId; ss >> tokenid; ss >> funcId; if (IS_CHARINSTR(funcId, "F")) { ss >> ownerPubkey; ss >> heirPubkey; ss >> inactivityTime; ss >> heirName; } if (IS_CHARINSTR(funcId, "AC")) { ss >> fundingtxidInOpret; } }); + if (!result /*|| assetFuncId != 't' -- any tx is ok*/) + funcId = 0; + } + static bool isSpendingTx(uint8_t funcid) { return (funcid == 'C'); } + + static CTxOut makeUserVout(int64_t amount, CPubKey myPubkey) { + return MakeCC1vout(EVAL_TOKENS, amount, myPubkey); + } + static CTxOut makeClaimerVout(int64_t amount, CPubKey myPubkey) { + return MakeCC1vout(EVAL_TOKENS, amount, myPubkey); + } +}; + +//#define OPTIONAL_VOUT 0 // if vout is optional then in a validation plan it will be skipped without error, if all validators return false + + + +/** +* Small framework for vins and vouts validation implementing a variation of 'chain of responsibility' pattern: +* It consists of two classes CInputValidationPlan and COutputValidationPlan which both are configured with an array of vectors of validators +* (These validators are derived from the class CValidatorBase). +* +* A example of a validator may verify for a vout if its public key corresponds to the public key which is stored in opreturn. +* Or, vin validator may check if this vin depicts correctly to the CC contract's address. +* +* For validating vins CInputValidator additionally is provided with an instance of a class derived from the CInputIdentifierBase class. +* this identifier class allows to select identical vins (for example, normal vins or cc input vins) and apply validators from the corresponding vector to it. +* Note: CInputValidator treats that at least one identified vin should be present, otherwise it returns eval->invalid() and false. +* +* For validating vouts COutputValidator is configured for each vector of validators with the vout index to which these validators are applied +* (see constructors of both CInputValidator and COutputValidator) +* +* +* Base class for all validators +*/ +/** + * base class for all validators + */ +class CValidatorBase +{ +public: + CValidatorBase(CCcontract_info* cp) : m_cp(cp) {} + virtual bool isVinValidator() const = 0; + virtual bool validateVin(CTxIn vin, CTxOut prevVout, std::string& message) const = 0; + virtual bool validateVout(CTxOut vout, std::string& message) const = 0; + +protected: + CCcontract_info * m_cp; +}; + + +/** + * Base class for classes which identify vins as normal or cc inputs + */ +class CInputIdentifierBase +{ +public: + CInputIdentifierBase(CCcontract_info* cp) : m_cp(cp) {} + virtual std::string inputName() const = 0; + virtual bool identifyInput(CTxIn vin) const = 0; +protected: + CCcontract_info * m_cp; +}; + + + + +/** +* Encapsulates an array containing rows of validators +* Each row is a vector of validators (zero is possible) for validating vins or prev tx's vouts +* this validation plan is used for validating tx inputs +*/ +template +class CInputValidationPlan +{ + using ValidatorsRow = std::vector; + +public: + + // Pushes a row of validators for validating a vin or vout + // @param CInputIdentifierBase* pointer to class-identifier which determines several identical adjacent vins (like in schema "vin.0+: normal inputs") + // @param pargs parameter pack of zero or more pointer to validator objects + // Why pointers? because we store the base class in validators' row and then call its virtual functions + template + void pushValidators(CInputIdentifierBase *identifier, ARGS*... pargs) // validators row passed as variadic arguments CValidatorX *val1, CValidatorY *val2 ... + { + ValidatorsRow vValidators({ (TValidatorBase*)pargs... }); + m_arrayValidators.push_back(std::make_pair(identifier, vValidators)); + } + + // validate tx inputs and corresponding prev tx vouts + bool validate(const CTransaction& tx, Eval* eval) + { + std::string message = ""; + //std::cerr << "CInputValidationPlan::validate() starting vins validation..." << std::endl; + + int32_t ival = 0; + int32_t iv = 0; + int32_t numv = tx.vin.size(); + int32_t numValidators = m_arrayValidators.size(); + + // run over vins: + while (iv < numv && ival < numValidators) { + + int32_t identifiedCount = 0; + CInputIdentifierBase *identifier = m_arrayValidators[ival].first; + // check if this is 'our' input: + while (iv < numv && identifier->identifyInput(tx.vin[iv])) { + + // get prev tx: + CTransaction prevTx, *pPrevTxOrNull = NULL; + uint256 hashBlock; + + if (!eval->GetTxUnconfirmed(tx.vin[iv].prevout.hash, prevTx, hashBlock)) { + std::ostringstream stream; + stream << "can't find vinTx for vin=" << iv << "."; + return eval->Invalid(stream.str().c_str()); + } + pPrevTxOrNull = &prevTx; // TODO: get prev tx only if it required (i.e. if vout validators are present) + + // exec 'validators' from validator row of ival index, for tx.vin[iv] + if (!execValidatorsInRow(&tx, pPrevTxOrNull, iv, ival, message)) { + std::ostringstream stream; + stream << "invalid tx vin[" << iv << "]:" << message; + return eval->Invalid(stream.str().c_str()); // ... if not, return 'invalid' + } + + identifiedCount++; // how many vins we identified + iv++; // advance to the next vin + } + + // CInputValidationPlan treats that there must be at least one identified vin for configured validators' row + // like in 'vin.0: normal input' + if (identifiedCount == 0) { + std::ostringstream stream; + stream << "can't find required vins for " << identifier->inputName() << "."; + return eval->Invalid(stream.str().c_str()); + } + + ival++; // advance to the next validator row + // and it will try the same vin with the new CInputIdentifierBase and validators row + } + + // validation is successful if all validators have been used (i.e. ival = numValidators) + if (ival < numValidators) { + std::cerr << "CInputValidationPlan::validate() incorrect tx" << " ival=" << ival << " numValidators=" << numValidators << std::endl; + return eval->Invalid("incorrect tx structure: not all required vins are present."); + } + + std::cerr << "CInputValidationPlan::validate() returns with true" << std::endl; + return true; + } + +private: + // Executes validators from the requested row of validators (selected by iValidators) for selected vin or vout (selected by iv) + bool execValidatorsInRow(const CTransaction* pTx, const CTransaction* pPrevTx, int32_t iv, int32_t ival, std::string& refMessage) const + { + // check boundaries: + if (ival < 0 || ival >= m_arrayValidators.size()) { + std::cerr << "CInputValidationPlan::execValidatorsInRow() internal error: incorrect param ival=" << ival << " size=" << m_arrayValidators.size(); + refMessage = "internal error: incorrect param ival index"; + return false; + } + + if (iv < 0 || iv >= pTx->vin.size()) { + std::cerr << "CInputValidationPlan::execValidatorsInRow() internal error: incorrect param iv=" << iv << " size=" << m_arrayValidators.size(); + refMessage = "internal error: incorrect param iv index"; + return false; + } + + // get requested row of validators: + ValidatorsRow vValidators = m_arrayValidators[ival].second; + + std::cerr << "CInputValidationPlan::execValidatorsInRow() calling validators" << " for vin iv=" << iv << " ival=" << ival << std::endl; + + for (auto v : vValidators) { + bool result; + + if (v->isVinValidator()) + // validate this vin and previous vout: + result = v->validateVin(pTx->vin[iv], pPrevTx->vout[pTx->vin[iv].prevout.n], refMessage); + else + // if it is vout validator pass the previous tx vout: + result = v->validateVout( pPrevTx->vout[pTx->vin[iv].prevout.n], refMessage); + if (!result) { + return result; + } + } + return true; // validation OK + } + + +private: + //std::map m_arrayValidators; + std::vector< std::pair > m_arrayValidators; + +}; + + +/** +* Encapsulates an array containing rows of validators +* Each row is a vector of validators (zero is possible) for validating vouts +* this validation plan is used for validating tx outputs +*/ +template +class COutputValidationPlan +{ + using ValidatorsRow = std::vector; + +public: + // Pushes a row of validators for validating a vout + // @param ivout index to vout to validate + // @param pargs parameter pack of zero or more pointer to validator objects + // Why pointers? because we store base class and call its virtual functions + + template + void pushValidators(int32_t ivout, ARGS*... pargs) // validators row passed as variadic arguments CValidatorX *val1, CValidatorY *val2 ... + { + ValidatorsRow vValidators({ (TValidatorBase*)pargs... }); + m_arrayValidators.push_back(std::make_pair(ivout, vValidators)); + } + + // validate tx outputs + bool validate(const CTransaction& tx, Eval* eval) + { + std::string message = ""; + //std::cerr << "COutputValidationPlan::validateOutputs() starting vouts validation..." << std::endl; + + int32_t ival = 0; + int32_t numVouts = tx.vout.size(); + int32_t numValidators = m_arrayValidators.size(); + + // run over vouts: + while (ival < numValidators) { + + int32_t ivout = m_arrayValidators[ival].first; + if (ivout >= numVouts) { + std::cerr << "COutputValidationPlan::validate() incorrect tx" << "for ival=" << ival << " in tx.vout no such ivout=" << ivout << std::endl; + return eval->Invalid("incorrect tx structure: not all required vouts are present."); + } + else + { + // exec 'validators' from validator row of ival index, for tx.vout[ivout] + if (!execValidatorsInRow(&tx, ivout, ival, message)) { + std::ostringstream stream; + stream << "invalid tx vout[" << ivout << "]:" << message; + return eval->Invalid(stream.str().c_str()); // ... if not, return 'invalid' + } + } + + ival++; // advance to the next vout + + } + + std::cerr << "COutputValidationPlan::validate() returns with true" << std::endl; + return true; + } + +private: + // Executes validators from the requested row of validators (selected by iValidators) for selected vin or vout (selected by iv) + bool execValidatorsInRow(const CTransaction* pTx, int32_t iv, int32_t ival, std::string& refMessage) const + { + // check boundaries: + if (ival < 0 || ival >= m_arrayValidators.size()) { + std::cerr << "COutputValidationPlan::execValidatorsInRow() internal error: incorrect param ival=" << ival << " size=" << m_arrayValidators.size(); + refMessage = "internal error: incorrect param ival index"; + return false; + } + + if (iv < 0 || iv >= pTx->vout.size()) { + std::cerr << "COutputValidationPlan::execValidatorsInRow() internal error: incorrect param iv=" << iv << " size=" << m_arrayValidators.size(); + refMessage = "internal error: incorrect param iv index"; + return false; + } + + // get requested row of validators: + ValidatorsRow vValidators = m_arrayValidators[ival].second; + + std::cerr << "COutputValidationPlan::execRow() calling validators" << " for vout iv=" << iv << " ival=" << ival << std::endl; + + for (auto v : vValidators) { + + if (!v->isVinValidator()) { + // if this is a 'in' validation plan then pass the previous tx vout: + bool result = v->validateVout(pTx->vout[iv], refMessage); + if (!result) + return result; + } + } + return true; // validation OK + } + + +private: + //std::map m_mapValidators; + std::vector< std::pair > m_arrayValidators; + +}; + + +class CNormalInputIdentifier : CInputIdentifierBase { +public: + CNormalInputIdentifier(CCcontract_info* cp) : CInputIdentifierBase(cp) {} + virtual std::string inputName() const { return std::string("normal input"); } + virtual bool identifyInput(CTxIn vin) const { + return !IsCCInput(vin.scriptSig); + } +}; + +class CCCInputIdentifier : CInputIdentifierBase { +public: + CCCInputIdentifier(CCcontract_info* cp) : CInputIdentifierBase(cp) {} + virtual std::string inputName() const { return std::string("CC input"); } + virtual bool identifyInput(CTxIn vin) const { + return IsCCInput(vin.scriptSig); + } +}; + + +/** +* Validates 1of2address for vout (may be used for either this or prev tx) +*/ +template class CCC1of2AddressValidator : CValidatorBase +{ +public: + CCC1of2AddressValidator(CCcontract_info* cp, CScript opRetScript, std::string customMessage = "") : + m_fundingOpretScript(opRetScript), m_customMessage(customMessage), CValidatorBase(cp) {} + + virtual bool isVinValidator() const { return false; } + virtual bool validateVout(CTxOut vout, std::string& message) const + { + //std::cerr << "CCC1of2AddressValidator::validateVout() entered" << std::endl; + uint8_t funcId; + CPubKey ownerPubkey, heirPubkey; + int64_t inactivityTime; + std::string heirName; + uint256 tokenid; + + if ((funcId = DecodeHeirOpRet(m_fundingOpretScript, tokenid, ownerPubkey, heirPubkey, inactivityTime, heirName)) == 0) { + message = m_customMessage + std::string(" invalid opreturn format"); + std::cerr << "CCC1of2AddressValidator::validateVout() exits with false: " << message << std::endl; + return false; + } + + char shouldBeAddr[65], ccAddr[65]; + + GetCCaddress1of2(m_cp, shouldBeAddr, ownerPubkey, heirPubkey); + if (vout.scriptPubKey.IsPayToCryptoCondition()) { + if (Getscriptaddress(ccAddr, vout.scriptPubKey) && strcmp(shouldBeAddr, ccAddr) == 0) { + std::cerr << "CCC1of2AddressValidator::validateVout() exits with true" << std::endl; + return true; + } + else { + message = m_customMessage + std::string(" incorrect heir funding address: incorrect pubkey(s)"); + } + } + else { + message = m_customMessage + std::string(" incorrect heir funding address: not a 1of2addr"); + } + + std::cerr << "CCC1of2AddressValidator::validateVout() exits with false: " << message << std::endl; + return false; + } + virtual bool validateVin(CTxIn vin, CTxOut prevVout, std::string& message) const { return false; } + +private: + CScript m_fundingOpretScript; + std::string m_customMessage; +}; + + +/** +* Validates if this is vout to owner or heir from opret (funding or change) +*/ +template class CMyPubkeyVoutValidator : CValidatorBase +{ +public: + CMyPubkeyVoutValidator(CCcontract_info* cp, CScript opRetScript, bool checkNormals) + : m_fundingOpretScript(opRetScript), m_checkNormals(checkNormals), CValidatorBase(cp) { } + + virtual bool isVinValidator() const { return false; } + virtual bool validateVout(CTxOut vout, std::string& message) const + { + //std::cerr << "CMyPubkeyVoutValidator::validateVout() entered" << std::endl; + + uint8_t funcId; + CPubKey ownerPubkey, heirPubkey; + int64_t inactivityTime; + std::string heirName; + uint256 tokenid; + + ///std::cerr << "CMyPubkeyVoutValidator::validateVout() m_opRetScript=" << m_opRetScript.ToString() << std::endl; + // get both pubkeys: + if ((funcId = DecodeHeirOpRet(m_fundingOpretScript, tokenid, ownerPubkey, heirPubkey, inactivityTime, heirName)) == 0) { + message = std::string("invalid opreturn format"); + return false; + } + + CScript ownerScript; + CScript heirScript; + if (m_checkNormals) { + ownerScript = CoinHelper::makeUserVout(vout.nValue, ownerPubkey).scriptPubKey; + heirScript = CoinHelper::makeUserVout(vout.nValue, heirPubkey).scriptPubKey; + } + else { + ownerScript = Helper::makeUserVout(vout.nValue, ownerPubkey).scriptPubKey; + heirScript = Helper::makeUserVout(vout.nValue, heirPubkey).scriptPubKey; + } + + //std::cerr << "CMyPubkeyVoutValidator::validateVout() vout.scriptPubKey=" << vout.scriptPubKey.ToString() << " makeUserVout=" << Helper::makeUserVout(vout.nValue, ownerPubkey).scriptPubKey.ToString() << std::endl; + + // recreate scriptPubKey for owner and heir and compare it with that of the vout to check: + if (vout.scriptPubKey == ownerScript || vout.scriptPubKey == heirScript) { + // this is vout to owner or heir addr: + std::cerr << "CMyPubkeyVoutValidator::validateVout() exits with true" << std::endl; + return true; + + } + + std::cerr << "CMyPubkeyVoutValidator::validateVout() exits with false (not the owner's or heir's addresses)" << std::endl; + return false; + } + virtual bool validateVin(CTxIn vin, CTxOut prevVout, std::string& message) const { return true; } + +private: + CScript m_fundingOpretScript; + //uint256 m_lasttxid; + bool m_checkNormals; +}; + +/** +* Check if the user is the heir and the heir is allowed to spend (duration > inactivityTime) +*/ +template class CHeirSpendValidator : CValidatorBase +{ +public: + CHeirSpendValidator(CCcontract_info* cp, CScript opRetScript, uint256 latesttxid, bool isHeirSpendingBegan) + : m_fundingOpretScript(opRetScript), m_latesttxid(latesttxid), m_isHeirSpendingBegan(isHeirSpendingBegan), CValidatorBase(cp) {} + + virtual bool isVinValidator() const { return false; } + virtual bool validateVout(CTxOut vout, std::string& message) const + { + //std::cerr << "CHeirSpendValidator::validateVout() entered" << std::endl; + + uint8_t funcId; + CPubKey ownerPubkey, heirPubkey; + int64_t inactivityTime; + std::string heirName; + uint256 tokenid; + + + // get heir pubkey: + if ((funcId = DecodeHeirOpRet(m_fundingOpretScript, tokenid, ownerPubkey, heirPubkey, inactivityTime, heirName, false)) == 0) { + message = std::string("invalid opreturn format"); + return false; + } + + int32_t numblocks; + int64_t durationSec = CCduration(numblocks, m_latesttxid); + + // recreate scriptPubKey for heir and compare it with that of the vout: + if (vout.scriptPubKey == Helper::makeClaimerVout(vout.nValue, heirPubkey).scriptPubKey) { + // this is the heir is trying to spend + if (!m_isHeirSpendingBegan && durationSec <= inactivityTime) { + message = "heir is not allowed yet to spend funds"; + std::cerr << "CHeirSpendValidator::validateVout() heir is not allowed yet to spend funds" << std::endl; + return false; + } + else { + // heir is allowed to spend + return true; + } + } + + std::cerr << "CHeirSpendValidator::validateVout() exits with true" << std::endl; + + // this is not heir: + return true; + } + virtual bool validateVin(CTxIn vin, CTxOut prevVout, std::string& message) const { return true; } + +private: + CScript m_fundingOpretScript; + uint256 m_latesttxid; + bool m_isHeirSpendingBegan; +}; + +/** +* Validates this opreturn and compares it with the opreturn from the previous tx +*/ +template class COpRetValidator : CValidatorBase +{ +public: + COpRetValidator(CCcontract_info* cp, CScript opret) + : m_fundingOpretScript(opret), CValidatorBase(cp) {} + + virtual bool isVinValidator() const { return false; } + virtual bool validateVout(CTxOut vout, std::string& message) const + { + //std::cerr << "COpRetValidator::validateVout() entered" << std::endl; + + uint8_t funcId, initialFuncId; // do not check heir name + uint256 fundingTxidInOpret = zeroid, dummyTxid, tokenid, initialTokenid; + + if ((funcId = DecodeHeirOpRet(vout.scriptPubKey, tokenid, fundingTxidInOpret)) == 0) { + message = std::string("invalid opreturn format"); + return false; + } + if ((initialFuncId = DecodeHeirOpRet(m_fundingOpretScript, initialTokenid, dummyTxid)) == 0) { + message = std::string("invalid initial tx opreturn format"); + return false; + } + + // validation rules: + if (!Helper::isMyFuncId(funcId)) { + message = std::string("invalid funcid in opret"); + return false; + } + + if(tokenid != initialTokenid ) { + message = std::string("invalid tokenid in opret"); + return false; + } + + std::cerr << "COpRetValidator::validateVout() exits with true" << std::endl; + return true; + } + virtual bool validateVin(CTxIn vin, CTxOut prevVout, std::string& message) const { return true; } + +private: + CScript m_fundingOpretScript; +}; + + + +#endif \ No newline at end of file diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 94403bcc0..5c32b2f5a 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -406,8 +406,16 @@ static const CRPCCommand vRPCCommands[] = { "faucet", "faucetget", &faucetget, true }, { "faucet", "faucetaddress", &faucetaddress, true }, - // Heir - { "heir", "heiraddress", &heiraddress, true }, + // Heir + { "heir", "heiraddress", &heiraddress, true }, + { "heir", "heirfund", &heirfund, true }, + { "heir", "heiradd", &heiradd, true }, + { "heir", "heirclaim", &heirclaim, true }, + { "heir", "heirfundtokens", &heirfundtokens, true }, + { "heir", "heiraddtokens", &heiraddtokens, true }, + { "heir", "heirclaimtokens", &heirclaimtokens, true }, + { "heir", "heirinfo", &heirinfo, true }, + { "heir", "heirlist", &heirlist, true }, // Channels { "channels", "channelsaddress", &channelsaddress, true }, @@ -479,7 +487,8 @@ static const CRPCCommand vRPCCommands[] = { "dice", "dicestatus", &dicestatus, true }, { "dice", "diceaddress", &diceaddress, true }, - // tokens + // tokens & assets + { "tokens", "assetsaddress", &assetsaddress, true }, { "tokens", "tokeninfo", &tokeninfo, true }, { "tokens", "tokenlist", &tokenlist, true }, { "tokens", "tokenorders", &tokenorders, true }, diff --git a/src/rpc/server.h b/src/rpc/server.h index 920aac970..7c85c43b6 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -242,6 +242,7 @@ extern UniValue tokeninfo(const UniValue& params, bool fHelp); extern UniValue tokenlist(const UniValue& params, bool fHelp); extern UniValue tokenorders(const UniValue& params, bool fHelp); extern UniValue tokenbalance(const UniValue& params, bool fHelp); +extern UniValue assetsaddress(const UniValue& params, bool fHelp); extern UniValue tokenaddress(const UniValue& params, bool fHelp); extern UniValue tokencreate(const UniValue& params, bool fHelp); extern UniValue tokentransfer(const UniValue& params, bool fHelp); @@ -253,6 +254,14 @@ extern UniValue tokencancelask(const UniValue& params, bool fHelp); extern UniValue tokenfillask(const UniValue& params, bool fHelp); extern UniValue tokenconvert(const UniValue& params, bool fHelp); extern UniValue heiraddress(const UniValue& params, bool fHelp); +extern UniValue heirfund(const UniValue& params, bool fHelp); +extern UniValue heiradd(const UniValue& params, bool fHelp); +extern UniValue heirclaim(const UniValue& params, bool fHelp); +extern UniValue heirfundtokens(const UniValue& params, bool fHelp); +extern UniValue heiraddtokens(const UniValue& params, bool fHelp); +extern UniValue heirclaimtokens(const UniValue& params, bool fHelp); +extern UniValue heirinfo(const UniValue& params, bool fHelp); +extern UniValue heirlist(const UniValue& params, bool fHelp); extern UniValue channelsaddress(const UniValue& params, bool fHelp); extern UniValue oraclesaddress(const UniValue& params, bool fHelp); extern UniValue oracleslist(const UniValue& params, bool fHelp); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 0dcaaa848..b39a19668 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -5417,27 +5417,145 @@ UniValue gatewaysaddress(const UniValue& params, bool fHelp) UniValue heiraddress(const UniValue& params, bool fHelp) { - struct CCcontract_info *cp,C; std::vector destPubkey; + /* + struct CCcontract_info *cp,C; std::vector pubkey; + cp = CCinit(&C,EVAL_HEIR); + if ( fHelp || params.size() > 1 ) + throw runtime_error("heiraddress [pubkey]\n"); + if ( ensure_CCrequirements() < 0 ) + throw runtime_error("to use CC contracts, you need to launch daemon with valid -pubkey= for an address in your wallet\n"); + if ( params.size() == 1 ) + pubkey = ParseHex(params[0].get_str().c_str()); + return(CCaddress(cp,(char *)"Heir",pubkey)); + */ - cp = CCinit(&C,EVAL_HEIR); - if ( fHelp || (params.size() != 4 && params.size() != 3)) - throw runtime_error("heiraddress func txid amount [destpubkey]\n"); - if ( ensure_CCrequirements() < 0 ) - throw runtime_error("to use CC contracts, you need to launch daemon with valid -pubkey= for an address in your wallet\n"); - //if ( params.size() == 1 ) - // pubkey = ParseHex(params[0].get_str().c_str()); - char funcid = ((char *)params[0].get_str().c_str())[0]; - uint256 assetid = Parseuint256((char *)params[1].get_str().c_str()); - int64_t funds = atof(params[2].get_str().c_str()) * COIN ; - if(params.size() == 4) - destPubkey = ParseHex(params[3].get_str().c_str()); + // make fake token tx: + struct CCcontract_info *cp, C; - //return HeirFundBad(funcid, assetid, funds, destPubkey); + if (fHelp || (params.size() < 1)) + throw runtime_error("heiraddress A|G|H|T|R assetid|destpubkey amountcoins [heirpubkey] [fundingtxid]\n"); + if (ensure_CCrequirements() < 0) + throw runtime_error("to use CC contracts, you need to launch daemon with valid -pubkey= for an address in your wallet\n"); - return(CCaddress(cp,(char *)"Heir",destPubkey)); + char badKind = ((char *)params[0].get_str().c_str())[0]; + + if (badKind == 'A') { + std::vector destPubkey; + if (params.size() == 2) { + cp = CCinit(&C, EVAL_HEIR); + destPubkey = ParseHex(params[1].get_str().c_str()); + return(CCaddress(cp, (char *)"Heir", destPubkey)); + } + else + return std::string("bad params for A"); + } + + CPubKey myPubkey = pubkey2pk(Mypubkey()); + CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight());; + + if (badKind != 'R') { + + if (badKind == 'G' && params.size() != 3) + return std::string("incorrect params for G"); + if (badKind == 'H' && params.size() != 5) + return std::string("incorrect params for H, = 5"); + if (badKind == 'T' && params.size() != 3) + return std::string("incorrect params for T, = 3"); + + + uint256 assetid = Parseuint256((char *)params[1].get_str().c_str()); + int64_t amount = atof(params[2].get_str().c_str()) * COIN; + + uint256 fundingtxid; + CPubKey heirPubkey; + + if (badKind == 'H') { + + std::vector heirPubkeyStr = ParseHex(params[3].get_str().c_str()); + heirPubkey = pubkey2pk(heirPubkeyStr); + + fundingtxid = Parseuint256((char *)params[4].get_str().c_str()); + } + + + + int64_t txfee = 10000; + + uint8_t evalCodeInOpret = (badKind == 'H') ? EVAL_HEIR : EVAL_GATEWAYS; + cp = CCinit(&C, EVAL_ASSETS); + + int64_t normalInputs = AddNormalinputs(mtx, myPubkey, txfee + amount, 60); + // int64_t ccInputs = 0; + + + if (badKind == 'T') { + // just empty fake token + mtx.vout.push_back(MakeCC1vout(EVAL_ASSETS, amount, myPubkey)); //note you need destPubkey for sending to gateways + } + else if (badKind == 'H') { + // heir add funding tx + mtx.vout.push_back(MakeCC1of2vout(EVAL_ASSETS, amount, myPubkey, heirPubkey)); // add cryptocondition to spend amount for either pk + } + else { // if (badKind == 'G') + CPubKey gatewayContractPubKey = GetUnspendable(cp, 0); + mtx.vout.push_back(CTxOut(txfee, CScript() << ParseHex(HexStr(gatewayContractPubKey)) << OP_CHECKSIG)); + } + + int64_t change = (normalInputs - amount); + if (change != 0) { + mtx.vout.push_back(CTxOut(change, CScript() << ParseHex(HexStr(myPubkey)) << OP_CHECKSIG)); + } + std::cerr << "make fake token for contract=" << badKind << " added normalInputs=" << normalInputs << " change=" << change << std::endl; + + // note: it sets eval=EVAL_ASSETS in opreturn both for tokens and gateways cc addr + //script = EncodeAssetOpRet('t', assetid, zeroid, 0, (badKind == 'A' ? Mypubkey() : destPubkey)); // dimxy: are we sure about destPubkey here? it may be just pubkey of the author... + CScript opret; + assetid = revuint256(assetid); + fundingtxid = revuint256(fundingtxid); + if (badKind == 'T') + opret << OP_RETURN << E_MARSHAL(ss << (uint8_t)evalCodeInOpret << (uint8_t)'t' << assetid ); + else if (badKind == 'H') + opret << OP_RETURN << E_MARSHAL(ss << (uint8_t)evalCodeInOpret << (uint8_t)'t' << assetid << (uint8_t)'A' << myPubkey << heirPubkey << (int64_t)120 << fundingtxid); + else + opret << OP_RETURN << E_MARSHAL(ss << (uint8_t)evalCodeInOpret << (uint8_t)'t' << assetid); // EVAL_GATEWAYS + + return(FinalizeCCTx(0, cp, mtx, myPubkey, txfee, opret)); // normal change added here + } + else + { + // move vout from user cc addr to gateways unspendable + CTransaction srctx; + + uint256 hashBlock; + const bool allowSlow = false; + uint256 srctxid = Parseuint256((char *)params[1].get_str().c_str()); + + struct CCcontract_info *cp, C; + cp = CCinit(&C, EVAL_GATEWAYS); + + uint64_t txfee = 10000; + CPubKey gatewayspk = GetUnspendable(cp, 0); + + if (myGetTransaction(srctxid, srctx, hashBlock) && srctx.vout.size() > 0) { + + int64_t normalInputs = AddNormalinputs(mtx, myPubkey, 2 * txfee, 4); + + mtx.vin.push_back(CTxIn(srctxid, 2, CScript())); // repaired src tx + mtx.vout.push_back(MakeCC1vout(EVAL_GATEWAYS, srctx.vout[2].nValue, gatewayspk)); + + CScript script = srctx.vout[srctx.vout.size() - 1].scriptPubKey; + + //mtx.fOverwintered = true; + + return(FinalizeCCTx(0, cp, mtx, myPubkey, txfee, script)); // normal change added here + } + } + return std::string("there has been some error"); } + + UniValue lottoaddress(const UniValue& params, bool fHelp) { struct CCcontract_info *cp,C; std::vector pubkey; @@ -5518,17 +5636,30 @@ UniValue rewardsaddress(const UniValue& params, bool fHelp) return(CCaddress(cp,(char *)"Rewards",pubkey)); } +UniValue assetsaddress(const UniValue& params, bool fHelp) +{ + struct CCcontract_info *cp, C; std::vector pubkey; + cp = CCinit(&C, EVAL_ASSETS); + if (fHelp || params.size() > 1) + throw runtime_error("assetsaddress [pubkey]\n"); + if (ensure_CCrequirements() < 0) + throw runtime_error("to use CC contracts, you need to launch daemon with valid -pubkey= for an address in your wallet\n"); + if (params.size() == 1) + pubkey = ParseHex(params[0].get_str().c_str()); + return(CCaddress(cp, (char *)"Assets", pubkey)); +} + UniValue tokenaddress(const UniValue& params, bool fHelp) { struct CCcontract_info *cp,C; std::vector pubkey; - cp = CCinit(&C,EVAL_ASSETS); + cp = CCinit(&C,EVAL_TOKENS); if ( fHelp || params.size() > 1 ) throw runtime_error("tokenaddress [pubkey]\n"); if ( ensure_CCrequirements() < 0 ) throw runtime_error("to use CC contracts, you need to launch daemon with valid -pubkey= for an address in your wallet\n"); if ( params.size() == 1 ) pubkey = ParseHex(params[0].get_str().c_str()); - return(CCaddress(cp,(char *)"Assets",pubkey)); + return(CCaddress(cp,(char *)"Tokens", pubkey)); } UniValue marmara_poolpayout(const UniValue& params, bool fHelp) @@ -6895,7 +7026,7 @@ UniValue tokencreate(const UniValue& params, bool fHelp) return(result); } } - hex = CreateAsset(0,supply,name,description); + hex = CreateToken(0,supply,name,description); if ( hex.size() > 0 ) { result.push_back(Pair("result", "success")); @@ -6927,7 +7058,7 @@ UniValue tokentransfer(const UniValue& params, bool fHelp) ERR_RESULT("amount must be positive"); return(result); } - hex = AssetTransfer(0,tokenid,pubkey,amount); + hex = TokenTransfer(0,tokenid,pubkey,amount); if (amount > 0) { if ( hex.size() > 0 ) { @@ -6964,7 +7095,11 @@ UniValue tokenconvert(const UniValue& params, bool fHelp) ERR_RESULT("amount must be positive"); return(result); } - hex = AssetConvert(0,tokenid,pubkey,amount,evalcode); + + ERR_RESULT("deprecated"); + return(result); + +/* hex = AssetConvert(0,tokenid,pubkey,amount,evalcode); if (amount > 0) { if ( hex.size() > 0 ) { @@ -6974,7 +7109,7 @@ UniValue tokenconvert(const UniValue& params, bool fHelp) } else { ERR_RESULT("amount must be positive"); } - return(result); + return(result); */ } UniValue tokenbid(const UniValue& params, bool fHelp) @@ -7271,6 +7406,270 @@ UniValue getbalance64(const UniValue& params, bool fHelp) return ret; } +// heir contract functions for coins +UniValue heirfund(const UniValue& params, bool fHelp) +{ + UniValue result(UniValue::VOBJ); + //uint256 txid; + int64_t txfee; + int64_t amount; + int64_t inactivitytime; + std::string hex; + std::vector pubkey; + std::string name; + + //TODO: do we need this? + if (!EnsureWalletIsAvailable(fHelp)) + return NullUniValue; + + if (fHelp || params.size() != 5) + throw runtime_error("heirfund fee funds heirname heirpubkey inactivitytime\n"); + if (ensure_CCrequirements() < 0) + throw runtime_error("to use CC contracts, you need to launch daemon with valid -pubkey= for an address in your wallet\n"); + + const CKeyStore& keystore = *pwalletMain; + LOCK2(cs_main, pwalletMain->cs_wallet); + + txfee = atof((char*)params[0].get_str().c_str()) * COIN; + amount = atof((char*)params[1].get_str().c_str()) * COIN; + name = params[2].get_str(); + pubkey = ParseHex(params[3].get_str().c_str()); + inactivitytime = atof((char*)params[4].get_str().c_str()); + + + hex = HeirFundCoinCaller(txfee, amount, name, pubkey2pk(pubkey), inactivitytime, zeroid); + if (hex.size() > 0) { + result.push_back(Pair("result", "success")); + result.push_back(Pair("hex", hex)); + } + else + ERR_RESULT("couldn't create heir fund"); + + return result; +} + + +UniValue heiradd(const UniValue& params, bool fHelp) +{ + UniValue result; // UniValue result(UniValue::VOBJ); + uint256 fundingtxid; + uint64_t txfee; + int64_t amount; + int64_t inactivitytime; + std::string hex; + std::vector pubkey; + std::string name; + + // TODO: do we need this? + if (!EnsureWalletIsAvailable(fHelp)) + return NullUniValue; + + if (fHelp || params.size() != 3) + throw runtime_error("heiradd fee funds fundingtxid\n"); + if (ensure_CCrequirements() < 0) + throw runtime_error("to use CC contracts, you need to launch daemon with valid -pubkey= for an address in your wallet\n"); + + const CKeyStore& keystore = *pwalletMain; + LOCK2(cs_main, pwalletMain->cs_wallet); + + txfee = atof((char*)params[0].get_str().c_str()) * COIN; + amount = atof((char*)params[1].get_str().c_str()) * COIN; + fundingtxid = Parseuint256((char*)params[2].get_str().c_str()); + + result = HeirAddCoinCaller(fundingtxid, txfee, amount); + /* if ( hex.size() > 0 ) + { + result.push_back(Pair("result", "success")); + result.push_back(Pair("hex", hex)); + } else ERR_RESULT("couldn't claim heir fund"); */ + + + return result; +} + +UniValue heirclaim(const UniValue& params, bool fHelp) +{ + UniValue result; // result(UniValue::VOBJ); + uint256 fundingtxid; + uint64_t txfee; + int64_t amount; + int64_t inactivitytime; + std::string hex; + std::vector pubkey; + std::string name; + + // do we need this? + if (!EnsureWalletIsAvailable(fHelp)) + return NullUniValue; + + if (fHelp || params.size() != 3) + throw runtime_error("heirclaim fee funds fundingtxid\n"); + if (ensure_CCrequirements() < 0) + throw runtime_error("to use CC contracts, you need to launch daemon with valid -pubkey= for an address in your wallet\n"); + + const CKeyStore& keystore = *pwalletMain; + LOCK2(cs_main, pwalletMain->cs_wallet); + + txfee = atof((char*)params[0].get_str().c_str()) * COIN; + amount = atof((char*)params[1].get_str().c_str()) * COIN; + fundingtxid = Parseuint256((char*)params[2].get_str().c_str()); + + result = HeirClaimCoinCaller(fundingtxid, txfee, amount); + /* if ( hex.size() > 0 ) + { + result.push_back(Pair("result", "success")); + result.push_back(Pair("hex", hex)); + } else ERR_RESULT("couldn't claim heir fund"); */ + + return result; +} + +// same heir contract functions for tokens +UniValue heirfundtokens(const UniValue& params, bool fHelp) +{ + UniValue result(UniValue::VOBJ); + uint256 assetid; + uint64_t txfee; + int64_t amount; + int64_t inactivitytime; + std::string hex; + std::vector pubkey; + std::string name; + + //TODO: do we need this (dimxy)? + if (!EnsureWalletIsAvailable(fHelp)) + return NullUniValue; + + if (fHelp || params.size() != 6) + throw runtime_error("heirfundtokens fee funds heirname heirpubkey inactivitytime assetid\n"); + if (ensure_CCrequirements() < 0) + throw runtime_error("to use CC contracts, you need to launch daemon with valid -pubkey= for an address in your wallet\n"); + + const CKeyStore& keystore = *pwalletMain; + LOCK2(cs_main, pwalletMain->cs_wallet); + + txfee = atoll((char*)params[0].get_str().c_str()); + amount = atoll((char*)params[1].get_str().c_str()); + name = params[2].get_str(); + pubkey = ParseHex(params[3].get_str().c_str()); + inactivitytime = atof((char*)params[4].get_str().c_str()); + assetid = Parseuint256((char*)params[5].get_str().c_str()); + + + hex = HeirFundTokenCaller(txfee, amount, name, pubkey2pk(pubkey), inactivitytime, assetid); + if (hex.size() > 0) { + result.push_back(Pair("result", "success")); + result.push_back(Pair("hex", hex)); + } + else + ERR_RESULT("couldn't create heir fund"); + + + return result; +} + + +UniValue heiraddtokens(const UniValue& params, bool fHelp) +{ + UniValue result; // UniValue result(UniValue::VOBJ); + uint256 fundingtxid; + uint64_t txfee; + int64_t amount; + int64_t inactivitytime; + std::string hex; + std::vector pubkey; + std::string name; + + // TODO: do we need this? + if (!EnsureWalletIsAvailable(fHelp)) + return NullUniValue; + + if (fHelp || params.size() != 3) + throw runtime_error("heiraddtokens fee funds fundingtxid\n"); + if (ensure_CCrequirements() < 0) + throw runtime_error("to use CC contracts, you need to launch daemon with valid -pubkey= for an address in your wallet\n"); + + const CKeyStore& keystore = *pwalletMain; + LOCK2(cs_main, pwalletMain->cs_wallet); + + txfee = atoll((char*)params[0].get_str().c_str()); + amount = atoll((char*)params[1].get_str().c_str()); + fundingtxid = Parseuint256((char*)params[2].get_str().c_str()); + + result = HeirAddTokenCaller(fundingtxid, txfee, amount); + /* if ( hex.size() > 0 ) + { + result.push_back(Pair("result", "success")); + result.push_back(Pair("hex", hex)); + } else ERR_RESULT("couldn't claim heir fund"); */ + + + return result; +} + +UniValue heirclaimtokens(const UniValue& params, bool fHelp) +{ + UniValue result; // result(UniValue::VOBJ); + uint256 fundingtxid; + int64_t txfee; + int64_t amount; + int64_t inactivitytime; + std::string hex; + std::vector pubkey; + std::string name; + + // do we need this? + if (!EnsureWalletIsAvailable(fHelp)) + return NullUniValue; + + if (fHelp || params.size() != 3) + throw runtime_error("heirclaimtokens fee funds fundingtxid\n"); + if (ensure_CCrequirements() < 0) + throw runtime_error("to use CC contracts, you need to launch daemon with valid -pubkey= for an address in your wallet\n"); + + const CKeyStore& keystore = *pwalletMain; + LOCK2(cs_main, pwalletMain->cs_wallet); + + txfee = atoll((char*)params[0].get_str().c_str()); + amount = atoll((char*)params[1].get_str().c_str()); + fundingtxid = Parseuint256((char*)params[2].get_str().c_str()); + + result = HeirClaimTokenCaller(fundingtxid, txfee, amount); + /* if ( hex.size() > 0 ) + { + result.push_back(Pair("result", "success")); + result.push_back(Pair("hex", hex)); + } else ERR_RESULT("couldn't claim heir fund"); */ + + return result; +} + +UniValue heirinfo(const UniValue& params, bool fHelp) +{ + uint256 fundingtxid; + if (fHelp || params.size() != 1) // or 0? + throw runtime_error("heirinfo fundingtxid\n"); + // if ( ensure_CCrequirements() < 0 ) + // throw runtime_error("to use CC contracts, you need to launch daemon with valid -pubkey= for an address in your wallet\n"); + + fundingtxid = Parseuint256((char*)params[0].get_str().c_str()); + return (HeirInfo(fundingtxid)); +} + +UniValue heirlist(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() != 0) // or 0? + throw runtime_error("heirlist\n"); + + // if ( ensure_CCrequirements() < 0 ) + // throw runtime_error("to use CC contracts, you need to launch daemon with valid -pubkey= for an address in your wallet\n"); + + return (HeirList()); +} + + + + extern UniValue dumpprivkey(const UniValue& params, bool fHelp); // in rpcdump.cpp extern UniValue importprivkey(const UniValue& params, bool fHelp); extern UniValue importaddress(const UniValue& params, bool fHelp);