LCOV - code coverage report
Current view: top level - src/budget - budgetproposal.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 122 159 76.7 %
Date: 2026-08-09 10:51:41 Functions: 26 27 96.3 %

          Line data    Source code
       1             : // Copyright (c) 2014-2015 The Dash developers
       2             : // Copyright (c) 2015-2022 The PIVX Core developers
       3             : // Distributed under the MIT/X11 software license, see the accompanying
       4             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       5             : 
       6             : #include "budget/budgetproposal.h"
       7             : #include "chainparams.h"
       8             : #include "script/standard.h"
       9             : #include "utilstrencodings.h"
      10             : 
      11          46 : CBudgetProposal::CBudgetProposal():
      12             :         nAllotted(0),
      13             :         fValid(true),
      14             :         strInvalid(""),
      15             :         strProposalName("unknown"),
      16             :         strURL(""),
      17             :         nBlockStart(0),
      18             :         nBlockEnd(0),
      19             :         address(),
      20             :         nAmount(0),
      21             :         nFeeTXHash(UINT256_ZERO),
      22          46 :         nTime(0)
      23          46 : {}
      24             : 
      25           6 : CBudgetProposal::CBudgetProposal(const std::string& name,
      26             :                                  const std::string& url,
      27             :                                  int paycount,
      28             :                                  const CScript& payee,
      29             :                                  const CAmount& amount,
      30             :                                  int blockstart,
      31           6 :                                  const uint256& nfeetxhash):
      32             :         nAllotted(0),
      33             :         fValid(true),
      34             :         strInvalid(""),
      35             :         strProposalName(name),
      36             :         strURL(url),
      37             :         nBlockStart(blockstart),
      38             :         address(payee),
      39           6 :         nAmount(amount),
      40             :         nFeeTXHash(nfeetxhash),
      41          18 :         nTime(0)
      42             : {
      43             :     // calculate the expiration block
      44           6 :     nBlockEnd = nBlockStart + (Params().GetConsensus().nBudgetCycleBlocks + 1)  * paycount;
      45           6 : }
      46             : 
      47             : // initialize from network broadcast message
      48          21 : bool CBudgetProposal::ParseBroadcast(CDataStream& broadcast)
      49             : {
      50          21 :     *this = CBudgetProposal();
      51          21 :     try {
      52          21 :         broadcast >> LIMITED_STRING(strProposalName, 20);
      53          21 :         broadcast >> LIMITED_STRING(strURL, 64);
      54          21 :         broadcast >> nTime;
      55          21 :         broadcast >> nBlockStart;
      56          21 :         broadcast >> nBlockEnd;
      57          21 :         broadcast >> nAmount;
      58          21 :         broadcast >> address;
      59          21 :         broadcast >> nFeeTXHash;
      60           0 :     } catch (std::exception& e) {
      61           0 :         return error("Unable to deserialize proposal broadcast: %s", e.what());
      62             :     }
      63             :     return true;
      64             : }
      65             : 
      66         144 : void CBudgetProposal::SyncVotes(CNode* pfrom, bool fPartial, int& nInvCount) const
      67             : {
      68         201 :     for (const auto& it: mapVotes) {
      69          57 :         const CBudgetVote& vote = it.second;
      70          57 :         if (vote.IsValid() && (!fPartial || !vote.IsSynced())) {
      71          53 :             pfrom->PushInventory(CInv(MSG_BUDGET_VOTE, vote.GetHash()));
      72          53 :             nInvCount++;
      73             :         }
      74             :     }
      75         144 : }
      76             : 
      77          20 : bool CBudgetProposal::IsHeavilyDownvoted(int mnCount)
      78             : {
      79          20 :     if (GetNays() - GetYeas() > 3 * mnCount / 10) {
      80           0 :         strInvalid = "Heavily Downvoted";
      81           0 :         return true;
      82             :     }
      83             :     return false;
      84             : }
      85             : 
      86          27 : bool CBudgetProposal::CheckStartEnd()
      87             : {
      88             :     // block start must be a superblock
      89          54 :     if (nBlockStart < 0 ||
      90          27 :             nBlockStart % Params().GetConsensus().nBudgetCycleBlocks != 0) {
      91           0 :         strInvalid = "Invalid nBlockStart";
      92           0 :         return false;
      93             :     }
      94             : 
      95          27 :     if (nBlockEnd < nBlockStart) {
      96           0 :         strInvalid = "Invalid nBlockEnd (end before start)";
      97           0 :         return false;
      98             :     }
      99             : 
     100          27 :     if (GetTotalPaymentCount() > Params().GetConsensus().nMaxProposalPayments) {
     101           0 :         strInvalid = "Invalid payment count";
     102           0 :         return false;
     103             :     }
     104             : 
     105             :     return true;
     106             : }
     107             : 
     108          27 : bool CBudgetProposal::CheckAmount(const CAmount& nTotalBudget)
     109             : {
     110             :     // check minimum amount
     111          27 :     if (nAmount < PROPOSAL_MIN_AMOUNT) {
     112           0 :         strInvalid = "Invalid nAmount (too low)";
     113           0 :         return false;
     114             :     }
     115             : 
     116             :     // check maximum amount
     117             :     // can only pay out 10% of the possible coins (min value of coins)
     118          27 :     if (nAmount > nTotalBudget) {
     119           0 :         strInvalid = "Invalid nAmount (too high)";
     120           0 :         return false;
     121             :     }
     122             : 
     123             :     return true;
     124             : }
     125             : 
     126          27 : bool CBudgetProposal::CheckAddress()
     127             : {
     128             :     // !TODO: There might be an issue with multisig in the coinbase on mainnet
     129             :     // we will add support for it in a future release.
     130          27 :     if (address.IsPayToScriptHash()) {
     131           0 :         strInvalid = "Multisig is not currently supported.";
     132           0 :         return false;
     133             :     }
     134             : 
     135             :     // Check address
     136          54 :     CTxDestination dest;
     137          27 :     if (!ExtractDestination(address, dest, false)) {
     138           0 :         strInvalid = "Invalid script";
     139             :         return false;
     140             :     }
     141          27 :     if (!IsValidDestination(dest)) {
     142          27 :         strInvalid = "Invalid recipient address";
     143             :         return false;
     144             :     }
     145             : 
     146             :     return true;
     147             : }
     148             : 
     149             : /* TODO: Add this to IsWellFormed() for the next hard-fork
     150             :  * This will networkly reject malformed proposal names and URLs
     151             :  */
     152           0 : bool CBudgetProposal::CheckStrings()
     153             : {
     154           0 :     if (strProposalName != SanitizeString(strProposalName)) {
     155           0 :         strInvalid = "Proposal name contains illegal characters.";
     156           0 :         return false;
     157             :     }
     158           0 :     if (strURL != SanitizeString(strURL)) {
     159           0 :         strInvalid = "Proposal URL contains illegal characters.";
     160             :     }
     161             : }
     162             : 
     163          27 : bool CBudgetProposal::IsWellFormed(const CAmount& nTotalBudget)
     164             : {
     165          27 :     return CheckStartEnd() && CheckAmount(nTotalBudget) && CheckAddress();
     166             : }
     167             : 
     168          67 : bool CBudgetProposal::updateExpired(int nCurrentHeight)
     169             : {
     170          67 :     if (IsExpired(nCurrentHeight)) {
     171           0 :         strInvalid = "Proposal expired";
     172           0 :         return true;
     173             :     }
     174             :     return false;
     175             : }
     176             : 
     177          67 : bool CBudgetProposal::UpdateValid(int nCurrentHeight, int mnCount)
     178             : {
     179          67 :     fValid = false;
     180             : 
     181             :     // Never kill a proposal before the first superblock
     182          67 :     if (nCurrentHeight > nBlockStart && IsHeavilyDownvoted(mnCount)) {
     183             :         return false;
     184             :     }
     185             : 
     186          67 :     if (updateExpired(nCurrentHeight)) {
     187             :         return false;
     188             :     }
     189             : 
     190          67 :     fValid = true;
     191          67 :     strInvalid.clear();
     192          67 :     return true;
     193             : }
     194             : 
     195         148 : bool CBudgetProposal::IsEstablished() const
     196             : {
     197         148 :     return nTime < GetAdjustedTime() - Params().GetConsensus().nProposalEstablishmentTime;
     198             : }
     199             : 
     200          77 : bool CBudgetProposal::IsPassing(int nBlockStartBudget, int nBlockEndBudget, int mnCount) const
     201             : {
     202          77 :     if (!fValid)
     203             :         return false;
     204             : 
     205          77 :     if (this->nBlockStart > nBlockStartBudget)
     206             :         return false;
     207             : 
     208          77 :     if (this->nBlockEnd < nBlockEndBudget)
     209             :         return false;
     210             : 
     211          77 :     if (GetYeas() - GetNays() <= mnCount / 10)
     212             :         return false;
     213             : 
     214          16 :     if (!IsEstablished())
     215           0 :         return false;
     216             : 
     217             :     return true;
     218             : }
     219             : 
     220          67 : bool CBudgetProposal::IsExpired(int nCurrentHeight) const
     221             : {
     222          67 :     return nBlockEnd < nCurrentHeight;
     223             : }
     224             : 
     225          17 : bool CBudgetProposal::AddOrUpdateVote(const CBudgetVote& vote, std::string& strError)
     226             : {
     227          34 :     std::string strAction = "New vote inserted:";
     228          34 :     const COutPoint& mnId = vote.GetVin().prevout;
     229          17 :     const int64_t voteTime = vote.GetTime();
     230             : 
     231          34 :     if (mapVotes.count(mnId)) {
     232           0 :         const int64_t& oldTime = mapVotes[mnId].GetTime();
     233           0 :         if (oldTime > voteTime) {
     234           0 :             strError = strprintf("new vote older than existing vote - %s\n", vote.GetHash().ToString());
     235           0 :             LogPrint(BCLog::MNBUDGET, "%s: %s\n", __func__, strError);
     236           0 :             return false;
     237             :         }
     238           0 :         if (voteTime - oldTime < BUDGET_VOTE_UPDATE_MIN) {
     239           0 :             strError = strprintf("time between votes is too soon - %s - %lli sec < %lli sec\n",
     240           0 :                     vote.GetHash().ToString(), voteTime - oldTime, BUDGET_VOTE_UPDATE_MIN);
     241           0 :             LogPrint(BCLog::MNBUDGET, "%s: %s\n", __func__, strError);
     242           0 :             return false;
     243             :         }
     244           0 :         strAction = "Existing vote updated:";
     245             :     }
     246             : 
     247          17 :     mapVotes[mnId] = vote;
     248          34 :     LogPrint(BCLog::MNBUDGET, "%s: %s %s\n", __func__, strAction.c_str(), vote.GetHash().ToString().c_str());
     249             : 
     250             :     return true;
     251             : }
     252             : 
     253           4 : UniValue CBudgetProposal::GetVotesArray() const
     254             : {
     255           4 :     UniValue ret(UniValue::VARR);
     256          12 :     for (const auto& it: mapVotes) {
     257           8 :         ret.push_back(it.second.ToJSON());
     258             :     }
     259           4 :     return ret;
     260             : }
     261             : 
     262          79 : void CBudgetProposal::SetSynced(bool synced)
     263             : {
     264         121 :     for (auto& it: mapVotes) {
     265          42 :         CBudgetVote& vote = it.second;
     266          42 :         if (synced) {
     267          28 :             if (vote.IsValid()) vote.SetSynced(true);
     268             :         } else {
     269          42 :             vote.SetSynced(false);
     270             :         }
     271             :     }
     272          79 : }
     273             : 
     274          55 : double CBudgetProposal::GetRatio() const
     275             : {
     276          55 :     int yeas = GetYeas();
     277          55 :     int nays = GetNays();
     278             : 
     279          55 :     if (yeas + nays == 0) return 0.0f;
     280             : 
     281           7 :     return ((double)(yeas) / (double)(yeas + nays));
     282             : }
     283             : 
     284        3867 : int CBudgetProposal::GetVoteCount(CBudgetVote::VoteDirection vd) const
     285             : {
     286        3867 :     int ret = 0;
     287        4757 :     for (const auto& it : mapVotes) {
     288         890 :         const CBudgetVote& vote = it.second;
     289         890 :         if (vote.GetDirection() == vd && vote.IsValid())
     290         426 :             ret++;
     291             :     }
     292        3867 :     return ret;
     293             : }
     294             : 
     295         192 : int CBudgetProposal::GetBlockStartCycle() const
     296             : {
     297             :     //end block is half way through the next cycle (so the proposal will be removed much after the payment is sent)
     298         192 :     return GetBlockCycle(nBlockStart);
     299             : }
     300             : 
     301         247 : int CBudgetProposal::GetBlockCycle(int nHeight)
     302             : {
     303         247 :     return nHeight - nHeight % Params().GetConsensus().nBudgetCycleBlocks;
     304             : }
     305             : 
     306         247 : int CBudgetProposal::GetBlockEndCycle() const
     307             : {
     308         247 :     return nBlockEnd;
     309             : 
     310             : }
     311             : 
     312         192 : int CBudgetProposal::GetTotalPaymentCount() const
     313             : {
     314         192 :     return (GetBlockEndCycle() - GetBlockStartCycle()) / Params().GetConsensus().nBudgetCycleBlocks;
     315             : }
     316             : 
     317          55 : int CBudgetProposal::GetRemainingPaymentCount(int nCurrentHeight) const
     318             : {
     319             :     // If the proposal is already finished (passed the end block cycle), the payments value will be negative
     320          55 :     int nPayments = (GetBlockEndCycle() - GetBlockCycle(nCurrentHeight)) / Params().GetConsensus().nBudgetCycleBlocks - 1;
     321             :     // Take the lowest value
     322          55 :     return std::min(nPayments, GetTotalPaymentCount());
     323             : }
     324             : 
     325             : // return broadcast serialization
     326          53 : CDataStream CBudgetProposal::GetBroadcast() const
     327             : {
     328          53 :     CDataStream broadcast(SER_NETWORK, PROTOCOL_VERSION);
     329          53 :     broadcast.reserve(1000);
     330          53 :     broadcast << LIMITED_STRING(strProposalName, 20);
     331          53 :     broadcast << LIMITED_STRING(strURL, 64);
     332          53 :     broadcast << nTime;
     333          53 :     broadcast << nBlockStart;
     334          53 :     broadcast << nBlockEnd;
     335          53 :     broadcast << nAmount;
     336          53 :     broadcast << address;
     337          53 :     broadcast << nFeeTXHash;
     338          53 :     return broadcast;
     339             : }
     340             : 
     341          24 : void CBudgetProposal::Relay()
     342             : {
     343          24 :     CInv inv(MSG_BUDGET_PROPOSAL, GetHash());
     344          24 :     g_connman->RelayInv(inv);
     345          24 : }
     346             : 

Generated by: LCOV version 1.14