LCOV - code coverage report
Current view: top level - src/test - evo_assetlocks_tests.cpp (source / functions) Hit Total Coverage
Test: test_dash_coverage.info Lines: 250 250 100.0 %
Date: 2026-06-25 07:23:51 Functions: 20 20 100.0 %

          Line data    Source code
       1             : // Copyright (c) 2023-2025 The Dash Core developers
       2             : // Distributed under the MIT software license, see the accompanying
       3             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4             : 
       5             : #include <test/util/setup_common.h>
       6             : 
       7             : #include <consensus/amount.h>
       8             : #include <consensus/tx_check.h>
       9             : #include <consensus/validation.h>
      10             : #include <evo/assetlocktx.h>
      11             : #include <evo/specialtx.h>
      12             : #include <llmq/context.h>
      13             : #include <policy/settings.h>
      14             : #include <script/script.h>
      15             : #include <script/signingprovider.h>
      16             : #include <util/ranges_set.h>
      17             : #include <validation.h>
      18             : 
      19             : #include <boost/test/unit_test.hpp>
      20             : 
      21             : 
      22             : //
      23             : // Helper: create two dummy transactions, each with
      24             : // two outputs.  The first has 11 and 50 CENT outputs
      25             : // paid to a TX_PUBKEY, the second 21 and 22 CENT outputs
      26             : // paid to a TX_PUBKEYHASH.
      27             : //
      28             : static std::vector<CMutableTransaction>
      29           2 : SetupDummyInputs(FillableSigningProvider& keystoreRet, CCoinsViewCache& coinsRet)
      30             : {
      31           2 :     std::vector<CMutableTransaction> dummyTransactions;
      32           2 :     dummyTransactions.resize(2);
      33             : 
      34             :     // Add some keys to the keystore:
      35           2 :     std::array<CKey, 4> key;
      36             :     {
      37           2 :         bool flip = true;
      38          10 :         for (auto& k : key) {
      39           8 :             k.MakeNewKey(flip);
      40           8 :             keystoreRet.AddKey(k);
      41           8 :             flip = !flip;
      42             :         }
      43             :     }
      44             : 
      45             :     // Create some dummy input transactions
      46           2 :     dummyTransactions[0].vout.resize(2);
      47           2 :     dummyTransactions[0].vout[0].nValue = 11*CENT;
      48           2 :     dummyTransactions[0].vout[0].scriptPubKey = GetScriptForRawPubKey(key[0].GetPubKey());
      49           2 :     dummyTransactions[0].vout[1].nValue = 50*CENT;
      50           2 :     dummyTransactions[0].vout[1].scriptPubKey = GetScriptForRawPubKey(key[1].GetPubKey());
      51           2 :     AddCoins(coinsRet, CTransaction(dummyTransactions[0]), 0);
      52             : 
      53           2 :     dummyTransactions[1].vout.resize(2);
      54           2 :     dummyTransactions[1].vout[0].nValue = 21*CENT;
      55           2 :     dummyTransactions[1].vout[0].scriptPubKey = GetScriptForDestination(PKHash(key[2].GetPubKey()));
      56           2 :     dummyTransactions[1].vout[1].nValue = 22*CENT;
      57           2 :     dummyTransactions[1].vout[1].scriptPubKey = GetScriptForDestination(PKHash(key[3].GetPubKey()));
      58           2 :     AddCoins(coinsRet, CTransaction(dummyTransactions[1]), 0);
      59             : 
      60           2 :     return dummyTransactions;
      61           2 : }
      62             : 
      63           1 : static CMutableTransaction CreateAssetLockTx(FillableSigningProvider& keystore, CCoinsViewCache& coins, CKey& key)
      64             : {
      65           1 :     std::vector<CMutableTransaction> dummyTransactions = SetupDummyInputs(keystore, coins);
      66             : 
      67           1 :     std::vector<CTxOut> creditOutputs(2);
      68           1 :     creditOutputs[0].nValue = 17 * CENT;
      69           1 :     creditOutputs[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey()));
      70           1 :     creditOutputs[1].nValue = 13 * CENT;
      71           1 :     creditOutputs[1].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey()));
      72             : 
      73           1 :     CAssetLockPayload assetLockTx(creditOutputs);
      74             : 
      75           1 :     CMutableTransaction tx;
      76           1 :     tx.nVersion = 3;
      77           1 :     tx.nType = TRANSACTION_ASSET_LOCK;
      78           1 :     SetTxPayload(tx, assetLockTx);
      79             : 
      80           1 :     tx.vin.resize(1);
      81           1 :     tx.vin[0].prevout.hash = dummyTransactions[0].GetHash();
      82           1 :     tx.vin[0].prevout.n = 1;
      83           1 :     tx.vin[0].scriptSig << std::vector<unsigned char>(65, 0);
      84             : 
      85           1 :     tx.vout.resize(2);
      86           1 :     tx.vout[0].nValue = 30 * CENT;
      87           1 :     tx.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("");
      88             : 
      89           1 :     tx.vout[1].nValue = 20 * CENT;
      90           1 :     tx.vout[1].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey()));
      91             : 
      92           1 :     return tx;
      93           1 : }
      94             : 
      95           1 : static CMutableTransaction CreateAssetUnlockTx(FillableSigningProvider& keystore, CKey& key)
      96             : {
      97           1 :     int nVersion = 1;
      98             :     // just a big number bigger than uint32_t
      99           1 :     uint64_t index = 0x001122334455667788L;
     100             :     // big enough to overflow int32_t
     101           1 :     uint32_t fee = 2000'000'000u;
     102             :     // just big enough to overflow uint16_t
     103           1 :     uint32_t requestedHeight = 1000'000;
     104           1 :     uint256 quorumHash;
     105           1 :     CBLSSignature quorumSig;
     106           1 :     CAssetUnlockPayload assetUnlockTx(nVersion, index, fee, requestedHeight, quorumHash, quorumSig);
     107             : 
     108           1 :     CMutableTransaction tx;
     109           1 :     tx.nVersion = 3;
     110           1 :     tx.nType = TRANSACTION_ASSET_UNLOCK;
     111           1 :     SetTxPayload(tx, assetUnlockTx);
     112             : 
     113           1 :     tx.vin.resize(0);
     114             : 
     115           1 :     tx.vout.resize(2);
     116           1 :     tx.vout[0].nValue = 10 * CENT;
     117           1 :     tx.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey()));
     118             : 
     119           1 :     tx.vout[1].nValue = 20 * CENT;
     120           1 :     tx.vout[1].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey()));
     121             : 
     122           1 :     return tx;
     123           1 : }
     124             : 
     125         146 : BOOST_FIXTURE_TEST_SUITE(evo_assetlocks_tests, TestChain100Setup)
     126             : 
     127         149 : BOOST_FIXTURE_TEST_CASE(evo_assetlock, TestChain100Setup)
     128             : {
     129             : 
     130           1 :     LOCK(cs_main);
     131           1 :     FillableSigningProvider keystore;
     132           1 :     CCoinsView coinsDummy;
     133           1 :     CCoinsViewCache coins(&coinsDummy);
     134             : 
     135           1 :     CKey key;
     136           1 :     key.MakeNewKey(true);
     137             : 
     138           1 :     const CTransaction tx{CreateAssetLockTx(keystore, coins, key)};
     139           1 :     std::string reason;
     140           1 :     BOOST_CHECK(IsStandardTx(CTransaction(tx), reason));
     141             : 
     142           1 :     TxValidationState tx_state;
     143           1 :     std::string strTest;
     144           1 :     BOOST_CHECK_MESSAGE(CheckTransaction(CTransaction(tx), tx_state), strTest);
     145           1 :     BOOST_CHECK(tx_state.IsValid());
     146             : 
     147           1 :     BOOST_CHECK(CheckAssetLockTx(CTransaction(tx), tx_state));
     148             : 
     149           1 :     BOOST_CHECK(AreInputsStandard(CTransaction(tx), coins));
     150             : 
     151             :     // Check version
     152             :     {
     153           1 :         BOOST_CHECK(tx.IsSpecialTxVersion());
     154             : 
     155           1 :         const auto opt_payload = GetTxPayload<CAssetLockPayload>(tx);
     156             : 
     157           1 :         BOOST_CHECK(opt_payload.has_value());
     158           1 :         BOOST_CHECK(opt_payload->getVersion() == 1);
     159           1 :     }
     160             : 
     161             :     {
     162             :         // Wrong type "Asset Unlock TX" instead "Asset Lock TX"
     163           1 :         CMutableTransaction txWrongType(tx);
     164           1 :         txWrongType.nType = TRANSACTION_ASSET_UNLOCK;
     165           1 :         BOOST_CHECK(!CheckAssetLockTx(CTransaction(txWrongType), tx_state));
     166           1 :         BOOST_CHECK(tx_state.GetRejectReason() == "bad-assetlocktx-type");
     167           1 :     }
     168             : 
     169             :     {
     170           1 :         CAmount inSum = 0;
     171           2 :         for (const auto& vin : tx.vin) {
     172           1 :             inSum += coins.AccessCoin(vin.prevout).out.nValue;
     173             :         }
     174             : 
     175           1 :         auto outSum = CTransaction(tx).GetValueOut();
     176           1 :         BOOST_CHECK(inSum == outSum);
     177             : 
     178             :         // Outputs should not be bigger than inputs
     179           1 :         CMutableTransaction txBigOutput(tx);
     180           1 :         txBigOutput.vout[0].nValue += 1;
     181           1 :         BOOST_CHECK(!CheckAssetLockTx(CTransaction(txBigOutput), tx_state));
     182           1 :         BOOST_CHECK(tx_state.GetRejectReason() == "bad-assetlocktx-creditamount");
     183             : 
     184             :         // Smaller outputs are allown
     185           1 :         CMutableTransaction txSmallOutput(tx);
     186           1 :         txSmallOutput.vout[1].nValue -= 1;
     187           1 :         BOOST_CHECK(CheckAssetLockTx(CTransaction(txSmallOutput), tx_state));
     188           1 :     }
     189             : 
     190           1 :     const auto assetLockPayload = GetTxPayload<CAssetLockPayload>(tx);
     191           1 :     const std::vector<CTxOut> creditOutputs = assetLockPayload->getCreditOutputs();
     192             : 
     193             :     {
     194             :         // Sum of credit output greater than OP_RETURN
     195           1 :         std::vector<CTxOut> wrongOutput = creditOutputs;
     196           1 :         wrongOutput[0].nValue += CENT;
     197           1 :         CAssetLockPayload greaterCreditsPayload(wrongOutput);
     198             : 
     199           1 :         CMutableTransaction txGreaterCredits(tx);
     200           1 :         SetTxPayload(txGreaterCredits, greaterCreditsPayload);
     201             : 
     202           1 :         BOOST_CHECK(!CheckAssetLockTx(CTransaction(txGreaterCredits), tx_state));
     203           1 :         BOOST_CHECK(tx_state.GetRejectReason() == "bad-assetlocktx-creditamount");
     204             : 
     205             :         // Sum of credit output less than OP_RETURN
     206           1 :         wrongOutput[1].nValue -= 2 * CENT;
     207           1 :         CAssetLockPayload lessCreditsPayload(wrongOutput);
     208             : 
     209           1 :         CMutableTransaction txLessCredits(tx);
     210           1 :         SetTxPayload(txLessCredits, lessCreditsPayload);
     211             : 
     212           1 :         BOOST_CHECK(!CheckAssetLockTx(CTransaction(txLessCredits), tx_state));
     213           1 :         BOOST_CHECK(tx_state.GetRejectReason() == "bad-assetlocktx-creditamount");
     214           1 :     }
     215             : 
     216             :     {
     217             :         // Credit output is out-of-range
     218           1 :         std::vector<CTxOut> creditOutputsOutOfRange = creditOutputs;
     219           1 :         creditOutputsOutOfRange[0].nValue = 0;
     220           1 :         CAssetLockPayload invalidOutputsPayload(creditOutputsOutOfRange);
     221             : 
     222           1 :         CMutableTransaction txInvalidOutputs(tx);
     223           1 :         SetTxPayload(txInvalidOutputs, invalidOutputsPayload);
     224             : 
     225           1 :         BOOST_CHECK(!CheckAssetLockTx(CTransaction(txInvalidOutputs), tx_state));
     226           1 :         BOOST_CHECK(tx_state.GetRejectReason() == "bad-assetlocktx-credit-outofrange");
     227             : 
     228             :         // one of output is out of range
     229           1 :         creditOutputsOutOfRange[0].nValue = MAX_MONEY + 1;
     230           1 :         SetTxPayload(txInvalidOutputs, CAssetLockPayload{creditOutputsOutOfRange});
     231           1 :         BOOST_CHECK(!CheckAssetLockTx(CTransaction(txInvalidOutputs), tx_state));
     232           1 :         BOOST_CHECK(tx_state.GetRejectReason() == "bad-assetlocktx-credit-outofrange");
     233             : 
     234             :         // sum of some of output is out of range
     235           1 :         creditOutputsOutOfRange[0].nValue = MAX_MONEY + 1 - creditOutputsOutOfRange[1].nValue;
     236           1 :         SetTxPayload(txInvalidOutputs, CAssetLockPayload{creditOutputsOutOfRange});
     237           1 :         BOOST_CHECK(!CheckAssetLockTx(CTransaction(txInvalidOutputs), tx_state));
     238           1 :         BOOST_CHECK(tx_state.GetRejectReason() == "bad-assetlocktx-credit-outofrange");
     239             : 
     240           1 :     }
     241             :     {
     242             :         // One credit output keys is not pub key
     243           1 :         std::vector<CTxOut> creditOutputsNotPubkey = creditOutputs;
     244           1 :         creditOutputsNotPubkey[0].scriptPubKey = CScript() << OP_1;
     245           1 :         CAssetLockPayload notPubkeyPayload(creditOutputsNotPubkey);
     246             : 
     247           1 :         CMutableTransaction txNotPubkey(tx);
     248           1 :         SetTxPayload(txNotPubkey, notPubkeyPayload);
     249             : 
     250           1 :         BOOST_CHECK(!CheckAssetLockTx(CTransaction(txNotPubkey), tx_state));
     251           1 :         BOOST_CHECK(tx_state.GetRejectReason() == "bad-assetlocktx-pubKeyHash");
     252             : 
     253           1 :     }
     254             : 
     255             :     {
     256             :         // OP_RETURN must be only one, not more
     257           1 :         CMutableTransaction txMultipleReturn(tx);
     258           1 :         txMultipleReturn.vout[1].scriptPubKey = CScript() << OP_RETURN << ParseHex("");
     259             : 
     260           1 :         BOOST_CHECK(!CheckAssetLockTx(CTransaction(txMultipleReturn), tx_state));
     261           1 :         BOOST_CHECK(tx_state.GetRejectReason() == "bad-assetlocktx-multiple-return");
     262             : 
     263           1 :     }
     264             : 
     265             :     {
     266             :         // zero/negative OP_RETURN
     267           1 :         CMutableTransaction txReturnOutOfRange(tx);
     268           1 :         txReturnOutOfRange.vout[0].nValue = 0;
     269             : 
     270           1 :         BOOST_CHECK(!CheckAssetLockTx(CTransaction(txReturnOutOfRange), tx_state));
     271           1 :         BOOST_CHECK(tx_state.GetRejectReason() == "bad-assetlocktx-opreturn-outofrange");
     272             : 
     273           1 :         txReturnOutOfRange.vout[0].nValue = MAX_MONEY + 1;
     274             : 
     275           1 :         BOOST_CHECK(!CheckAssetLockTx(CTransaction(txReturnOutOfRange), tx_state));
     276           1 :         BOOST_CHECK(tx_state.GetRejectReason() == "bad-assetlocktx-opreturn-outofrange");
     277           1 :     }
     278             : 
     279             : 
     280             :     {
     281             :         // OP_RETURN is missing
     282           1 :         CMutableTransaction txNoReturn(tx);
     283           1 :         txNoReturn.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey()));
     284             : 
     285           1 :         BOOST_CHECK(!CheckAssetLockTx(CTransaction(txNoReturn), tx_state));
     286           1 :         BOOST_CHECK(tx_state.GetRejectReason() == "bad-assetlocktx-no-return");
     287           1 :     }
     288             : 
     289             :     {
     290             :         // OP_RETURN should not have any data
     291           1 :         CMutableTransaction txReturnData(tx);
     292           1 :         txReturnData.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("abcd");
     293             : 
     294           1 :         BOOST_CHECK(!CheckAssetLockTx(CTransaction(txReturnData), tx_state));
     295           1 :         BOOST_CHECK(tx_state.GetRejectReason() == "bad-assetlocktx-non-empty-return");
     296           1 :     }
     297           1 : }
     298             : 
     299         149 : BOOST_FIXTURE_TEST_CASE(evo_assetunlock, TestChain100Setup)
     300             : {
     301           1 :     LOCK(cs_main);
     302           1 :     FillableSigningProvider keystore;
     303             : 
     304           1 :     CKey key;
     305           1 :     key.MakeNewKey(true);
     306             : 
     307           1 :     const CTransaction tx{CreateAssetUnlockTx(keystore, key)};
     308           1 :     std::string reason;
     309           1 :     BOOST_CHECK(IsStandardTx(CTransaction(tx), reason));
     310             : 
     311           1 :     TxValidationState tx_state;
     312           1 :     std::string strTest;
     313           1 :     BOOST_CHECK_MESSAGE(CheckTransaction(CTransaction(tx), tx_state), strTest);
     314           1 :     BOOST_CHECK(tx_state.IsValid());
     315             : 
     316           1 :     auto& blockman = Assert(m_node.chainman)->m_blockman;
     317           1 :     auto& qman = *Assert(m_node.llmq_ctx)->qman;
     318             : 
     319           1 :     const CBlockIndex *block_index = m_node.chainman->ActiveChain().Tip();
     320           1 :     BOOST_CHECK(!CheckAssetUnlockTx(blockman, qman, CTransaction(tx), block_index, std::nullopt, tx_state));
     321           1 :     BOOST_CHECK(tx_state.GetRejectReason() == "bad-assetunlock-quorum-hash");
     322             : 
     323             :     {
     324             :         // Any input should be a reason to fail CheckAssetUnlockTx()
     325           1 :         CCoinsView coinsDummy;
     326           1 :         CCoinsViewCache coins(&coinsDummy);
     327           1 :         std::vector<CMutableTransaction> dummyTransactions = SetupDummyInputs(keystore, coins);
     328             : 
     329           1 :         CMutableTransaction txNonemptyInput(tx);
     330           1 :         txNonemptyInput.vin.resize(1);
     331           1 :         txNonemptyInput.vin[0].prevout.hash = dummyTransactions[0].GetHash();
     332           1 :         txNonemptyInput.vin[0].prevout.n = 1;
     333           1 :         txNonemptyInput.vin[0].scriptSig << std::vector<unsigned char>(65, 0);
     334             : 
     335           1 :         std::string reason;
     336           1 :         BOOST_CHECK(IsStandardTx(CTransaction(tx), reason));
     337             : 
     338           1 :         BOOST_CHECK(!CheckAssetUnlockTx(blockman, qman, CTransaction(txNonemptyInput), block_index, std::nullopt, tx_state));
     339           1 :         BOOST_CHECK(tx_state.GetRejectReason() == "bad-assetunlocktx-have-input");
     340           1 :     }
     341             : 
     342             :     {
     343           1 :         const auto unlockPayload = GetTxPayload<CAssetUnlockPayload>(tx);
     344           1 :         BOOST_CHECK(unlockPayload.has_value());
     345           1 :         BOOST_CHECK(unlockPayload->getVersion() == 1);
     346           1 :         BOOST_CHECK(unlockPayload->getRequestedHeight() == 1000'000);
     347           1 :         BOOST_CHECK(unlockPayload->getFee() == 2000'000'000u);
     348           1 :         BOOST_CHECK(unlockPayload->getIndex() == 0x001122334455667788L);
     349             : 
     350             :         // Wrong type "Asset Lock TX" instead "Asset Unlock TX"
     351           1 :         CMutableTransaction txWrongType(tx);
     352           1 :         txWrongType.nType = TRANSACTION_ASSET_LOCK;
     353           1 :         BOOST_CHECK(!CheckAssetUnlockTx(blockman, qman, CTransaction(txWrongType), block_index, std::nullopt, tx_state));
     354           1 :         BOOST_CHECK(tx_state.GetRejectReason() == "bad-assetunlocktx-type");
     355             : 
     356             :         // Check version of tx and payload
     357           1 :         BOOST_CHECK(tx.IsSpecialTxVersion());
     358           5 :         for (uint8_t payload_version : {0, 1, 2, 255}) {
     359           8 :             CAssetUnlockPayload unlockPayload_tmp{payload_version,
     360           4 :                 unlockPayload->getIndex(),
     361           4 :                 unlockPayload->getFee(),
     362           4 :                 unlockPayload->getRequestedHeight(),
     363           4 :                 unlockPayload->getQuorumHash(),
     364           4 :                 unlockPayload->getQuorumSig()};
     365           4 :             CMutableTransaction txWrongVersion(tx);
     366           4 :             SetTxPayload(txWrongVersion, unlockPayload_tmp);
     367           4 :             if (payload_version != 1) {
     368           3 :                 BOOST_CHECK(!CheckAssetUnlockTx(blockman, qman, CTransaction(txWrongVersion), block_index, std::nullopt, tx_state));
     369           3 :                 BOOST_CHECK(tx_state.GetRejectReason() == "bad-assetunlocktx-version");
     370           3 :             } else {
     371           1 :                 BOOST_CHECK(!CheckAssetUnlockTx(blockman, qman, CTransaction(txWrongVersion), block_index, std::nullopt, tx_state));
     372           1 :                 BOOST_CHECK(tx_state.GetRejectReason() == "bad-assetunlock-quorum-hash");
     373             :             }
     374           4 :         }
     375           1 :     }
     376             : 
     377             :     {
     378             :         // Exactly 32 withdrawal is fine
     379           1 :         CMutableTransaction txManyOutputs(tx);
     380           1 :         int outputsLimit = 32;
     381           1 :         txManyOutputs.vout.resize(outputsLimit);
     382          33 :         for (auto& out : txManyOutputs.vout) {
     383          32 :             out.nValue = CENT;
     384          32 :             out.scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey()));
     385             :         }
     386             : 
     387           1 :         BOOST_CHECK(!CheckAssetUnlockTx(blockman, qman, CTransaction(txManyOutputs), block_index, std::nullopt, tx_state));
     388           1 :         BOOST_CHECK(tx_state.GetRejectReason() == "bad-assetunlock-quorum-hash");
     389             : 
     390             :         // Basic checks for CRangesSet
     391           1 :         CRangesSet indexes;
     392           1 :         BOOST_CHECK(!CheckAssetUnlockTx(blockman, qman, CTransaction(txManyOutputs), block_index, indexes, tx_state));
     393           1 :         BOOST_CHECK(tx_state.GetRejectReason() == "bad-assetunlock-quorum-hash");
     394           1 :         BOOST_CHECK(indexes.Add(0x001122334455667788L));
     395           1 :         BOOST_CHECK(!CheckAssetUnlockTx(blockman, qman, CTransaction(txManyOutputs), block_index, indexes, tx_state));
     396           1 :         BOOST_CHECK(tx_state.GetRejectReason() == "bad-assetunlock-duplicated-index");
     397             : 
     398             : 
     399             :         // Should not be more than 32 withdrawal in one transaction
     400           1 :         txManyOutputs.vout.resize(outputsLimit + 1);
     401           1 :         txManyOutputs.vout.back().nValue = CENT;
     402           1 :         txManyOutputs.vout.back().scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey()));
     403           1 :         BOOST_CHECK(!CheckAssetUnlockTx(blockman, qman, CTransaction(txManyOutputs), block_index, std::nullopt, tx_state));
     404           1 :         BOOST_CHECK(tx_state.GetRejectReason() == "bad-assetunlocktx-too-many-outs");
     405           1 :     }
     406             : 
     407           1 : }
     408             : 
     409         146 : BOOST_AUTO_TEST_SUITE_END()

Generated by: LCOV version 1.16