diff --git a/src/qt/assetmanagerpage.cpp b/src/qt/assetmanagerpage.cpp index be97e39..c745c26 100644 --- a/src/qt/assetmanagerpage.cpp +++ b/src/qt/assetmanagerpage.cpp @@ -41,10 +41,16 @@ void AssetManagerPage::setWalletModel(WalletModel *_walletModel, ClientModel *_c contractFilter = new ContractFilterProxy(this); contractFilter->setSourceModel(walletModel->getContractTableModel()); - contractFilter->setSortRole(ContractTableModel::NameRole); contractFilter->setOnlyMine(false); + contractFilter->setSortRole(ContractTableModel::NameRole); contractFilter->sort(ContractTableModel::Name, Qt::AscendingOrder); + mycontractFilter = new ContractFilterProxy(this); + mycontractFilter->setSourceModel(walletModel->getContractTableModel()); + mycontractFilter->setOnlyMine(true); + mycontractFilter->setSortRole(ContractTableModel::NameRole); + mycontractFilter->sort(ContractTableModel::Name, Qt::AscendingOrder); + assetFilter = new AssetFilterProxy(this); assetFilter->setSourceModel(walletModel->getAssetTableModel()); assetFilter->setDynamicSortFilter(true); @@ -114,7 +120,7 @@ void AssetManagerPage::on_CreateNewAsset_clicked() NewAssetPage dialog(this); dialog.setWindowModality(Qt::ApplicationModal); dialog.setWindowTitle("Create a New Asset"); - dialog.setWalletModel(walletModel); + dialog.setWalletModel(walletModel, mycontractFilter); if(dialog.exec()) update(); } diff --git a/src/qt/assetmanagerpage.h b/src/qt/assetmanagerpage.h index 5e12693..c5b0e8d 100644 --- a/src/qt/assetmanagerpage.h +++ b/src/qt/assetmanagerpage.h @@ -38,6 +38,7 @@ class AssetManagerPage : public QWidget AssetFilterProxy *assetFilter = nullptr; ContractFilterProxy *contractFilter = nullptr; CoinControlFilterProxy *coincontrolfilter = nullptr; + ContractFilterProxy *mycontractFilter = nullptr; void updateAssetList(); void updateContractList(); diff --git a/src/qt/assettablemodel.cpp b/src/qt/assettablemodel.cpp index bef8ea5..2a78d4b 100755 --- a/src/qt/assettablemodel.cpp +++ b/src/qt/assettablemodel.cpp @@ -8,6 +8,9 @@ #include #include +#include +#include + #include #include #include @@ -256,6 +259,11 @@ void AssetTableModel::update() { if (walletModel->getClientModel()->node().isInitialBlockDownload()) return; + + if (!masternodeSync.IsSynced() || !systemnodeSync.IsSynced()) { + return; + } + Q_EMIT layoutAboutToBeChanged(); beginResetModel(); endResetModel(); diff --git a/src/qt/contracttablemodel.cpp b/src/qt/contracttablemodel.cpp index 414ec57..9fa7c4a 100755 --- a/src/qt/contracttablemodel.cpp +++ b/src/qt/contracttablemodel.cpp @@ -8,6 +8,9 @@ #include #include +#include +#include + #include #include #include @@ -223,10 +226,15 @@ void ContractTableModel::update() { if (walletModel->getClientModel()->node().isInitialBlockDownload()) return; - Q_EMIT layoutAboutToBeChanged(); - beginResetModel(); - endResetModel(); + + if (!masternodeSync.IsSynced() || !systemnodeSync.IsSynced()) { + return; + } + +// Q_EMIT layoutAboutToBeChanged(); +// beginResetModel(); +// endResetModel(); priv->refreshWallet(); Q_EMIT dataChanged(index(0, 0, QModelIndex()), index(priv->size(), columns.length()-1, QModelIndex())); - Q_EMIT layoutChanged(); +// Q_EMIT layoutChanged(); } diff --git a/src/qt/forms/assetmanagerpage.ui b/src/qt/forms/assetmanagerpage.ui index e379aee..b402dc5 100644 --- a/src/qt/forms/assetmanagerpage.ui +++ b/src/qt/forms/assetmanagerpage.ui @@ -17,7 +17,7 @@ - 0 + 1 @@ -163,6 +163,9 @@ + + + New Contract diff --git a/src/qt/forms/newassetpage.ui b/src/qt/forms/newassetpage.ui index 4e66aa1..f41ab77 100644 --- a/src/qt/forms/newassetpage.ui +++ b/src/qt/forms/newassetpage.ui @@ -21,10 +21,23 @@ - + + + + + + + + + Select Contract + + + + + Qt::LeftToRight @@ -60,6 +73,9 @@ + + + Qt::AlignCenter @@ -74,6 +90,9 @@ + + + Qt::AlignCenter @@ -84,6 +103,9 @@ + + + Qt::AlignCenter @@ -98,6 +120,9 @@ + + + Convertable @@ -105,6 +130,9 @@ + + + Transferable @@ -112,6 +140,9 @@ + + + Restricted @@ -119,6 +150,9 @@ + + + Limited @@ -126,6 +160,9 @@ + + + Divisible @@ -165,6 +202,9 @@ + + + Create diff --git a/src/qt/forms/newcontractpage.ui b/src/qt/forms/newcontractpage.ui index 675df3a..95eb179 100644 --- a/src/qt/forms/newcontractpage.ui +++ b/src/qt/forms/newcontractpage.ui @@ -38,6 +38,9 @@ + + + @@ -51,6 +54,9 @@ + + + Qt::AlignCenter @@ -65,6 +71,9 @@ + + + Qt::AlignCenter @@ -75,6 +84,9 @@ + + + Qt::AlignCenter @@ -91,6 +103,9 @@ + + + Enter Contract Script @@ -98,6 +113,9 @@ + + + Enter Contract Description diff --git a/src/qt/newassetpage.cpp b/src/qt/newassetpage.cpp index e5f98c7..d200062 100644 --- a/src/qt/newassetpage.cpp +++ b/src/qt/newassetpage.cpp @@ -16,6 +16,35 @@ NewAssetPage::NewAssetPage(QWidget *parent) : ui->inputAmount->setValidator( new QDoubleValidator(0, 1000000, 4, this) ); ui->outputAmount->setValidator( new QDoubleValidator(0, 1000000, 4, this) ); + ui->inputAmount->setStatusTip(tr("Crown to use to create Asset")); + ui->inputAmount->setToolTip(ui->inputAmount->statusTip()); + + ui->outputAmount->setStatusTip(tr("Asset amount to create")); + ui->outputAmount->setToolTip(ui->outputAmount->statusTip()); + + ui->contractcomboBox->setStatusTip(tr("Select Contract to use")); + ui->contractcomboBox->setToolTip(ui->contractcomboBox->statusTip()); + + ui->typecomboBox->setStatusTip(tr("Select Asset Type")); + ui->typecomboBox->setToolTip(ui->typecomboBox->statusTip()); + + ui->expiry->setStatusTip(tr("Set Expiry Date")); + ui->expiry->setToolTip(ui->expiry->statusTip()); + + ui->convertcheckBox->setStatusTip(tr("Can the asset be later liquidated")); + ui->convertcheckBox->setToolTip(ui->convertcheckBox->statusTip()); + + ui->transfercheckBox->setStatusTip(tr("Can the asset be sent")); + ui->transfercheckBox->setToolTip(ui->transfercheckBox->statusTip()); + + ui->restrictedcheckBox->setStatusTip(tr("Restrict sending to Issuer")); + ui->restrictedcheckBox->setToolTip(ui->restrictedcheckBox->statusTip()); + + ui->limitedcheckBox->setStatusTip(tr("Prevent conversion from other assets")); + ui->limitedcheckBox->setToolTip(ui->limitedcheckBox->statusTip()); + + ui->divisiblecheckBox->setStatusTip(tr("Allow/Disallow division into smaller units")); + ui->divisiblecheckBox->setToolTip(ui->divisiblecheckBox->statusTip()); } NewAssetPage::~NewAssetPage() @@ -23,21 +52,14 @@ NewAssetPage::~NewAssetPage() delete ui; } -void NewAssetPage::setWalletModel(WalletModel* model) +void NewAssetPage::setWalletModel(WalletModel* model, ContractFilterProxy *mycontractFilter) { if (!model) return; this->walletModel = model; - mycontractFilter = new ContractFilterProxy(this); - mycontractFilter->setSourceModel(walletModel->getContractTableModel()); - mycontractFilter->setOnlyMine(true); - mycontractFilter->setSortRole(ContractTableModel::IssuerRole); - mycontractFilter->sort(ContractTableModel::Issuer, Qt::AscendingOrder); - ui->contractcomboBox->setModel(mycontractFilter); - qDebug() << "NewAssetPage, filter size " << mycontractFilter->rowCount(); - + } void NewAssetPage::on_Create_clicked() @@ -57,7 +79,7 @@ void NewAssetPage::on_Create_clicked() QString format = "dd-MM-yyyy hh:mm:ss"; QDateTime expiryDate = QDateTime::fromString(expiry, format); - QMessageBox* msgbox = new QMessageBox(this); + QMessageBox* msgbox = new QMessageBox(this); WalletModel::EncryptionStatus encStatus = walletModel->getEncryptionStatus(); if(encStatus == walletModel->Locked) { @@ -65,40 +87,40 @@ void NewAssetPage::on_Create_clicked() if(!ctx.isValid()) { return; } - if(!walletModel->CreateAsset(inputamount, outputamount, assettype, assetcontract, transferable, convertable, restricted, limited, divisible, expiryDate, strFailReason)){ - msgbox->setWindowTitle("Failed To Create Asset"); - msgbox->setText(QString::fromStdString(strFailReason)); + if(!walletModel->CreateAsset(inputamount, outputamount, assettype, assetcontract, transferable, convertable, restricted, limited, divisible, expiryDate, strFailReason)){ + msgbox->setWindowTitle("Failed To Create Asset"); + msgbox->setText(QString::fromStdString(strFailReason)); msgbox->setStandardButtons(QMessageBox::Cancel); msgbox->setDefaultButton(QMessageBox::Cancel); - } - else { - msgbox->setWindowTitle("New Asset Created"); - msgbox->setText("Success"); - } + } + else { + msgbox->setWindowTitle("New Asset Created"); + msgbox->setText("Success"); + } return; } if(!walletModel->CreateAsset(inputamount, outputamount, assettype, assetcontract, transferable, convertable, restricted, limited, divisible, expiryDate, strFailReason)){ msgbox->setWindowTitle("Failed To Create Asset"); msgbox->setText(QString::fromStdString(strFailReason)); - msgbox->setStandardButtons(QMessageBox::Cancel); - msgbox->setDefaultButton(QMessageBox::Cancel); + msgbox->setStandardButtons(QMessageBox::Cancel); + msgbox->setDefaultButton(QMessageBox::Cancel); } else { msgbox->setWindowTitle("New Asset Created"); msgbox->setText("Success"); } - + int ret = msgbox->exec(); - switch (ret) { - case QMessageBox::Cancel: - msgbox->hide(); - break; - default: - accept(); - break; - } + switch (ret) { + case QMessageBox::Cancel: + msgbox->hide(); + break; + default: + accept(); + break; + } } QString NewAssetPage::getinputamount(){ diff --git a/src/qt/newassetpage.h b/src/qt/newassetpage.h index 2917193..c6f58d3 100644 --- a/src/qt/newassetpage.h +++ b/src/qt/newassetpage.h @@ -28,9 +28,7 @@ class NewAssetPage : public QDialog bool getlimited(); bool getdivisible(); QString getexpiry(); - void setWalletModel(WalletModel* walletModel); - ContractFilterProxy *mycontractFilter = nullptr; - ContractTableModel *contractTableModel; + void setWalletModel(WalletModel* walletModel, ContractFilterProxy *mycontractFilter); private: WalletModel* walletModel; diff --git a/src/rpc/contracts.cpp b/src/rpc/contracts.cpp index 82f687b..091644a 100755 --- a/src/rpc/contracts.cpp +++ b/src/rpc/contracts.cpp @@ -288,13 +288,14 @@ static RPCHelpMan convertasset() std::string name = request.params[0].get_str(); CAmount nAmount = AmountFromValue(request.params[1].get_int()); + std::string address = request.params[3].get_str(); CAsset asset = GetAsset(name); CAmountMap assetAmount{{asset,nAmount}}; CTransactionRef tx; std::string strFailReason; - if(!pwallet->ConvertAsset(assetAmount, tx, strFailReason)) + if(!pwallet->ConvertAsset(assetAmount, tx, address, strFailReason)) throw JSONRPCError(RPC_MISC_ERROR, strFailReason); return tx->GetHash().GetHex(); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index eada15f..4df49af 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -3306,7 +3306,7 @@ bool CWallet::CreateContract(CContract& contract, CTransactionRef& tx, std::stri strFailReason = "Contract name already reserved"; return false; } - + if(name == "" || shortname == "") { strFailReason = "Contract name or symbol cannot be empty"; return false; @@ -3416,7 +3416,7 @@ bool CWallet::CreateContract(CContract& contract, CTransactionRef& tx, std::stri return true; } -bool CWallet::ConvertAsset(CAmountMap &assetin, CTransactionRef& tx, std::string& strFailReason){ +bool CWallet::ConvertAsset(CAmountMap &assetin, CTransactionRef& tx, std::string& address, std::string& strFailReason){ if (IsLocked()){ strFailReason = "Wallet Locked"; @@ -3455,49 +3455,139 @@ bool CWallet::ConvertAsset(CAmountMap &assetin, CTransactionRef& tx, std::string double rate = (assetdata.issuedAmount / assetdata.inputAmount) + (assetdata.issuedAmount % assetdata.inputAmount); rate *= amountin; CCoinControl coin_control; - - // Do we have the coins ? - std::vector vecOutputs; + std::set setCoins; + LOCK(cs_wallet); { - CCoinControl cctl; - cctl.m_avoid_address_reuse = false; - cctl.m_min_depth = 1; - cctl.m_max_depth = 9999999; - AvailableCoins(vecOutputs, asset, false, &cctl, ALL_COINS, 0, MAX_MONEY, amountin, 0); - } - CAmount available =0; - for (const COutput& out : vecOutputs) { - // Elements - CAmount amount = (out.tx->tx->nVersion >= TX_ELE_VERSION ? out.tx->tx->vpout[out.i].nValue : out.tx->tx->vout[out.i].nValue) ; - CAsset assetid; - if(out.tx->tx->nVersion >= TX_ELE_VERSION) - assetid = out.tx->tx->vpout[out.i].nAsset; + // Do we have the coins ? + std::vector vecOutputs; + { + coin_control.m_avoid_address_reuse = false; + coin_control.m_min_depth = 1; + coin_control.m_max_depth = 9999999; + AvailableCoins(vecOutputs, asset, false, &coin_control, ALL_COINS, 0, MAX_MONEY, amountin, 0); + } + CAmount available =0; + for (const COutput& out : vecOutputs) { + // Elements + CAmount amount = (out.tx->tx->nVersion >= TX_ELE_VERSION ? out.tx->tx->vpout[out.i].nValue : out.tx->tx->vout[out.i].nValue) ; + CAsset assetid; + if(out.tx->tx->nVersion >= TX_ELE_VERSION) + assetid = out.tx->tx->vpout[out.i].nAsset; - if ((amount < 0 || assetid.IsNull())) { - WalletLogPrintf("Bad amount or asset: %s:%d\n", out.tx->tx->GetHash().GetHex(), out.i); - continue; + if ((amount < 0 || assetid.IsNull())) { + WalletLogPrintf("Bad amount or asset: %s:%d\n", out.tx->tx->GetHash().GetHex(), out.i); + continue; + } + + if (asset != assetid) { + continue; + } + available += amount; + + coin_control.Select(COutPoint(out.tx->GetHash(), out.i)); } - if (asset != assetid) { - continue; + if(coin_control.setSelected.size() < 1){ + strFailReason ="No suitable output found"; + return false; } - available += amount; - coin_control.Select(COutPoint(out.tx->GetHash(), out.i)); - } + if(available < amountin){ + strFailReason ="Insufficient Funds of this asset"; + return false; + } + CTxDestination dest = DecodeDestination(address); + if (!IsValidDestination(dest)) { + strFailReason = "Invalid Crown address"; + return false; + } + CAmount m_amount = std::max(available, amountin); - if(coin_control.setSelected.size() < 1){ - strFailReason ="No suitable output found"; - return false; - } + CMutableTransaction txNew; + txNew.vin.clear(); + txNew.vout.clear(); + txNew.vpout.clear(); + txNew.witness.SetNull(); + txNew.nLockTime = GetLocktimeForNewTransaction(chain(), GetLastBlockHash(), GetLastBlockHeight()); + txNew.nVersion=chain().getTxVersion(); + + CTxOutAsset txout(Params().GetConsensus().subsidy_asset, m_amount*0.9, GetScriptForDestination(dest)); + CTxOutAsset txfee(Params().GetConsensus().subsidy_asset, m_amount*0.1, CScript()); + assert(txfee.IsFee()); + + txNew.vpout.push_back(txout); + txNew.vpout.push_back(txfee); + + bool pick_new_inputs = true; + CAmountMap mapValueIn; + CAmountMap mapValueToSelect{{asset, m_amount}}; + CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy + + // Choose coins to use + bool bnb_used = false; + if (pick_new_inputs) { + mapValueIn.clear(); + setCoins.clear(); + // If the wallet doesn't know how to sign change output, assume p2sh-p2wpkh + // as lower-bound to allow BnB to do it's thing + + if (!SelectCoins(vecOutputs, mapValueToSelect, setCoins, mapValueIn, coin_control, coin_selection_params, bnb_used)) + { + // If BnB was used, it was the first pass. No longer the first pass and continue loop with knapsack. + if (bnb_used) { + coin_selection_params.use_bnb = false; + } + else { + LogPrintf("Insufficient funds vAvailableCoins %d\n mapValueToSelect %s\n setCoins %d mapValueIn %s", vecOutputs.size(), mapValueToSelect, setCoins.size(), mapValueIn); + strFailReason = "Insufficient funds"; + return false; + } + } + } else { + bnb_used = false; + } - if(available < amountin){ - strFailReason ="Insufficient Funds of this asset"; - return false; - } + // Shuffle selected coins and fill in final vin + txNew.vin.clear(); + std::vector selected_coins(setCoins.begin(), setCoins.end()); + Shuffle(selected_coins.begin(), selected_coins.end(), FastRandomContext()); + // Note how the sequence number is set to non-maxint so that + // the nLockTime set above actually works. + // + // BIP125 defines opt-in RBF as any nSequence < maxint-1, so + // we use the highest possible value in that range (maxint-2) + // to avoid conflicting with other possible uses of nSequence, + // and in the spirit of "smallest possible change from prior + // behavior." + const uint32_t nSequence = coin_control.m_signal_bip125_rbf.value_or(m_signal_rbf) ? MAX_BIP125_RBF_SEQUENCE : (CTxIn::SEQUENCE_FINAL - 1); + for (const auto& coin : selected_coins) { + txNew.vin.push_back(CTxIn(coin.outpoint, CScript(), nSequence)); + } + if (!SignTransaction(txNew)) { + strFailReason = "Signing transaction failed"; + return false; + } + + // Normalize the witness in case it is not serialized before mempool + if (!txNew.HasWitness()) { + txNew.witness.SetNull(); + } + // Return the constructed transaction data. + tx = MakeTransactionRef(std::move(txNew)); + // Limit size + if (GetTransactionWeight(*tx) > MAX_STANDARD_TX_WEIGHT) + { + strFailReason = "Transaction too large"; + return false; + } + } + mapValue_t mapValue; + + CommitTransaction(tx, std::move(mapValue), {} /* orderForm */); + return true; } bool CWallet::CreateAsset(CAsset& asset, CTransactionRef& tx, std::string& assetname, std::string& shortname, CAmount& inputamt, CAmount& outputamt, int64_t& expiry, int& type, CContract& contract, std::string& strFailReason, bool transferable, bool convertable, bool restricted, bool limited, bool divisible) @@ -3584,22 +3674,22 @@ bool CWallet::CreateAsset(CAsset& asset, CTransactionRef& tx, std::string& asset strFailReason = "Asset type is Token but not marked transferable"; return false; } - + if(assetNew.isConvertable()){ strFailReason = "Asset type is Token, Assets conversion is currently disabled"; return false; } - + if(assetNew.isInflatable()){ strFailReason = "Asset type is Token, but marked inflatable"; return false; } - + if(assetNew.isStakeable()){ strFailReason = "Asset type is Token, Stakeable Tokens are disabled."; return false; } - + if(!assetNew.isDivisible()){ strFailReason = "Asset type is Token, but marked indivisible."; return false; @@ -3609,7 +3699,7 @@ bool CWallet::CreateAsset(CAsset& asset, CTransactionRef& tx, std::string& asset if(assetNew.nExpiry != std::numeric_limits::max()){ strFailReason = strprintf("Asset type is Token, but has expiry %d, use other (Point/Credits)", assetNew.nExpiry); return false; - } + } } } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 71ac470..27b2e42 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1068,7 +1068,7 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati */ bool CreateContract(CContract& contract, CTransactionRef& tx, std::string& address, std::string& contract_url, std::string& website_url, std::string& description, CScript& script, std::string& name, std::string& shortname, std::string& strFailReason); bool CreateAsset(CAsset& asset, CTransactionRef& tx, std::string& assetname, std::string& shortname, CAmount& inputamt, CAmount& outputamt, int64_t& expiry, int& type, CContract& contract, std::string& strFailReason, bool transferable = false, bool convertable = false, bool restricted = false, bool limited = false, bool divisible = false); - bool ConvertAsset(CAmountMap &assetin, CTransactionRef& tx, std::string& strFailReason); + bool ConvertAsset(CAmountMap &assetin, CTransactionRef& tx, std::string& address, std::string& strFailReason); void CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector> orderForm); bool DummySignTx(CMutableTransaction &txNew, const std::set &txouts, bool use_max_sig = false) const