Hush Full Node software. We were censored from Github, this is where all development happens now.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1068 lines
51 KiB

/******************************************************************************
* 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<uint8_t> origpubkey, std::string name, std::string description, std::vector<uint8_t> vopretNonfungible )
{
CScript opret;
uint8_t evalcode = EVAL_TOKENS;
funcid = 'c'; // override the param
opret << OP_RETURN << E_MARSHAL(ss << evalcode << funcid << origpubkey << name << description; \
if (!vopretNonfungible.empty()) { \
ss << (uint8_t)OPRETID_NONFUNGIBLEDATA; \
ss << vopretNonfungible; \
});
return(opret);
}
CScript EncodeTokenOpRet(uint256 tokenid, std::vector<CPubKey> voutPubkeys, CScript payload)
{
CScript opret;
uint8_t tokenFuncId = 't';
uint8_t evalCodeInOpret = EVAL_TOKENS;
tokenid = revuint256(tokenid);
uint8_t ccType = 0;
if (voutPubkeys.size() >= 0 && voutPubkeys.size() <= 2)
ccType = voutPubkeys.size();
else {
LOGSTREAM((char *)"cctokens", CCLOG_DEBUG2, stream << "EncodeTokenOpRet voutPubkeys.size()=" << voutPubkeys.size() << " not supported" << std::endl);
}
std::vector<uint8_t> vpayload;
GetOpReturnData(payload, vpayload);
opret << OP_RETURN << E_MARSHAL(ss << evalCodeInOpret << tokenFuncId << tokenid << ccType; \
if (ccType >= 1) ss << voutPubkeys[0]; \
if (ccType == 2) ss << voutPubkeys[1]; \
if (vpayload.size() > 0) ss << vpayload);
// bad opret cases (retries to attach payload without re-serialization):
// "error 64: scriptpubkey":
// if (payload.size() > 0)
// opret += payload;
// error 64: scriptpubkey:
// CScript opretPayloadNoOpcode(vpayload);
// return opret + opretPayloadNoOpcode;
// error sig_aborted:
// opret.resize(opret.size() + vpayload.size());
// CScript::iterator it = opret.begin() + opret.size();
// for (int i = 0; i < vpayload.size(); i++, it++)
// *it = vpayload[i];
return opret;
}
// overload for compatibility
CScript EncodeTokenOpRet(uint8_t tokenFuncId, uint8_t evalCodeInOpret, uint256 tokenid, std::vector<CPubKey> voutPubkeys, CScript payload)
{
return EncodeTokenOpRet(tokenid, voutPubkeys, payload);
}
// overload for fungible:
uint8_t DecodeTokenCreateOpRet(const CScript &scriptPubKey, std::vector<uint8_t> &origpubkey, std::string &name, std::string &description) {
std::vector<uint8_t> vopretNonfungibleDummy;
return DecodeTokenCreateOpRet(scriptPubKey, origpubkey, name, description, vopretNonfungibleDummy);
}
uint8_t DecodeTokenCreateOpRet(const CScript &scriptPubKey,std::vector<uint8_t> &origpubkey,std::string &name,std::string &description, std::vector<uint8_t> &vopretNonfungible)
{
std::vector<uint8_t> vopret; uint8_t dummyEvalcode, funcid, *script, fieldId = 0;
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; \
// we suppose in 'c' opret it might be only non-fungible payload and not any assets/heir/etc payloads
if (!ss.eof()) { \
ss >> fieldId; \
if( fieldId == OPRETID_NONFUNGIBLEDATA ) \
ss >> vopretNonfungible; \
}))
return(funcid);
}
return (uint8_t)0;
}
// overload for compatibility allows only usual fungible tokens:
// warning: it makes vopret marshalling to CScript because this is what caller would expect
/*uint8_t DecodeTokenOpRet(const CScript scriptPubKey, uint8_t &evalCode, uint256 &tokenid, std::vector<CPubKey> &voutPubkeys, std::vector<uint8_t> &vopretExtra) {
std::vector<uint8_t> vopret1, vopret2;
uint8_t funcId = DecodeTokenOpRet(scriptPubKey, evalCode, tokenid, voutPubkeys, vopret1, vopret2);
CScript opretExtra;
vopretExtra.clear();
// make marshalling for compatibility
// callers of this func expect length of full array at the beginning (and they will make 'vopretStripped' from vopretExtra)
if (vopret2.empty())
opretExtra << OP_RETURN << E_MARSHAL(ss << vopret1); // if first opret (or no oprets)
else
opretExtra << OP_RETURN << E_MARSHAL(ss << vopret2); // if both oprets present, return assets/heir/gateways/... opret (dump non-fungible opret)
GetOpReturnData(opretExtra, vopretExtra);
return funcId;
} */
// decodes token opret:
// for 't' returns all data from opret, vopretExtra contains other contract's data (currently only assets').
// for 'c' returns only funcid. NOTE: nonfungible data is not returned
uint8_t DecodeTokenOpRet(const CScript scriptPubKey, uint8_t &evalCodeTokens, uint256 &tokenid, std::vector<CPubKey> &voutPubkeys, std::vector<uint8_t> &vopretExtra)
{
std::vector<uint8_t> vopret, extra, dummyPubkey, vnonfungibleDummy;
uint8_t funcId=0, *script, dummyEvalCode, dummyFuncId, ccType, fieldId = 0;
std::string dummyName; std::string dummyDescription;
CPubKey voutPubkey1, voutPubkey2;
GetOpReturnData(scriptPubKey, vopret);
script = (uint8_t *)vopret.data();
tokenid = zeroid;
vopretExtra.clear();
if (script != NULL && vopret.size() > 2)
{
// 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 isEof = true;
evalCodeTokens = script[0];
if (evalCodeTokens != EVAL_TOKENS)
return (uint8_t)0;
funcId = script[1];
LOGSTREAM((char *)"cctokens", CCLOG_DEBUG2, stream << "DecodeTokenOpRet decoded funcId=" << (char)(funcId?funcId:' ') << std::endl);
switch( funcId )
{
case 'c':
return DecodeTokenCreateOpRet(scriptPubKey, dummyPubkey, dummyName, dummyDescription, vnonfungibleDummy);
//break;
case 't':
//not used yet: case 'l':
// NOTE: 'E_UNMARSHAL result==false' means 'parse error' OR 'not eof state'. Consequently, 'result==false' but 'isEof==true' means just 'parse error'
if (E_UNMARSHAL(vopret, ss >> dummyEvalCode; ss >> dummyFuncId; ss >> tokenid; ss >> ccType; \
if (ccType >= 1) ss >> voutPubkey1; \
if (ccType == 2) ss >> voutPubkey2; \
isEof = ss.eof(); \
if (!isEof) ss >> vopretExtra; \
// if something else remains -> bad format
isEof = ss.eof()) || !isEof)
{
if (!(ccType >= 0 && ccType <= 2)) { //incorrect ccType
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << "DecodeTokenOpRet() incorrect ccType=" << (int)ccType << " tokenid=" << revuint256(tokenid).GetHex() << std::endl);
return (uint8_t)0;
}
// add verification pubkeys:
voutPubkeys.clear();
if (voutPubkey1.IsValid())
voutPubkeys.push_back(voutPubkey1);
if (voutPubkey2.IsValid())
voutPubkeys.push_back(voutPubkey2);
tokenid = revuint256(tokenid);
return(funcId);
}
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << "DecodeTokenOpRet() bad opret format, isEof=" << isEof << " ccType=" << ccType << " tokenid=" << revuint256(tokenid).GetHex() << std::endl);
return (uint8_t)0;
default:
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << "DecodeTokenOpRet() illegal funcid=" << (int)funcId << std::endl);
return (uint8_t)0;
}
}
Squashed commit of the following: commit 843168eddff34390f19fa661ac6315e1a51aafe2 Author: dimxy <dimxy@komodoplatform.com> Date: Wed Jan 9 14:34:03 2019 +0500 logging improved for CCtokens.cpp commit 43c5b3a5c4e36e76a235f987e07fe893d941d95e Author: dimxy <dimxy@komodoplatform.com> Date: Wed Jan 9 14:23:41 2019 +0500 more extra logging disabled for heir and token commit a3f7ca963a0042237f4c97d6d89852646b541c6c Author: dimxy <dimxy@komodoplatform.com> Date: Wed Jan 9 14:13:58 2019 +0500 more extra logging disabled commit f4dec8f8ad733c5a61dd0fc9f3decd103dd7a0c3 Author: dimxy <dimxy@komodoplatform.com> Date: Wed Jan 9 14:05:45 2019 +0500 more extra logging disabled commit eb9a41472004ccdacdacfc12b79880ac6da390ee Author: dimxy <dimxy@komodoplatform.com> Date: Wed Jan 9 13:50:11 2019 +0500 extra logging disabled in heir and tokens commit 0fe01c8a7b52b03e0c0a3ba5d77f916862031d23 Author: dimxy <dimxy@komodoplatform.com> Date: Wed Jan 9 13:15:08 2019 +0500 heir normal input validator changed to CNullValidator commit 3b86e87c83b6993d5a3b4c056c10120223e8c9ed Author: dimxy <dimxy@komodoplatform.com> Date: Wed Jan 9 12:58:03 2019 +0500 enabled logiing in FinalizeCCtx commit 2ea87c1b3c7967b316d22e2e65fc3c0203cc4b03 Author: dimxy <dimxy@komodoplatform.com> Date: Wed Jan 9 12:37:43 2019 +0500 corrected parameters for tests commit 216eb4ed84c6fd23e3fc22cd79509a09c8504c0c Author: dimxy <dimxy@komodoplatform.com> Date: Wed Jan 9 12:22:30 2019 +0500 changed to eval_assets for test commit 2af850f06a4692200864a316208cdfc0709b1634 Author: dimxy <dimxy@komodoplatform.com> Date: Wed Jan 9 02:42:43 2019 +0500 heiradd loggin vin commit 28e033d2b2a1a01b2bc9c2d51a81b61ae53f3a29 Author: dimxy <dimxy@komodoplatform.com> Date: Wed Jan 9 02:04:33 2019 +0500 logging added commit ef1dbc504c5984a4997b5e06e736fa7b5b4e125d Author: dimxy <dimxy@komodoplatform.com> Date: Wed Jan 9 01:29:46 2019 +0500 logging added and corrected commit 98c1e5a6896a97667c9d64f347c26b36c6a320a9 Author: dimxy <dimxy@komodoplatform.com> Date: Wed Jan 9 01:05:17 2019 +0500 corrected isHeirSpendingBegan init in _UnmarshalOpret commit 6006f073bfc136ed06c1636b3e6ffa7b403ce1d9 Author: dimxy <dimxy@komodoplatform.com> Date: Wed Jan 9 01:01:18 2019 +0500 corrected dummyIsHeirSpendingBegan using commit 9a95edf4e311a80538dd7b13aeeb201e97474ee8 Author: dimxy <dimxy@komodoplatform.com> Date: Wed Jan 9 00:53:50 2019 +0500 isHeirSpendingBegan is put in opret commit 4bd210709626e2f8b99889e50410eb0f75b3dbda Author: dimxy <dimxy@komodoplatform.com> Date: Tue Jan 8 20:16:20 2019 +0500 added logging to _FindLastextFundingTx for print C vouts commit 3603b49c1143b1efe82dd545b9d7dca43f5bcec0 Author: dimxy <dimxy@komodoplatform.com> Date: Tue Jan 8 19:25:11 2019 +0500 corrected isHeirSpendingBegun calc commit e7d6923d4be080a5e98405634fafb175fa55f801 Author: dimxy <dimxy@komodoplatform.com> Date: Tue Jan 8 18:43:35 2019 +0500 yet another cpToken correction in RunValidators commit e425dcc4497652a062b45d4a5ce4c137bd2caa3d Author: dimxy <dimxy@komodoplatform.com> Date: Tue Jan 8 18:22:08 2019 +0500 changed cp param to cpTokens for heir tokens in validation commit a4420f30d42d93dbc2729db4a5b2895d4b16d22b Author: dimxy <dimxy@komodoplatform.com> Date: Tue Jan 8 17:11:10 2019 +0500 corrected EVAL_HEIR in create opret commit 788d75263fd5aaa7b66417441622c9bb6603ab81 Author: dimxy <dimxy@komodoplatform.com> Date: Tue Jan 8 16:41:07 2019 +0500 corrected opret forming for 'F', tokens commit 92bf6c53b36a5a4450b5c7079383792d634d61a7 Author: dimxy <dimxy@komodoplatform.com> Date: Tue Jan 8 16:12:09 2019 +0500 _UnmarshalOpret corrected for non F commit e4eb26e875d44f3e4c1d45aff6df8a7026b3329a Author: dimxy <dimxy@komodoplatform.com> Date: Tue Jan 8 15:26:34 2019 +0500 Revert "CCduration debug logging added" This reverts commit 5ffc2b0a8b49d387cd175754738d2c07701c7856. commit ce97ac88af0b87b304ad1fc9cb29db0803d0bd42 Author: dimxy <dimxy@komodoplatform.com> Date: Tue Jan 8 15:17:10 2019 +0500 changed pindex to pindexTip in CCduration for better reading commit 5ffc2b0a8b49d387cd175754738d2c07701c7856 Author: dimxy <dimxy@komodoplatform.com> Date: Tue Jan 8 14:43:07 2019 +0500 CCduration debug logging added commit 34afdd16f0f6b5b97da864f082dcd019a7566da7 Author: dimxy <dimxy@komodoplatform.com> Date: Tue Jan 8 12:23:31 2019 +0500 corrected again order to DecodeOpRet call in heir commit bba6149969ea23aa4e3c6263a36bae0ba7fa6f5d Author: dimxy <dimxy@komodoplatform.com> Date: Tue Jan 8 12:17:11 2019 +0500 logging added in IsHeirFundingVout commit 6c5d4b313ef3e54cb5eb2bf67b2fa78d9ffd0b04 Author: dimxy <dimxy@komodoplatform.com> Date: Tue Jan 8 12:02:15 2019 +0500 logging added to Add1of2AddrInputs commit 40d6d84971a8ed764523567f08df4c74fda4f12d Author: dimxy <dimxy@komodoplatform.com> Date: Tue Jan 8 11:36:23 2019 +0500 corrected vopretExtra getting for heir for coins commit 36061d25fb8992551915afc3dd0600878a64c540 Author: dimxy <dimxy@komodoplatform.com> Date: Tue Jan 8 11:13:30 2019 +0500 corrected CCinit C-param separate commit 0a6710433868c315afe3f656c1bc5f5138fe5288 Author: dimxy <dimxy@komodoplatform.com> Date: Tue Jan 8 10:58:19 2019 +0500 logging added in decodeheiropret commit 2503d8cb59b94b024a9ded43a0602f89800a9549 Author: dimxy <dimxy@komodoplatform.com> Date: Tue Jan 8 10:39:21 2019 +0500 corrected decode opret for heir for coins
6 years ago
else {
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << "DecodeTokenOpRet() empty opret, could not parse" << std::endl);
Squashed commit of the following: commit 843168eddff34390f19fa661ac6315e1a51aafe2 Author: dimxy <dimxy@komodoplatform.com> Date: Wed Jan 9 14:34:03 2019 +0500 logging improved for CCtokens.cpp commit 43c5b3a5c4e36e76a235f987e07fe893d941d95e Author: dimxy <dimxy@komodoplatform.com> Date: Wed Jan 9 14:23:41 2019 +0500 more extra logging disabled for heir and token commit a3f7ca963a0042237f4c97d6d89852646b541c6c Author: dimxy <dimxy@komodoplatform.com> Date: Wed Jan 9 14:13:58 2019 +0500 more extra logging disabled commit f4dec8f8ad733c5a61dd0fc9f3decd103dd7a0c3 Author: dimxy <dimxy@komodoplatform.com> Date: Wed Jan 9 14:05:45 2019 +0500 more extra logging disabled commit eb9a41472004ccdacdacfc12b79880ac6da390ee Author: dimxy <dimxy@komodoplatform.com> Date: Wed Jan 9 13:50:11 2019 +0500 extra logging disabled in heir and tokens commit 0fe01c8a7b52b03e0c0a3ba5d77f916862031d23 Author: dimxy <dimxy@komodoplatform.com> Date: Wed Jan 9 13:15:08 2019 +0500 heir normal input validator changed to CNullValidator commit 3b86e87c83b6993d5a3b4c056c10120223e8c9ed Author: dimxy <dimxy@komodoplatform.com> Date: Wed Jan 9 12:58:03 2019 +0500 enabled logiing in FinalizeCCtx commit 2ea87c1b3c7967b316d22e2e65fc3c0203cc4b03 Author: dimxy <dimxy@komodoplatform.com> Date: Wed Jan 9 12:37:43 2019 +0500 corrected parameters for tests commit 216eb4ed84c6fd23e3fc22cd79509a09c8504c0c Author: dimxy <dimxy@komodoplatform.com> Date: Wed Jan 9 12:22:30 2019 +0500 changed to eval_assets for test commit 2af850f06a4692200864a316208cdfc0709b1634 Author: dimxy <dimxy@komodoplatform.com> Date: Wed Jan 9 02:42:43 2019 +0500 heiradd loggin vin commit 28e033d2b2a1a01b2bc9c2d51a81b61ae53f3a29 Author: dimxy <dimxy@komodoplatform.com> Date: Wed Jan 9 02:04:33 2019 +0500 logging added commit ef1dbc504c5984a4997b5e06e736fa7b5b4e125d Author: dimxy <dimxy@komodoplatform.com> Date: Wed Jan 9 01:29:46 2019 +0500 logging added and corrected commit 98c1e5a6896a97667c9d64f347c26b36c6a320a9 Author: dimxy <dimxy@komodoplatform.com> Date: Wed Jan 9 01:05:17 2019 +0500 corrected isHeirSpendingBegan init in _UnmarshalOpret commit 6006f073bfc136ed06c1636b3e6ffa7b403ce1d9 Author: dimxy <dimxy@komodoplatform.com> Date: Wed Jan 9 01:01:18 2019 +0500 corrected dummyIsHeirSpendingBegan using commit 9a95edf4e311a80538dd7b13aeeb201e97474ee8 Author: dimxy <dimxy@komodoplatform.com> Date: Wed Jan 9 00:53:50 2019 +0500 isHeirSpendingBegan is put in opret commit 4bd210709626e2f8b99889e50410eb0f75b3dbda Author: dimxy <dimxy@komodoplatform.com> Date: Tue Jan 8 20:16:20 2019 +0500 added logging to _FindLastextFundingTx for print C vouts commit 3603b49c1143b1efe82dd545b9d7dca43f5bcec0 Author: dimxy <dimxy@komodoplatform.com> Date: Tue Jan 8 19:25:11 2019 +0500 corrected isHeirSpendingBegun calc commit e7d6923d4be080a5e98405634fafb175fa55f801 Author: dimxy <dimxy@komodoplatform.com> Date: Tue Jan 8 18:43:35 2019 +0500 yet another cpToken correction in RunValidators commit e425dcc4497652a062b45d4a5ce4c137bd2caa3d Author: dimxy <dimxy@komodoplatform.com> Date: Tue Jan 8 18:22:08 2019 +0500 changed cp param to cpTokens for heir tokens in validation commit a4420f30d42d93dbc2729db4a5b2895d4b16d22b Author: dimxy <dimxy@komodoplatform.com> Date: Tue Jan 8 17:11:10 2019 +0500 corrected EVAL_HEIR in create opret commit 788d75263fd5aaa7b66417441622c9bb6603ab81 Author: dimxy <dimxy@komodoplatform.com> Date: Tue Jan 8 16:41:07 2019 +0500 corrected opret forming for 'F', tokens commit 92bf6c53b36a5a4450b5c7079383792d634d61a7 Author: dimxy <dimxy@komodoplatform.com> Date: Tue Jan 8 16:12:09 2019 +0500 _UnmarshalOpret corrected for non F commit e4eb26e875d44f3e4c1d45aff6df8a7026b3329a Author: dimxy <dimxy@komodoplatform.com> Date: Tue Jan 8 15:26:34 2019 +0500 Revert "CCduration debug logging added" This reverts commit 5ffc2b0a8b49d387cd175754738d2c07701c7856. commit ce97ac88af0b87b304ad1fc9cb29db0803d0bd42 Author: dimxy <dimxy@komodoplatform.com> Date: Tue Jan 8 15:17:10 2019 +0500 changed pindex to pindexTip in CCduration for better reading commit 5ffc2b0a8b49d387cd175754738d2c07701c7856 Author: dimxy <dimxy@komodoplatform.com> Date: Tue Jan 8 14:43:07 2019 +0500 CCduration debug logging added commit 34afdd16f0f6b5b97da864f082dcd019a7566da7 Author: dimxy <dimxy@komodoplatform.com> Date: Tue Jan 8 12:23:31 2019 +0500 corrected again order to DecodeOpRet call in heir commit bba6149969ea23aa4e3c6263a36bae0ba7fa6f5d Author: dimxy <dimxy@komodoplatform.com> Date: Tue Jan 8 12:17:11 2019 +0500 logging added in IsHeirFundingVout commit 6c5d4b313ef3e54cb5eb2bf67b2fa78d9ffd0b04 Author: dimxy <dimxy@komodoplatform.com> Date: Tue Jan 8 12:02:15 2019 +0500 logging added to Add1of2AddrInputs commit 40d6d84971a8ed764523567f08df4c74fda4f12d Author: dimxy <dimxy@komodoplatform.com> Date: Tue Jan 8 11:36:23 2019 +0500 corrected vopretExtra getting for heir for coins commit 36061d25fb8992551915afc3dd0600878a64c540 Author: dimxy <dimxy@komodoplatform.com> Date: Tue Jan 8 11:13:30 2019 +0500 corrected CCinit C-param separate commit 0a6710433868c315afe3f656c1bc5f5138fe5288 Author: dimxy <dimxy@komodoplatform.com> Date: Tue Jan 8 10:58:19 2019 +0500 logging added in decodeheiropret commit 2503d8cb59b94b024a9ded43a0602f89800a9549 Author: dimxy <dimxy@komodoplatform.com> Date: Tue Jan 8 10:39:21 2019 +0500 corrected decode opret for heir for coins
6 years ago
}
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<uint8_t> vopretExtra, tmporigpubkey, ignorepubkey;
uint8_t funcid, evalCodeInOpret;
char destaddr[64], origaddr[64], CCaddr[64];
std::vector<CPubKey> voutTokenPubkeys, vinTokenPubkeys;
if (strcmp(ASSETCHAINS_SYMBOL, "ROGUE") == 0 && chainActive.Height() <= 12500)
return true;
numvins = tx.vin.size();
numvouts = tx.vout.size();
outputs = inputs = 0;
preventCCvins = preventCCvouts = -1;
// check boundaries:
if (numvouts < 1)
return eval->Invalid("no vouts");
if ((funcid = DecodeTokenOpRet(tx.vout[numvouts - 1].scriptPubKey, evalCodeInOpret, tokenid, voutTokenPubkeys, vopretExtra)) == 0)
return eval->Invalid("TokenValidate: invalid opreturn payload");
LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << "TokensValidate funcId=" << (char)(funcid?funcid:' ') << " evalcode=" << std::hex << (int)cp->evalcode << std::endl);
5 years ago
if (eval->GetTxUnconfirmed(tokenid, createTx, hashBlock) == 0)
return eval->Invalid("cant find token create txid");
//else if (IsCCInput(tx.vin[0].scriptSig) != 0)
// return eval->Invalid("illegal token vin0"); // <-- this validation was removed because some token tx might not have normal vins
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");
}
}
// validate spending from token cc addr: allowed only for burned non-fungible tokens:
if (ExtractTokensVinPubkeys(tx, vinTokenPubkeys) && std::find(vinTokenPubkeys.begin(), vinTokenPubkeys.end(), GetUnspendable(cp, NULL)) != vinTokenPubkeys.end()) {
// validate spending from token unspendable cc addr:
int64_t burnedAmount = HasBurnedTokensvouts(cp, eval, tx, tokenid);
if (burnedAmount > 0) {
std::vector<uint8_t> vopretNonfungible;
GetNonfungibleData(tokenid, vopretNonfungible);
if( vopretNonfungible.empty() )
return eval->Invalid("spending cc marker not supported for fungible tokens");
}
}
switch (funcid)
{
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' <tokenname> <description>
//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 <other evalcode> 't' tokenid <other contract payload>
if (inputs == 0)
return eval->Invalid("no token inputs for transfer");
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << "token transfer preliminarily validated inputs=" << inputs << "->outputs=" << outputs << " preventCCvins=" << preventCCvins<< " preventCCvouts=" << preventCCvouts << std::endl);
break; // breaking to other contract validation...
default:
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << "illegal tokens funcid=" << (char)(funcid?funcid:' ') << std::endl);
return eval->Invalid("unexpected token funcid");
}
// forward validation if evalcode in opret is not EVAL_TOKENS
// init for forwarding validation call
//if (evalCodeInOpret != EVAL_TOKENS) { // TODO: should we check also only allowed for tokens evalcodes, like EVAL_ASSETS, EVAL_GATEWAYS?
// struct CCcontract_info *cpOther = NULL, C;
// cpOther = CCinit(&C, evalCodeInOpret);
// if (cpOther)
// return cpOther->validate(cpOther, eval, tx, nIn);
// else
// return eval->Invalid("unsupported evalcode in opret");
//}
return true;
// what does this do?
// return(PreventCC(eval,tx,preventCCvins,numvins,preventCCvouts,numvouts));
}
// helper funcs:
// extract cc token vins' pubkeys:
bool ExtractTokensVinPubkeys(CTransaction tx, std::vector<CPubKey> &vinPubkeys) {
bool found = false;
CPubKey pubkey;
struct CCcontract_info *cpTokens, tokensC;
cpTokens = CCinit(&tokensC, EVAL_TOKENS);
for (int32_t i = 0; i < tx.vin.size(); i++)
{
// check for cc token vins:
if( (*cpTokens->ismyvin)(tx.vin[i].scriptSig) )
{
auto findEval = [](CC *cond, struct CCVisitor _) {
bool r = false;
if (cc_typeId(cond) == CC_Secp256k1) {
*(CPubKey*)_.context = buf2pk(cond->publicKey);
//std::cerr << "findEval found pubkey=" << HexStr(*(CPubKey*)_.context) << std::endl;
r = true;
}
// false for a match, true for continue
return r ? 0 : 1;
};
CC *cond = GetCryptoCondition(tx.vin[i].scriptSig);
if (cond) {
CCVisitor visitor = { findEval, (uint8_t*)"", 0, &pubkey };
bool out = !cc_visit(cond, visitor);
cc_free(cond);
if (pubkey.IsValid()) {
vinPubkeys.push_back(pubkey);
found = true;
}
}
}
}
return found;
}
// this is just for log messages indentation fur debugging recursive calls:
thread_local uint32_t tokenValIndentSize = 0;
// validates opret for token tx:
uint8_t ValidateTokenOpret(CTransaction tx, uint256 tokenid) {
uint256 tokenidOpret = zeroid;
uint8_t funcid;
uint8_t dummyEvalCode;
std::vector<CPubKey> voutPubkeysDummy;
std::vector<uint8_t> vopretExtraDummy;
// this is just for log messages indentation fur debugging recursive calls:
std::string indentStr = std::string().append(tokenValIndentSize, '.');
if (tx.vout.size() == 0)
return (uint8_t)0;
if ((funcid = DecodeTokenOpRet(tx.vout.back().scriptPubKey, dummyEvalCode, tokenidOpret, voutPubkeysDummy, vopretExtraDummy)) == 0)
{
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << indentStr << "ValidateTokenOpret() DecodeTokenOpret could not parse opret for txid=" << tx.GetHash().GetHex() << std::endl);
return (uint8_t)0;
}
else if (funcid == 'c')
{
if (tokenid != zeroid && tokenid == tx.GetHash()) {
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_DEBUG1, stream << indentStr << "ValidateTokenOpret() this is the tokenbase 'c' tx, txid=" << tx.GetHash().GetHex() << " returning true" << std::endl);
return funcid;
}
else {
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_DEBUG1, stream << indentStr << "ValidateTokenOpret() not my tokenbase txid=" << tx.GetHash().GetHex() << std::endl);
}
}
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) {
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_DEBUG1, stream << indentStr << "ValidateTokenOpret() this is a transfer 't' tx, txid=" << tx.GetHash().GetHex() << " returning true" << std::endl);
return funcid;
}
else {
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_DEBUG1, stream << indentStr << "ValidateTokenOpret() not my tokenid=" << tokenidOpret.GetHex() << std::endl);
}
}
else {
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_DEBUG1, stream << indentStr << "ValidateTokenOpret() not supported funcid=" << (char)funcid << " tokenIdOpret=" << tokenidOpret.GetHex() << " txid=" << tx.GetHash().GetHex() << std::endl);
}
return (uint8_t)0;
}
// remove token->unspendablePk (it is only for marker usage)
std::vector<CPubKey> FilterOutTokensUnspendablePk(std::vector<CPubKey> sourcePubkeys) {
struct CCcontract_info *cpTokens, tokensC;
cpTokens = CCinit(&tokensC, EVAL_TOKENS);
CPubKey tokensUnspendablePk = GetUnspendable(cpTokens, NULL);
std::vector<CPubKey> destPubkeys;
for (auto pk : sourcePubkeys)
if (pk != tokensUnspendablePk)
destPubkeys.push_back(pk);
return destPubkeys;
}
// Checks if the vout is a really Tokens CC vout
// also checks tokenid in opret or txid if this is 'c' tx
// goDeeper is true: the func also validates amounts of the passed transaction:
// it should be either sum(cc vins) == sum(cc vouts) or the transaction is the 'tokenbase' ('c') tx
// checkPubkeys is true: validates if the vout is token vout1 or token vout1of2. Should always be true!
int64_t IsTokensvout(bool goDeeper, bool checkPubkeys /*<--not used, always true*/, struct CCcontract_info *cp, Eval* eval, 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, '.');
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_DEBUG2, stream << indentStr << "IsTokensvout() entered for txid=" << tx.GetHash().GetHex() << " v=" << v << " for tokenid=" << reftokenid.GetHex() << std::endl);
int32_t n = tx.vout.size();
// just check boundaries:
if (n == 0 || v < 0 || v >= n-1) {
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << indentStr << "isTokensvout() incorrect params: (n == 0 or v < 0 or v >= n-1)" << " v=" << v << " n=" << n << " returning 0" << std::endl);
return(0);
}
if (tx.vout[v].scriptPubKey.IsPayToCryptoCondition())
{
if (goDeeper) {
//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)
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << 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;
}
}
}
// token opret most important checks (tokenid == reftokenid, tokenid is non-zero, tx is 'tokenbase'):
const uint8_t funcId = ValidateTokenOpret(tx, reftokenid);
//std::cerr << indentStr << "IsTokensvout() ValidateTokenOpret returned=" << (char)(funcId?funcId:' ') << " for txid=" << tx.GetHash().GetHex() << " for tokenid=" << reftokenid.GetHex() << std::endl;
if (funcId != 0) {
LOGSTREAM((char *)"cctokens", CCLOG_DEBUG2, stream << indentStr << "IsTokensvout() ValidateTokenOpret returned not-null funcId=" << (char)(funcId ? funcId : ' ') << " for txid=" << tx.GetHash().GetHex() << " for tokenid=" << reftokenid.GetHex() << std::endl);
uint8_t dummyEvalCode;
uint256 tokenIdOpret;
std::vector<CPubKey> voutPubkeys, voutPubkeysInOpret;
std::vector<uint8_t> vopretExtra, vopretNonfungible;
uint8_t evalCode = EVAL_TOKENS; // if both payloads are empty maybe it is a transfer to non-payload-one-eval-token vout like GatewaysClaim
uint8_t evalCode2 = 0; // will be checked if zero or not
// test vouts for possible token use-cases:
std::vector<std::pair<CTxOut, std::string>> testVouts;
DecodeTokenOpRet(tx.vout.back().scriptPubKey, dummyEvalCode, tokenIdOpret, voutPubkeysInOpret, vopretExtra);
LOGSTREAM((char *)"cctokens", CCLOG_DEBUG2, stream << "IsTokensvout() vopretExtra=" << HexStr(vopretExtra) << std::endl);
GetNonfungibleData(reftokenid, vopretNonfungible);
voutPubkeys = FilterOutTokensUnspendablePk(voutPubkeysInOpret); // cannot send tokens to token unspendable cc addr (only marker is allowed there)
// NOTE: evalcode order in vouts is important:
// non-fungible-eval -> EVAL_TOKENS -> assets-eval
if (vopretNonfungible.size() > 0)
evalCode = vopretNonfungible.begin()[0];
if (vopretExtra.size() > 0)
evalCode2 = vopretExtra.begin()[0];
if (evalCode == EVAL_TOKENS && evalCode2 != 0) {
evalCode = evalCode2;
evalCode2 = 0;
}
if( /*checkPubkeys &&*/ funcId != 'c' ) { // for 'c' there is no pubkeys
// verify that the vout is token by constructing vouts with the pubkeys in the opret:
// maybe this is dual-eval 1 pubkey or 1of2 pubkey vout?
if (voutPubkeys.size() >= 1 && voutPubkeys.size() <= 2) {
// check dual/three-eval 1 pubkey vout with the first pubkey
testVouts.push_back( std::make_pair(MakeTokensCC1vout(evalCode, evalCode2, tx.vout[v].nValue, voutPubkeys[0]), std::string("three-eval cc1 pk[0]")) );
if (evalCode2 != 0)
// also check in backward evalcode order
testVouts.push_back( std::make_pair(MakeTokensCC1vout(evalCode2, evalCode, tx.vout[v].nValue, voutPubkeys[0]), std::string("three-eval cc1 pk[0] backward-eval")) );
if(voutPubkeys.size() == 2) {
// check dual/three eval 1of2 pubkeys vout
testVouts.push_back( std::make_pair(MakeTokensCC1of2vout(evalCode, evalCode2, tx.vout[v].nValue, voutPubkeys[0], voutPubkeys[1]), std::string("three-eval cc1of2")) );
// check dual/three eval 1 pubkey vout with the second pubkey
testVouts.push_back(std::make_pair(MakeTokensCC1vout(evalCode, evalCode2, tx.vout[v].nValue, voutPubkeys[1]), std::string("three-eval cc1 pk[1]")));
if (evalCode2 != 0) {
// also check in backward evalcode order:
// check dual/three eval 1of2 pubkeys vout
testVouts.push_back(std::make_pair(MakeTokensCC1of2vout(evalCode2, evalCode, tx.vout[v].nValue, voutPubkeys[0], voutPubkeys[1]), std::string("three-eval cc1of2 backward-eval")));
// check dual/three eval 1 pubkey vout with the second pubkey
testVouts.push_back(std::make_pair(MakeTokensCC1vout(evalCode2, evalCode, tx.vout[v].nValue, voutPubkeys[1]), std::string("three-eval cc1 pk[1] backward-eval")));
}
}
// maybe this is like gatewayclaim to single-eval token?
testVouts.push_back(std::make_pair(MakeCC1vout(EVAL_TOKENS, tx.vout[v].nValue, voutPubkeys[0]), std::string("single-eval cc1 pk[0]")));
// maybe this is like FillSell for non-fungible token?
if( evalCode != 0 )
testVouts.push_back(std::make_pair(MakeTokensCC1vout(evalCode, tx.vout[v].nValue, voutPubkeys[0]), std::string("dual-eval-token cc1 pk[0]")));
if( evalCode2 != 0 )
testVouts.push_back(std::make_pair(MakeTokensCC1vout(evalCode2, tx.vout[v].nValue, voutPubkeys[0]), std::string("dual-eval2-token cc1 pk[0]")));
if (voutPubkeys.size() == 2) {
// the same for pk[1]:
testVouts.push_back(std::make_pair(MakeCC1vout(EVAL_TOKENS, tx.vout[v].nValue, voutPubkeys[1]), std::string("single-eval cc1 pk[1]")));
if (evalCode != 0)
testVouts.push_back(std::make_pair(MakeTokensCC1vout(evalCode, tx.vout[v].nValue, voutPubkeys[1]), std::string("dual-eval-token cc1 pk[1]")));
if (evalCode2 != 0)
testVouts.push_back(std::make_pair(MakeTokensCC1vout(evalCode2, tx.vout[v].nValue, voutPubkeys[1]), std::string("dual-eval2-token cc1 pk[1]")));
}
}
// maybe it is single-eval or dual/three-eval token change?
std::vector<CPubKey> vinPubkeys, vinPubkeysUnfiltered;
ExtractTokensVinPubkeys(tx, vinPubkeysUnfiltered);
vinPubkeys = FilterOutTokensUnspendablePk(vinPubkeysUnfiltered); // cannot send tokens to token unspendable cc addr (only marker is allowed there)
for(std::vector<CPubKey>::iterator it = vinPubkeys.begin(); it != vinPubkeys.end(); it++) {
testVouts.push_back(std::make_pair(MakeCC1vout(EVAL_TOKENS, tx.vout[v].nValue, *it), std::string("single-eval cc1 self vin pk")));
testVouts.push_back(std::make_pair(MakeTokensCC1vout(evalCode, evalCode2, tx.vout[v].nValue, *it), std::string("three-eval cc1 self vin pk")));
if (evalCode2 != 0)
// also check in backward evalcode order:
testVouts.push_back(std::make_pair(MakeTokensCC1vout(evalCode2, evalCode, tx.vout[v].nValue, *it), std::string("three-eval cc1 self vin pk backward-eval")));
}
}
else {
CPubKey origPubkey;
std::vector<uint8_t> vorigPubkey;
std::string dummyName, dummyDescription;
std::vector<uint8_t> vopret1;
if (DecodeTokenCreateOpRet(tx.vout.back().scriptPubKey, vorigPubkey, dummyName, dummyDescription, vopret1) == 0) {
LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << indentStr << "IsTokensvout() could not decode create opret" << " for txid=" << tx.GetHash().GetHex() << " for tokenid=" << reftokenid.GetHex() << std::endl);
return 0;
}
origPubkey = pubkey2pk(vorigPubkey);
// for 'c' recognize the tokens only to token originator pubkey (but not to unspendable <-- closed sec violation)
// maybe this is like gatewayclaim to single-eval token?
testVouts.push_back(std::make_pair(MakeCC1vout(EVAL_TOKENS, tx.vout[v].nValue, origPubkey), std::string("single-eval cc1 orig-pk")));
// maybe this is like FillSell for non-fungible token?
if (evalCode != 0)
testVouts.push_back(std::make_pair(MakeTokensCC1vout(evalCode, tx.vout[v].nValue, origPubkey), std::string("dual-eval-token cc1 orig-pk")));
}
// try all test vouts:
for (auto t : testVouts) {
if (t.first == tx.vout[v]) {
LOGSTREAM((char *)"cctokens", CCLOG_DEBUG1, stream << indentStr << "IsTokensvout() valid amount=" << tx.vout[v].nValue << " msg=" << t.second << " evalCode=" << (int)evalCode << " evalCode2=" << (int)evalCode2 << " txid=" << tx.GetHash().GetHex() << " tokenid=" << reftokenid.GetHex() << std::endl);
return tx.vout[v].nValue;
}
}
LOGSTREAM((char *)"cctokens", CCLOG_DEBUG1, stream << indentStr << "IsTokensvout() no valid vouts evalCode=" << (int)evalCode << " evalCode2=" << (int)evalCode2 << " for txid=" << tx.GetHash().GetHex() << " for tokenid=" << reftokenid.GetHex() << std::endl);
}
//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 goDeeper, struct CCcontract_info *cp, int64_t &inputs, int64_t &outputs, Eval* eval, const CTransaction &tx, uint256 reftokenid)
{
CTransaction vinTx;
uint256 hashBlock;
int64_t tokenoshis;
struct CCcontract_info *cpTokens, tokensC;
cpTokens = CCinit(&tokensC, EVAL_TOKENS);
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, '.');
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_DEBUG2, stream << indentStr << "TokensExactAmounts() entered for txid=" << tx.GetHash().GetHex() << " for tokenid=" << reftokenid.GetHex() << std::endl);
for (int32_t i = 0; i<numvins; i++)
{ // check for additional contracts which may send tokens to the Tokens contract
if ((*cpTokens->ismyvin)(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)))
{
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << 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 {
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_DEBUG2, stream << indentStr << "TokenExactAmounts() checking vintx.vout for tx.vin[" << i << "] nValue=" << vinTx.vout[tx.vin[i].prevout.n].nValue << std::endl);
// validate vouts of vintx
tokenValIndentSize++;
tokenoshis = IsTokensvout(goDeeper, true, cpTokens, eval, vinTx, tx.vin[i].prevout.n, reftokenid);
tokenValIndentSize--;
if (tokenoshis != 0)
{
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_DEBUG1, stream << indentStr << "TokensExactAmounts() adding vintx.vout for tx.vin[" << i << "] tokenoshis=" << tokenoshis << std::endl);
inputs += tokenoshis;
}
}
}
}
for (int32_t i = 0; i < numvouts-1; i ++) // 'numvouts-1' <-- do not check opret
{
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_DEBUG2, stream << indentStr << "TokenExactAmounts() recursively checking tx.vout[" << i << "] nValue=" << tx.vout[i].nValue << std::endl);
// Note: we pass in here IsTokenvout(false,...) because we don't need to call TokenExactAmounts() recursively from IsTokensvout here
// indeed, if we pass 'true' we'll be checking this tx vout again
tokenValIndentSize++;
tokenoshis = IsTokensvout(false /*<--do not recursion here*/, true /*<--exclude non-tokens vouts*/, cpTokens, eval, tx, i, reftokenid);
tokenValIndentSize--;
if (tokenoshis != 0)
{
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_DEBUG1, stream << indentStr << "TokensExactAmounts() adding tx.vout[" << i << "] tokenoshis=" << tokenoshis << std::endl);
outputs += tokenoshis;
}
}
//std::cerr << indentStr << "TokensExactAmounts() inputs=" << inputs << " outputs=" << outputs << " for txid=" << tx.GetHash().GetHex() << std::endl;
if (inputs != outputs) {
if (tx.GetHash() != reftokenid)
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_DEBUG1, stream << indentStr << "TokenExactAmounts() found unequal token cc inputs=" << inputs << " vs cc outputs=" << outputs << " for txid=" << tx.GetHash().GetHex() << " and this is not the create tx" << std::endl);
5 years ago
//fprintf(stderr,"inputs %llu vs outputs %llu\n",(long long)inputs,(long long)outputs);
return false; // do not call eval->Invalid() here!
}
else
return true;
}
// get non-fungible data from 'tokenbase' tx (the data might be empty)
void GetNonfungibleData(uint256 tokenid, std::vector<uint8_t> &vopretNonfungible)
{
CTransaction tokenbasetx;
uint256 hashBlock;
if (!myGetTransaction(tokenid, tokenbasetx, hashBlock)) {
LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << "GetNonfungibleData() cound not load token creation tx=" << tokenid.GetHex() << std::endl);
return;
}
vopretNonfungible.clear();
// check if it is non-fungible tx and get its second evalcode from non-fungible payload
if (tokenbasetx.vout.size() > 0) {
std::vector<uint8_t> origpubkey;
std::string name, description;
std::vector<uint8_t> vopretExtra;
if (DecodeTokenCreateOpRet(tokenbasetx.vout.back().scriptPubKey, origpubkey, name, description, vopretExtra) == 'c')
vopretNonfungible = vopretExtra;
}
}
// overload, adds inputs from token cc addr
int64_t AddTokenCCInputs(struct CCcontract_info *cp, CMutableTransaction &mtx, CPubKey pk, uint256 tokenid, int64_t total, int32_t maxinputs) {
std::vector<uint8_t> vopretNonfungibleDummy;
return AddTokenCCInputs(cp, mtx, pk, tokenid, total, maxinputs, vopretNonfungibleDummy);
}
// adds inputs from token cc addr and returns non-fungible opret payload if present
// also sets evalcode in cp, if needed
int64_t AddTokenCCInputs(struct CCcontract_info *cp, CMutableTransaction &mtx, CPubKey pk, uint256 tokenid, int64_t total, int32_t maxinputs, std::vector<uint8_t> &vopretNonfungible)
{
char tokenaddr[64], destaddr[64];
int64_t threshold, nValue, price, totalinputs = 0;
int32_t n = 0;
std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> > unspentOutputs;
GetNonfungibleData(tokenid, vopretNonfungible);
if (vopretNonfungible.size() > 0)
cp->additionalTokensEvalcode2 = vopretNonfungible.begin()[0];
GetTokensCCaddress(cp, tokenaddr, pk);
SetCCunspents(unspentOutputs, tokenaddr);
if (unspentOutputs.empty()) {
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << "AddTokenCCInputs() no utxos for token dual/three eval addr=" << tokenaddr << " evalcode=" << (int)cp->evalcode << " additionalTokensEvalcode2=" << (int)cp->additionalTokensEvalcode2 << std::endl);
}
threshold = total / (maxinputs != 0 ? maxinputs : 64); // TODO: maxinputs really could not be over 64? what if i want to calc total balance for all available uxtos?
// maybe it is better to add all uxtos if maxinputs == 0
for (std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> >::const_iterator it = unspentOutputs.begin(); it != unspentOutputs.end(); it++)
{
CTransaction vintx;
uint256 hashBlock;
uint256 vintxid = it->first.txhash;
int32_t vout = (int32_t)it->first.index;
if (it->second.satoshis < threshold) // this should work also for non-fungible tokens (there should be only 1 satoshi for non-fungible token issue)
continue;
int32_t ivin;
for (ivin = 0; ivin < mtx.vin.size(); ivin ++)
if (vintxid == mtx.vin[ivin].prevout.hash && vout == mtx.vin[ivin].prevout.n)
break;
if (ivin != mtx.vin.size()) // that is, the tx.vout is already added to mtx.vin (in some previous calls)
continue;
if (GetTransaction(vintxid, vintx, hashBlock, false) != 0)
{
Getscriptaddress(destaddr, vintx.vout[vout].scriptPubKey);
if (strcmp(destaddr, tokenaddr) != 0 &&
strcmp(destaddr, cp->unspendableCCaddr) != 0 && // TODO: check why this. Should not we add token inputs from unspendable cc addr if mypubkey is used?
strcmp(destaddr, cp->unspendableaddr2) != 0) // or the logic is to allow to spend all available tokens (what about unspendableaddr3)?
continue;
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_DEBUG1, stream << "AddTokenCCInputs() check vintx vout destaddress=" << destaddr << " amount=" << vintx.vout[vout].nValue << std::endl);
if ((nValue = IsTokensvout(true, true/*<--add only valid token uxtos */, cp, NULL, vintx, vout, tokenid)) > 0 && myIsutxo_spentinmempool(ignoretxid,ignorevin,vintxid, vout) == 0)
{
//for non-fungible tokens check payload:
if (!vopretNonfungible.empty()) {
std::vector<uint8_t> vopret;
// check if it is non-fungible token:
GetNonfungibleData(tokenid, vopret);
if (vopret != vopretNonfungible) {
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << "AddTokenCCInputs() found incorrect non-fungible opret payload for vintxid=" << vintxid.GetHex() << std::endl);
continue;
}
// non-fungible evalCode2 cc contract should also check if there exists only one non-fungible vout with amount = 1
}
if (total != 0 && maxinputs != 0) // if it is not just to calc amount...
mtx.vin.push_back(CTxIn(vintxid, vout, CScript()));
nValue = it->second.satoshis;
totalinputs += nValue;
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_DEBUG1, stream << "AddTokenCCInputs() adding input nValue=" << nValue << std::endl);
n++;
if ((total > 0 && totalinputs >= total) || (maxinputs > 0 && n >= maxinputs))
break;
}
}
}
//std::cerr << "AddTokenCCInputs() found totalinputs=" << totalinputs << std::endl;
return(totalinputs);
}
// checks if any token vouts are sent to 'dead' pubkey
int64_t HasBurnedTokensvouts(struct CCcontract_info *cp, Eval* eval, const CTransaction& tx, uint256 reftokenid)
{
uint8_t dummyEvalCode;
uint256 tokenIdOpret;
std::vector<CPubKey> voutPubkeys, voutPubkeysDummy;
std::vector<uint8_t> vopretExtra, vopretNonfungible;
uint8_t evalCode = EVAL_TOKENS; // if both payloads are empty maybe it is a transfer to non-payload-one-eval-token vout like GatewaysClaim
uint8_t evalCode2 = 0; // will be checked if zero or not
// test vouts for possible token use-cases:
std::vector<std::pair<CTxOut, std::string>> testVouts;
int32_t n = tx.vout.size();
// just check boundaries:
if (n == 0) {
LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << "HasBurnedTokensvouts() incorrect params: tx.vout.size() == 0, txid=" << tx.GetHash().GetHex() << std::endl);
return(0);
}
if (DecodeTokenOpRet(tx.vout.back().scriptPubKey, dummyEvalCode, tokenIdOpret, voutPubkeysDummy, vopretExtra) == 0) {
LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << "HasBurnedTokensvouts() cannot parse opret DecodeTokenOpRet returned 0, txid=" << tx.GetHash().GetHex() << std::endl);
return 0;
}
LOGSTREAM((char *)"cctokens", CCLOG_DEBUG2, stream << "HasBurnedTokensvouts() vopretExtra=" << HexStr(vopretExtra) << std::endl);
GetNonfungibleData(reftokenid, vopretNonfungible);
if (vopretNonfungible.size() > 0)
evalCode = vopretNonfungible.begin()[0];
if (vopretExtra.size() > 0)
evalCode2 = vopretExtra.begin()[0];
if (evalCode == EVAL_TOKENS && evalCode2 != 0) {
evalCode = evalCode2;
evalCode2 = 0;
}
voutPubkeys.push_back(pubkey2pk(ParseHex(CC_BURNPUBKEY)));
int64_t burnedAmount = 0;
for (int i = 0; i < tx.vout.size(); i++) {
if (tx.vout[i].scriptPubKey.IsPayToCryptoCondition())
{
// make all possible token vouts for dead pk:
for (std::vector<CPubKey>::iterator it = voutPubkeys.begin(); it != voutPubkeys.end(); it++) {
testVouts.push_back(std::make_pair(MakeCC1vout(EVAL_TOKENS, tx.vout[i].nValue, *it), std::string("single-eval cc1 burn pk")));
testVouts.push_back(std::make_pair(MakeTokensCC1vout(evalCode, evalCode2, tx.vout[i].nValue, *it), std::string("three-eval cc1 burn pk")));
if (evalCode2 != 0)
// also check in backward evalcode order:
testVouts.push_back(std::make_pair(MakeTokensCC1vout(evalCode2, evalCode, tx.vout[i].nValue, *it), std::string("three-eval cc1 burn pk backward-eval")));
}
// try all test vouts:
for (auto t : testVouts) {
if (t.first == tx.vout[i]) {
LOGSTREAM((char *)"cctokens", CCLOG_DEBUG1, stream << "HasBurnedTokensvouts() burned amount=" << tx.vout[i].nValue << " msg=" << t.second << " evalCode=" << (int)evalCode << " evalCode2=" << (int)evalCode2 << " txid=" << tx.GetHash().GetHex() << " tokenid=" << reftokenid.GetHex() << std::endl);
burnedAmount += tx.vout[i].nValue;
}
}
LOGSTREAM((char *)"cctokens", CCLOG_DEBUG2, stream << "HasBurnedTokensvouts() total burned=" << burnedAmount << " evalCode=" << (int)evalCode << " evalCode2=" << (int)evalCode2 << " for txid=" << tx.GetHash().GetHex() << " for tokenid=" << reftokenid.GetHex() << std::endl);
}
}
return burnedAmount;
}
std::string CreateToken(int64_t txfee, int64_t tokensupply, std::string name, std::string description, std::vector<uint8_t> nonfungibleData)
{
CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight());
CPubKey mypk; struct CCcontract_info *cp, C;
if (tokensupply < 0)
{
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << "CreateToken() negative tokensupply=" << tokensupply << std::endl);
return std::string("");
}
if (!nonfungibleData.empty() && tokensupply != 1) {
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << "CreateToken() for non-fungible tokens tokensupply should be equal to 1" << std::endl);
CCerror = "for non-fungible tokens tokensupply should be equal to 1";
return std::string("");
}
cp = CCinit(&C, EVAL_TOKENS);
if (name.size() > 32 || description.size() > 4096) // this is also checked on rpc level
{
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << "name of=" << name.size() << " or description of=" << description.size() << " is too big" << std::endl);
CCerror = "name should be < 32, description should be < 4096";
return("");
}
if (txfee == 0)
txfee = 10000;
mypk = pubkey2pk(Mypubkey());
if (AddNormalinputs(mtx, mypk, tokensupply + 2 * txfee, 64) > 0)
{
uint8_t destEvalCode = EVAL_TOKENS;
if( nonfungibleData.size() > 0 )
destEvalCode = nonfungibleData.begin()[0];
mtx.vout.push_back(MakeTokensCC1vout(destEvalCode, tokensupply, mypk));
//mtx.vout.push_back(CTxOut(txfee, CScript() << ParseHex(cp->CChexstr) << OP_CHECKSIG)); // old marker (non-burnable because spending could not be validated)
// NOTE: we should prevent spending fake-tokens from this marker in IsTokenvout():
mtx.vout.push_back(MakeCC1vout(EVAL_TOKENS, txfee, GetUnspendable(cp, NULL))); // new marker to token cc addr, burnable and validated, this vout must be=1
return(FinalizeCCTx(0, cp, mtx, mypk, txfee, EncodeTokenCreateOpRet('c', Mypubkey(), name, description, nonfungibleData)));
}
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << "cant find normal inputs" << std::endl);
CCerror = "cant find normal inputs";
return std::string("");
}
// transfer tokens to another pubkey
// param additionalEvalCode allows transfer of dual-eval non-fungible tokens
std::string TokenTransfer(int64_t txfee, uint256 tokenid, std::vector<uint8_t> 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<uint8_t> vopretNonfungible;
if (total < 0) {
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << "negative total=" << total << std::endl);
return("");
}
cp = CCinit(&C, EVAL_TOKENS);
if (txfee == 0)
txfee = 10000;
mypk = pubkey2pk(Mypubkey());
if (AddNormalinputs(mtx, mypk, txfee, 3) > 0)
{
mask = ~((1LL << mtx.vin.size()) - 1); // seems, mask is not used anymore
if ((inputs = AddTokenCCInputs(cp, mtx, mypk, tokenid, total, 60, vopretNonfungible)) > 0) // NOTE: AddTokenCCInputs might set cp->additionalEvalCode which is used in FinalizeCCtx!
{
if (inputs < total) { //added dimxy
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << "TokenTransfer(): insufficient token funds" << std::endl);
CCerror = strprintf("insufficient token inputs");
return std::string("");
}
uint8_t destEvalCode = EVAL_TOKENS;
if (vopretNonfungible.size() > 0)
destEvalCode = vopretNonfungible.begin()[0];
if (inputs > total)
CCchange = (inputs - total);
mtx.vout.push_back(MakeTokensCC1vout(destEvalCode, total, pubkey2pk(destpubkey))); // if destEvalCode == EVAL_TOKENS then it is actually MakeCC1vout(EVAL_TOKENS,...)
if (CCchange != 0)
mtx.vout.push_back(MakeTokensCC1vout(destEvalCode, CCchange, mypk));
std::vector<CPubKey> voutTokenPubkeys;
voutTokenPubkeys.push_back(pubkey2pk(destpubkey)); // dest pubkey for validating vout
return(FinalizeCCTx(mask, cp, mtx, mypk, txfee, EncodeTokenOpRet(tokenid, voutTokenPubkeys, CScript())));
}
else {
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << "not enough CC token inputs for amount=" << total << std::endl);
CCerror = strprintf("no token inputs");
}
//} else fprintf(stderr,"numoutputs.%d != numamounts.%d\n",n,(int32_t)amounts.size());
}
else {
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << "not enough normal inputs for txfee" << std::endl);
CCerror = strprintf("insufficient normal inputs");
}
return("");
}
int64_t GetTokenBalance(CPubKey pk, uint256 tokenid)
{
uint256 hashBlock;
CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight());
CTransaction tokentx;
// CCerror = strprintf("obsolete, cannot return correct value without eval");
// return 0;
if (GetTransaction(tokenid, tokentx, hashBlock, false) == 0)
{
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << "cant find tokenid" << std::endl);
CCerror = strprintf("cant find tokenid");
return 0;
}
struct CCcontract_info *cp, C;
cp = CCinit(&C, EVAL_TOKENS);
return(AddTokenCCInputs(cp, mtx, pk, tokenid, 0, 0));
}
UniValue TokenInfo(uint256 tokenid)
{
UniValue result(UniValue::VOBJ);
uint256 hashBlock;
CTransaction vintx;
std::vector<uint8_t> origpubkey;
std::vector<uint8_t> vopretNonfungible;
std::string name, description;
if (GetTransaction(tokenid, vintx, hashBlock, false) == 0)
{
fprintf(stderr, "TokenInfo() cant find tokenid\n");
result.push_back(Pair("result", "error"));
result.push_back(Pair("error", "cant find tokenid"));
return(result);
}
if (vintx.vout.size() > 0 && DecodeTokenCreateOpRet(vintx.vout[vintx.vout.size() - 1].scriptPubKey, origpubkey, name, description, vopretNonfungible) == 0)
{
5 years ago
LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << "TokenInfo() passed tokenid isnt token creation txid" << std::endl);
result.push_back(Pair("result", "error"));
result.push_back(Pair("error", "tokenid isnt token creation txid"));
}
result.push_back(Pair("result", "success"));
result.push_back(Pair("tokenid", tokenid.GetHex()));
result.push_back(Pair("owner", HexStr(origpubkey)));
result.push_back(Pair("name", name));
result.push_back(Pair("supply", vintx.vout[0].nValue));
result.push_back(Pair("description", description));
if( !vopretNonfungible.empty() )
result.push_back(Pair("data", HexStr(vopretNonfungible)));
return(result);
}
UniValue TokenList()
{
UniValue result(UniValue::VARR);
std::vector<std::pair<CAddressIndexKey, CAmount> > addressIndex;
std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> > addressIndexCCMarker;
struct CCcontract_info *cp, C; uint256 txid, hashBlock;
CTransaction vintx; std::vector<uint8_t> origpubkey;
std::string name, description;
cp = CCinit(&C, EVAL_TOKENS);
auto addTokenId = [&](uint256 txid) {
if (GetTransaction(txid, vintx, hashBlock, false) != 0) {
if (vintx.vout.size() > 0 && DecodeTokenCreateOpRet(vintx.vout[vintx.vout.size() - 1].scriptPubKey, origpubkey, name, description) != 0) {
result.push_back(txid.GetHex());
}
}
};
SetCCtxids(addressIndex, cp->normaladdr); // find by old normal addr marker
for (std::vector<std::pair<CAddressIndexKey, CAmount> >::const_iterator it = addressIndex.begin(); it != addressIndex.end(); it++) {
addTokenId(it->first.txhash);
}
SetCCunspents(addressIndexCCMarker, cp->unspendableCCaddr); // find by burnable validated cc addr marker
for (std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> >::const_iterator it = addressIndexCCMarker.begin(); it != addressIndexCCMarker.end(); it++) {
addTokenId(it->first.txhash);
}
return(result);
6 years ago
}