Browse Source

CCtxidaddr enforcement

pull/4/head
jl777 6 years ago
parent
commit
612f676a5e
  1. 1
      src/cc/CCinclude.h
  2. 10
      src/cc/CCutils.cpp
  3. 62
      src/cc/gateways.cpp
  4. 36
      src/cc/oracles.cpp

1
src/cc/CCinclude.h

@ -101,6 +101,7 @@ int32_t iguana_rwnum(int32_t rwflag,uint8_t *serialized,int32_t len,void *endian
int32_t iguana_rwbignum(int32_t rwflag,uint8_t *serialized,int32_t len,uint8_t *endianedp);
CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys);
int64_t CCaddress_balance(char *coinaddr);
CPubKey CCtxidaddr(char *txidaddr,uint256 txid);
int64_t OraclePrice(int32_t height,uint256 reforacletxid,char *markeraddr,char *format);
uint8_t DecodeOraclesCreateOpRet(const CScript &scriptPubKey,std::string &name,std::string &description,std::string &format);

10
src/cc/CCutils.cpp

@ -181,6 +181,16 @@ bool Getscriptaddress(char *destaddr,const CScript &scriptPubKey)
return(false);
}
CPubKey CCtxidaddr(char *txidaddr,uint256 txid)
{
uint8_t buf33[33]; CPubKey pk;
buf33[0] = 0x02;
endiancpy(&buf33[1],(uint8_t *)&txid,32);
pk = buf2pk(buf33);
Getscriptaddress(coinaddr,CScript() << ParseHex(HexStr(pk)) << OP_CHECKSIG);
return(pk);
}
bool _GetCCaddress(char *destaddr,uint8_t evalcode,CPubKey pk)
{
CC *payoutCond;

62
src/cc/gateways.cpp

@ -16,12 +16,11 @@
#include "CCGateways.h"
/*
prevent duplicate bindtxid via mempool scan
claim needs to create a marker that is checked or validate deposittxid spending is ok
prevent duplicate bindtxid and cointxid via mempool scan
assets vin selector needs to filter by signable vins
loop on merkle
loop on merkle oracle
string oracles
*/
@ -107,7 +106,7 @@ string oracles
010000000149964cdcd17fe9b1cae4d0f3b5f5db301d9b4f54099fdf4d34498df281757094010000006a4730440220594f3a630dd73c123f44621aa8bb9968ab86734833453dd479af6d79ae6f584202207bb5e35f13b337ccc8a88d9a006c8c5ddb016c0a6f4f2dc44357a8128623d85d01210223154bf53cd3a75e64d86697070d6437c8f0010a09c1df35b659e31ce3d79b5dffffffff0310270000000000001976a91447d2e323a14b0c3be08698aa46a9b91489b189d688ac701de52d000000001976a91459fdba29ea85c65ad90f6d38f7a6646476b26b1688acb0a86a00000000001976a914f9a9daf5519dae38b8b61d945f075da895df441d88ace18d965b
gatewaysdeposit bindtxid height coin cointxid claimvout deposithex proof destpub amount numpks oraclepks
./komodo-cli -ac_name=AT5 gatewaysdeposit e6c99f79d4afb216aa8063658b4222edb773dd24bb0f8e91bd4ef341f3e47e5e 1003776 KMD bc41a00e429db741c3199f17546a48012fd3b7eea45dfc6bc2f5228278133009 0 010000000149964cdcd17fe9b1cae4d0f3b5f5db301d9b4f54099fdf4d34498df281757094010000006a4730440220594f3a630dd73c123f44621aa8bb9968ab86734833453dd479af6d79ae6f584202207bb5e35f13b337ccc8a88d9a006c8c5ddb016c0a6f4f2dc44357a8128623d85d01210223154bf53cd3a75e64d86697070d6437c8f0010a09c1df35b659e31ce3d79b5dffffffff0310270000000000001976a91447d2e323a14b0c3be08698aa46a9b91489b189d688ac701de52d000000001976a91459fdba29ea85c65ad90f6d38f7a6646476b26b1688acb0a86a00000000001976a914f9a9daf5519dae38b8b61d945f075da895df441d88ace18d965b proofhere 76a91447d2e323a14b0c3be08698aa46a9b91489b189d688ac 7.6999 1 02ebc786cb83de8dc3922ab83c21f3f8a2f3216940c3bf9da43ce39e2a3a882c92
./komodo-cli -ac_name=AT5 gatewaysdeposit e6c99f79d4afb216aa8063658b4222edb773dd24bb0f8e91bd4ef341f3e47e5e 1003776 KMD bc41a00e429db741c3199f17546a48012fd3b7eea45dfc6bc2f5228278133009 0 010000000149964cdcd17fe9b1cae4d0f3b5f5db301d9b4f54099fdf4d34498df281757094010000006a4730440220594f3a630dd73c123f44621aa8bb9968ab86734833453dd479af6d79ae6f584202207bb5e35f13b337ccc8a88d9a006c8c5ddb016c0a6f4f2dc44357a8128623d85d01210223154bf53cd3a75e64d86697070d6437c8f0010a09c1df35b659e31ce3d79b5dffffffff0310270000000000001976a91447d2e323a14b0c3be08698aa46a9b91489b189d688ac701de52d000000001976a91459fdba29ea85c65ad90f6d38f7a6646476b26b1688acb0a86a00000000001976a914f9a9daf5519dae38b8b61d945f075da895df441d88ace18d965b 04000000232524ea04b54489eb222f8b3f566ed35504e3050488a63f0ab7b1c2030000007f91615f04835822bf6984a56482aeeb11d03814352eca9afc0a20192fdcae900000000000000000000000000000000000000000000000000000000000000000268e965ba608071d0800038e16762900000000000000000000000000000000000000000042008dd6fd4005016b4292b05df6dfd316c458c53e28fb2befb96e4870a40c6c04e733d75d8a7a18cce34fe326005efdc403bfa4644e30eeafdaeff34419edc591299e6cc5933cb2eeecbab5c4dfe97cd413b75215999a3dd02b540373581e81d8512bff1640590a6b4da4aaa9b8adc0102c38ca0022daed997b53ed192ba326e212fba5e505ce29e3ad149cef7f48d0e00948a1acd81731d84008760759211eb4abbc7b037939a7964182edb59cf9065357e864188ee5fc7316e8796963036bb99eeb9f06c95d64f78749ecec7181c12eb5d83a3b9b1c1e8a0aae9a20ce04a250b28216620bfc99bb81a6de4db80b93a5aea916de97c1a272e26644abdd683f19c5e3174a2e4513ed767d8f11a4c3074295f697839c5d9139676a813451cc7da38f68cbae5d990a79075f98903233ca04fe1b4b099e433585e5adcc45d41d54a9c648179297359c75950a5e574f13f70b728bbbf552770256315cd0a00139d6ab6934cb5ed70a4fc01a92611b096dd0028f17f4cc687b75f37dca530aa47a18321c50528dbd9272eabb3e13a87021a05918a6d2627e2caba6d7cf1a9f0b831ea3337b9a6af92746d83140078d60c72b6beacf91c9e68a34cee209e08670be1d17ff8d80b7a2285b1325461a2e33f2ee675593f1900e066a5d212615cd8da18749b0e684eee73edcc9031709be715b889c6d015cf4bd4ad5ab7e21bd3492c208930a54d353ef36a437f507ead38855633c1b88d060d9e4221ca8ce2f698e8a6ae0d41e9ace3cbd401f1e0f07650e9c126d4ef20278c8be6e85c7637513643f8d02d7ad64c09da11c16429d60e5160c345844b8158ece62794e8ad280d4e4664150e74978609ece431e51a9f9e1ce8aa49c16f36c7fd12b71acc42d893e18476b8b1e144a8175519612efc93e0aecc61f3b21212c958b0e2331d76aaa62faf11a58fe2bd91ab9ab01b906406c9bbc02df2a106e67182aae0a20b538bf19f09c57f9de5e198ba254580fb1b11e22ad526550093420cb7c68628d4c3ad329c8acc6e219093d277810ed016b6099b7e3781de412a22dacedaa2acf29e8062debcd85c7b9529a20b2782a2470763ac27cf89611a527d43ac89b8063ffb93b6ed993425194f8ee821a8493a563072c896f9584f95db28e3f2fc5fb4a6f3c39d615cd563641717cd50afb73ed3989cbf504b2043882993ce9575f56402534173b1396fbc13df80920b46788ae340ad5a91f25177cc74aa69024d76f56166199d2e4d50a053555256c4e3137ea1cee1130e916a88b6ee5cf2c85652fb8824d5dacfa485e3ef6190591ac0c2fcacc4fc7deb65aca4b0b89b76e35a46b0627e2e967cc63a5d606a984c8e63eabb98fde3e69114340ae524c974cb936e57690e98a7a74533f6f7d1d0496976496b54d14a8163efb32b70dfbb79d80a3022c4f53571c08bf044270565716b435084376714b224ab23e9817c05af8223723afc0577af5c8fc28f71036ca82528aaa4ca9bcd18a50e25d2a528f183d3a2074d968d170876d8dce434c5937261b55173ab87e03d5632ca0834fdc5387c15ab3a17d75c0f274004f289ff1bf7d14e97fdf4172eb49adfb418cc2f4794806ae7c0111c97df4d65d38679ec93fea3ef738ed565e8906a8fe1861cafe3938c772fedcfab40159938e06ef414fd299f2355c6d3369bc1bd3c4db64ce205f0a1b70a40030f505b736e28230de82e97776b5ee7b10708bb3020d28cec7a8e124549ec80c547ac4e7b52bf397c72bcfce30820554ab8fb4d1f73b209bc32a0e7e878843cdbf5f01222728ccea7e6ab7cb5e3fee3234f5b85d1985f91492f6ceaa6454a658dab5074f163ce26ed753137fa61c940679de13bd7b212cd3cf2b334f5201cecbc7473342bd7a239e09169bccd56d03000000037a9068df0625e548e71263c8361b4e904c998378f6b9e32729c3f19b10ad752e093013788222f5c26bfc5da4eeb7d32f01486a54179f19c341b79d420ea041bc8878d22fad4692b2d609c3cf190903874d3682a714c7483518c9392e07c25035010b 76a91447d2e323a14b0c3be08698aa46a9b91489b189d688ac 7.6999 1 02ebc786cb83de8dc3922ab83c21f3f8a2f3216940c3bf9da43ce39e2a3a882c92
-> acc58b4552c4696406b681eb853b0c9a80ef8f02e3ec0e7bbd407b9261ecba81
gatewaysclaim bindtxid coin deposittxid destpub amount
@ -214,7 +213,7 @@ bool GatewaysExactAmounts(struct CCcontract_info *cp,Eval* eval,const CTransacti
else return(true);
}
bool GatewaysValidate(struct CCcontract_info *cp,Eval* eval,const CTransaction &tx)
bool GatewaysValidate(struct CCcontract_info *cp,Eval *eval,const CTransaction &tx)
{
int32_t numvins,numvouts,preventCCvins,preventCCvouts,i,numblocks; bool retval; uint256 txid; uint8_t hash[32]; char str[65],destaddr[64];
std::vector<std::pair<CAddressIndexKey, CAmount> > txids;
@ -284,6 +283,44 @@ int64_t AddGatewaysInputs(struct CCcontract_info *cp,CMutableTransaction &mtx,CP
return(totalinputs);
}
int32_t GatewaysBindExists(struct CCcontract_info *cp,CPubKey gatewayspk,uint256 reftokenid) // dont forget to check mempool!
{
char markeraddr[64]; std::string coin; int32_t numvouts; int64_t totalsupply; uint256 oracletxid,hashBlock; uint8_t M,N,taddr,prefix,prefix2; std::vector<CPubKey> pubkeys;
std::vector<std::pair<CAddressIndexKey, CAmount> > addressIndex;
_GetCCaddress(markeraddr,EVAL_GATEWAYS,gatewayspk);
fprintf(stderr,"bind markeraddr.(%s) need to scan mempool also\n",markeraddr);
SetCCtxids(addressIndex,markeraddr);
for (std::vector<std::pair<CAddressIndexKey, CAmount> >::const_iterator it=addressIndex.begin(); it!=addressIndex.end(); it++)
{
if ( GetTransaction(it->first.txhash,tx,hashBlock,false) != 0 && (numvouts= tx.vout.size()) > 0 )
{
if ( DecodeGatewaysBindOpRet(tx.vout[numvouts-1].scriptPubKey,coin,tokenid,totalsupply,oracletxid,M,N,pubkeys,taddr,prefix,prefix2) == 'B' )
{
if ( tokenid == reftokenid )
{
fprintf(stderr,"trying to bind an existing tokenid\n");
return(1);
}
}
}
}
return(0);
}
int32_t GatewaysCointxidExists(struct CCcontract_info *cp,uint256 cointxid) // dont forget to check mempool!
{
char txidaddr[64]; std::string coin; int32_t numvouts; uint256 hashBlock;
std::vector<std::pair<CAddressIndexKey, CAmount> > addressIndex;
CCtxidaddr(txidaddr,cointxid);
fprintf(stderr," txidaddr.(%s) need to scan mempool also\n",txidaddr);
SetCCtxids(addressIndex,txidaddr);
for (std::vector<std::pair<CAddressIndexKey, CAmount> >::const_iterator it=addressIndex.begin(); it!=addressIndex.end(); it++)
{
return(-1);
}
return(0);
}
UniValue GatewaysInfo(uint256 bindtxid)
{
UniValue result(UniValue::VOBJ),a(UniValue::VARR); std::string coin; char str[67],numstr[65],depositaddr[64],gatewaysassets[64]; uint8_t M,N; std::vector<CPubKey> pubkeys; uint8_t taddr,prefix,prefix2; uint256 tokenid,oracletxid,hashBlock; CTransaction tx; CMutableTransaction mtx; CPubKey Gatewayspk; struct CCcontract_info *cp,C; int32_t i; int64_t totalsupply,remaining;
@ -408,12 +445,11 @@ std::string GatewaysBind(uint64_t txfee,std::string coin,uint256 tokenid,int64_t
fprintf(stderr,"illegal format (%s) != (%s)\n",fstr,(char *)"Ihh");
return("");
}
fprintf(stderr,"implement GatewaysBindExists\n");
/*if ( GatewaysBindExists(cp,gatewayspk,coin,tokenid) != 0 ) // dont forget to check mempool!
if ( GatewaysBindExists(cp,gatewayspk,tokenid) != 0 ) // dont forget to check mempool!
{
fprintf(stderr,"Gateway bind.%s (%s) already exists\n",coin.c_str(),uint256_str(str,tokenid));
return("");
}*/
}
if ( AddNormalinputs(mtx,mypk,2*txfee,60) > 0 )
{
mtx.vout.push_back(MakeCC1vout(cp->evalcode,txfee,gatewayspk));
@ -521,7 +557,7 @@ int64_t GatewaysDepositval(CTransaction tx)
std::string GatewaysDeposit(uint64_t txfee,uint256 bindtxid,std::vector<CPubKey>pubkeys,int32_t height,std::string refcoin,uint256 cointxid,int32_t claimvout,std::string deposithex,std::vector<uint8_t>proof,CPubKey destpub,int64_t amount)
{
CMutableTransaction mtx; CTransaction bindtx; CPubKey mypk,gatewayspk; uint256 oracletxid,merkleroot,mhash,hashBlock,tokenid,txid; int64_t totalsupply; int32_t i,m,n,numvouts; uint8_t M,N,taddr,prefix,prefix2; std::string coin; struct CCcontract_info *cp,C; std::vector<CPubKey> msigpubkeys,publishers; std::vector<uint256>txids; char str[65],depositaddr[64];
CMutableTransaction mtx; CTransaction bindtx; CPubKey mypk,gatewayspk; uint256 oracletxid,merkleroot,mhash,hashBlock,tokenid,txid; int64_t totalsupply; int32_t i,m,n,numvouts; uint8_t M,N,taddr,prefix,prefix2; std::string coin; struct CCcontract_info *cp,C; std::vector<CPubKey> msigpubkeys,publishers; std::vector<uint256>txids; char str[65],depositaddr[64],txidaddr[64];
cp = CCinit(&C,EVAL_GATEWAYS);
if ( txfee == 0 )
txfee = 10000;
@ -538,6 +574,11 @@ std::string GatewaysDeposit(uint64_t txfee,uint256 bindtxid,std::vector<CPubKey>
fprintf(stderr,"invalid bindtxid %s coin.%s\n",uint256_str(str,bindtxid),coin.c_str());
return("");
}
if ( GatewaysCointxidExists(cp,cointxid) != 0 )
{
fprintf(stderr,"cointxid.%s already exists\n",uint256_str(str,cointxid));
return("");
}
n = (int32_t)pubkeys.size();
merkleroot = zeroid;
for (i=m=0; i<n; i++)
@ -563,9 +604,10 @@ std::string GatewaysDeposit(uint64_t txfee,uint256 bindtxid,std::vector<CPubKey>
fprintf(stderr,"deposittxid didnt validate\n");
return("");
}
if ( AddNormalinputs(mtx,mypk,2*txfee,60) > 0 )
if ( AddNormalinputs(mtx,mypk,3*txfee,60) > 0 )
{
mtx.vout.push_back(MakeCC1vout(cp->evalcode,txfee,mypk));
mtx.vout.push_back(CTxOut(txfee,CScript() << ParseHex(HexStr(CCtxidaddr(txidaddr,cointxid))) << OP_CHECKSIG));
return(FinalizeCCTx(0,cp,mtx,mypk,txfee,EncodeGatewaysOpRet('D',coin,bindtxid,publishers,txids,height,cointxid,deposithex,proof,destpub,amount)));
}
fprintf(stderr,"cant find enough inputs\n");

36
src/cc/oracles.cpp

@ -223,16 +223,12 @@ int64_t OracleCurrentDatafee(uint256 reforacletxid,char *markeraddr,CPubKey publ
int64_t OracleDatafee(CScript &scriptPubKey,uint256 oracletxid,CPubKey publisher)
{
CTransaction oracletx; char markeraddr[64]; CPubKey markerpubkey; uint8_t buf33[33]; uint256 hashBlock; std::string name,description,format; int32_t numvouts; int64_t datafee = 0;
CTransaction oracletx; char markeraddr[64]; uint256 hashBlock; std::string name,description,format; int32_t numvouts; int64_t datafee = 0;
if ( myGetTransaction(oracletxid,oracletx,hashBlock) != 0 && (numvouts= oracletx.vout.size()) > 0 )
{
if ( DecodeOraclesCreateOpRet(oracletx.vout[numvouts-1].scriptPubKey,name,description,format) == 'C' )
{
buf33[0] = 0x02;
endiancpy(&buf33[1],(uint8_t *)&oracletxid,32);
markerpubkey = buf2pk(buf33);
scriptPubKey = CScript() << ParseHex(HexStr(markerpubkey)) << OP_CHECKSIG;
Getscriptaddress(markeraddr,scriptPubKey);
CCtxidaddr(markeraddr,oracletxid);
datafee = OracleCurrentDatafee(oracletxid,markeraddr,publisher);
}
}
@ -484,13 +480,10 @@ int64_t OraclePrice(int32_t height,uint256 reforacletxid,char *markeraddr,char *
uint256 OraclesBatontxid(uint256 reforacletxid,CPubKey refpk)
{
std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> > unspentOutputs;
CTransaction tx; uint256 hash,txid,rettxid,oracletxid; CPubKey pk,markerpubkey; int32_t numvouts,maxheight=0; int64_t datafee,ht; uint8_t buf33[33]; char markeraddr[64]; std::vector <uint8_t> data; struct CCcontract_info *cp,C;
CTransaction tx; uint256 hash,txid,rettxid,oracletxid; CPubKey pk; int32_t numvouts,maxheight=0; int64_t datafee,ht; char markeraddr[64]; std::vector <uint8_t> data; struct CCcontract_info *cp,C;
rettxid = zeroid;
cp = CCinit(&C,EVAL_ORACLES);
buf33[0] = 0x02;
endiancpy(&buf33[1],(uint8_t *)&reforacletxid,32);
markerpubkey = buf2pk(buf33);
Getscriptaddress(markeraddr,CScript() << ParseHex(HexStr(markerpubkey)) << OP_CHECKSIG);
CCtxidaddr(markeraddr,reforacletxid);
SetCCunspents(unspentOutputs,markeraddr);
for (std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> >::const_iterator it=unspentOutputs.begin(); it!=unspentOutputs.end(); it++)
{
@ -721,7 +714,7 @@ std::string OracleCreate(int64_t txfee,std::string name,std::string description,
std::string OracleRegister(int64_t txfee,uint256 oracletxid,int64_t datafee)
{
CMutableTransaction mtx; CPubKey mypk,markerpubkey,batonpk; struct CCcontract_info *cp,C; uint8_t buf33[33]; char markeraddr[64],batonaddr[64];
CMutableTransaction mtx; CPubKey mypk,markerpubkey,batonpk; struct CCcontract_info *cp,C; char markeraddr[64],batonaddr[64];
cp = CCinit(&C,EVAL_ORACLES);
if ( txfee == 0 )
txfee = 10000;
@ -731,11 +724,8 @@ std::string OracleRegister(int64_t txfee,uint256 oracletxid,int64_t datafee)
return("");
}
mypk = pubkey2pk(Mypubkey());
buf33[0] = 0x02;
endiancpy(&buf33[1],(uint8_t *)&oracletxid,32);
markerpubkey = buf2pk(buf33);
Getscriptaddress(markeraddr,CScript() << ParseHex(HexStr(markerpubkey)) << OP_CHECKSIG);
batonpk = OracleBatonPk(batonaddr,cp);
markerpubkey = CCtxidaddr(markeraddr,oracletxid);
if ( AddNormalinputs(mtx,mypk,3*txfee,4) > 0 )
{
mtx.vout.push_back(CTxOut(txfee,CScript() << ParseHex(HexStr(markerpubkey)) << OP_CHECKSIG));
@ -747,15 +737,12 @@ std::string OracleRegister(int64_t txfee,uint256 oracletxid,int64_t datafee)
std::string OracleSubscribe(int64_t txfee,uint256 oracletxid,CPubKey publisher,int64_t amount)
{
CMutableTransaction mtx; CPubKey mypk,markerpubkey; struct CCcontract_info *cp,C; uint8_t buf33[33]; char markeraddr[64];
CMutableTransaction mtx; CPubKey mypk,markerpubkey; struct CCcontract_info *cp,C; char markeraddr[64];
cp = CCinit(&C,EVAL_ORACLES);
if ( txfee == 0 )
txfee = 10000;
mypk = pubkey2pk(Mypubkey());
buf33[0] = 0x02;
endiancpy(&buf33[1],(uint8_t *)&oracletxid,32);
markerpubkey = buf2pk(buf33);
Getscriptaddress(markeraddr,CScript() << ParseHex(HexStr(markerpubkey)) << OP_CHECKSIG);
markerpubkey = CCtxidaddr(markeraddr,oracletxid);
if ( AddNormalinputs(mtx,mypk,amount + 2*txfee,1) > 0 )
{
mtx.vout.push_back(MakeCC1vout(cp->evalcode,amount,publisher));
@ -849,12 +836,9 @@ UniValue OracleInfo(uint256 origtxid)
{
UniValue result(UniValue::VOBJ),a(UniValue::VARR),obj(UniValue::VOBJ);
std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> > unspentOutputs;
CMutableTransaction mtx; CTransaction regtx,tx; std::string name,description,format; uint256 hashBlock,txid,oracletxid,batontxid; CPubKey markerpubkey,pk; struct CCcontract_info *cp,C; uint8_t buf33[33]; int64_t datafee,funding; char str[67],markeraddr[64],numstr[64],batonaddr[64]; std::vector <uint8_t> data;
CMutableTransaction mtx; CTransaction regtx,tx; std::string name,description,format; uint256 hashBlock,txid,oracletxid,batontxid; CPubKey pk; struct CCcontract_info *cp,C; int64_t datafee,funding; char str[67],markeraddr[64],numstr[64],batonaddr[64]; std::vector <uint8_t> data;
cp = CCinit(&C,EVAL_ORACLES);
buf33[0] = 0x02;
endiancpy(&buf33[1],(uint8_t *)&origtxid,32);
markerpubkey = buf2pk(buf33);
Getscriptaddress(markeraddr,CScript() << ParseHex(HexStr(markerpubkey)) << OP_CHECKSIG);
CCtxidaddr(markeraddr,origtxid);
if ( GetTransaction(origtxid,tx,hashBlock,false) != 0 )
{
if ( tx.vout.size() > 0 && DecodeOraclesCreateOpRet(tx.vout[tx.vout.size()-1].scriptPubKey,name,description,format) == 'C' )

Loading…
Cancel
Save