LCOV - code coverage report
Current view: top level - src/test/librust - transaction_builder_tests.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 187 195 95.9 %
Date: 2025-04-02 01:23:23 Functions: 18 18 100.0 %

          Line data    Source code
       1             : // Copyright (c) 2016-2020 The ZCash developers
       2             : // Copyright (c) 2020-2021 The PIVX Core developers
       3             : // Distributed under the MIT software license, see the accompanying
       4             : // file COPYING or https://www.opensource.org/licenses/mit-license.php.
       5             : 
       6             : #include "test/librust/sapling_test_fixture.h"
       7             : #include "test/librust/utiltest.h"
       8             : 
       9             : #include "sapling/sapling.h"
      10             : #include "sapling/transaction_builder.h"
      11             : #include "sapling/sapling_validation.h"
      12             : 
      13             : #include <univalue.h>
      14             : #include <boost/test/unit_test.hpp>
      15             : 
      16             : BOOST_FIXTURE_TEST_SUITE(sapling_transaction_builder_tests, SaplingRegTestingSetup)
      17             : 
      18           2 : BOOST_AUTO_TEST_CASE(TransparentToSapling)
      19             : {
      20           2 :     auto consensusParams = Params().GetConsensus();
      21             : 
      22           1 :     CBasicKeyStore keystore;
      23           2 :     CKey tsk = AddTestCKeyToKeyStore(keystore);
      24           2 :     auto scriptPubKey = GetScriptForDestination(tsk.GetPubKey().GetID());
      25             : 
      26           1 :     auto sk_from = libzcash::SaplingSpendingKey::random();
      27           1 :     auto fvk_from = sk_from.full_viewing_key();
      28             : 
      29           1 :     auto sk = libzcash::SaplingSpendingKey::random();
      30           1 :     auto fvk = sk.full_viewing_key();
      31           1 :     auto ivk = fvk.in_viewing_key();
      32           1 :     diversifier_t d = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
      33           2 :     auto pk = *ivk.address(d);
      34             : 
      35             :     // Create a shielding transaction from transparent to Sapling
      36             :     // 0.5 t-PIV in, 0.4 shielded-PIV out, 0.1 t-PIV fee
      37           2 :     auto builder = TransactionBuilder(consensusParams, &keystore);
      38           2 :     builder.AddTransparentInput(COutPoint(uint256S("1234"), 0), scriptPubKey, 50000000);
      39           1 :     builder.AddSaplingOutput(fvk_from.ovk, pk, 40000000, {});
      40           1 :     builder.SetFee(10000000);
      41           2 :     auto tx = builder.Build().GetTxOrThrow();
      42             : 
      43           1 :     BOOST_CHECK_EQUAL(tx.vin.size(), 1);
      44           1 :     BOOST_CHECK_EQUAL(tx.vout.size(), 0);
      45           1 :     BOOST_CHECK_EQUAL(tx.sapData->vShieldedSpend.size(), 0);
      46           1 :     BOOST_CHECK_EQUAL(tx.sapData->vShieldedOutput.size(), 1);
      47           1 :     BOOST_CHECK_EQUAL(tx.sapData->valueBalance, -40000000);
      48             : 
      49           2 :     CValidationState state;
      50           2 :     BOOST_CHECK(SaplingValidation::ContextualCheckTransaction(tx, state, Params(), 2, true, false));
      51           2 :     BOOST_CHECK_EQUAL(state.GetRejectReason(), "");
      52           1 : }
      53             : 
      54           2 : BOOST_AUTO_TEST_CASE(SaplingToSapling)
      55             : {
      56           1 :     auto consensusParams = Params().GetConsensus();
      57             : 
      58           1 :     auto sk = libzcash::SaplingSpendingKey::random();
      59           1 :     auto expsk = sk.expanded_spending_key();
      60           1 :     auto fvk = sk.full_viewing_key();
      61           1 :     auto pa = sk.default_address();
      62             : 
      63             :     // Create a Sapling-only transaction
      64             :     // --- 0.4 shielded-PIV in, 0.299 shielded-PIV out, 0.1 shielded-PIV fee, 0.001 shielded-PIV change (added to fee)
      65           2 :     auto testNote = GetTestSaplingNote(pa, 40000000);
      66           2 :     auto builder = TransactionBuilder(consensusParams);
      67           1 :     builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
      68           1 :     builder.SetFee(10000000);
      69             : 
      70             :     // Check that trying to add a different anchor fails
      71             :     // TODO: the following check can be split out in to another test
      72           4 :     BOOST_CHECK_THROW(builder.AddSaplingSpend(expsk, testNote.note, uint256(), testNote.tree.witness()), std::runtime_error);
      73             : 
      74           1 :     builder.AddSaplingOutput(fvk.ovk, pa, 29900000, {});
      75           2 :     auto tx = builder.Build().GetTxOrThrow();
      76             : 
      77           1 :     BOOST_CHECK_EQUAL(tx.vin.size(), 0);
      78           1 :     BOOST_CHECK_EQUAL(tx.vout.size(), 0);
      79           1 :     BOOST_CHECK_EQUAL(tx.sapData->vShieldedSpend.size(), 1);
      80             :     // since the change is below the dust threshold, it is added to the fee
      81           1 :     BOOST_CHECK_EQUAL(tx.sapData->vShieldedOutput.size(), 1);
      82           1 :     BOOST_CHECK_EQUAL(tx.sapData->valueBalance, 10100000);
      83             : 
      84           1 :     CValidationState state;
      85           2 :     BOOST_CHECK(SaplingValidation::ContextualCheckTransaction(tx, state, Params(), 3, true, false));
      86           2 :     BOOST_CHECK_EQUAL(state.GetRejectReason(), "");
      87             : 
      88             :     // --- Now try with 1 shielded-PIV in, 0.5 shielded-PIV out, 0.1 shielded-PIV fee, 0.4 shielded-PIV change
      89           2 :     auto testNote2 = GetTestSaplingNote(pa, 100000000);
      90           2 :     auto builder2 = TransactionBuilder(consensusParams);
      91           1 :     builder2.AddSaplingSpend(expsk, testNote2.note, testNote2.tree.root(), testNote2.tree.witness());
      92           1 :     builder2.SetFee(10000000);
      93           1 :     builder2.AddSaplingOutput(fvk.ovk, pa, 50000000, {});
      94           2 :     auto tx2 = builder2.Build().GetTxOrThrow();
      95           1 :     BOOST_CHECK_EQUAL(tx2.vin.size(), 0);
      96           1 :     BOOST_CHECK_EQUAL(tx2.vout.size(), 0);
      97           1 :     BOOST_CHECK_EQUAL(tx2.sapData->vShieldedSpend.size(), 1);
      98           1 :     BOOST_CHECK_EQUAL(tx2.sapData->vShieldedOutput.size(), 2);
      99           1 :     BOOST_CHECK_EQUAL(tx2.sapData->valueBalance, 10000000);
     100           2 :     BOOST_CHECK(SaplingValidation::ContextualCheckTransaction(tx2, state, Params(), 3, true, false));
     101           2 :     BOOST_CHECK_EQUAL(state.GetRejectReason(), "");
     102           1 : }
     103             : 
     104           2 : BOOST_AUTO_TEST_CASE(ThrowsOnTransparentInputWithoutKeyStore)
     105             : {
     106           2 :     auto builder = TransactionBuilder(Params().GetConsensus());
     107           2 :     BOOST_CHECK_THROW(builder.AddTransparentInput(COutPoint(), CScript(), 1), std::runtime_error);
     108           1 : }
     109             : 
     110           2 : BOOST_AUTO_TEST_CASE(RejectsInvalidTransparentOutput)
     111             : {
     112             :     // Default CTxDestination type is an invalid address
     113           1 :     CTxDestination taddr;
     114           2 :     auto builder = TransactionBuilder(Params().GetConsensus());
     115           2 :     BOOST_CHECK_THROW(builder.AddTransparentOutput(taddr, 50), std::runtime_error);
     116           1 : }
     117             : 
     118           2 : BOOST_AUTO_TEST_CASE(RejectsInvalidTransparentChangeAddress)
     119             : {
     120             :     // Default CTxDestination type is an invalid address
     121           1 :     CTxDestination taddr;
     122           2 :     auto builder = TransactionBuilder(Params().GetConsensus());
     123           2 :     BOOST_CHECK_THROW(builder.SendChangeTo(taddr), std::runtime_error);
     124           1 : }
     125             : 
     126           2 : BOOST_AUTO_TEST_CASE(FailsWithNegativeChange)
     127             : {
     128           2 :     auto consensusParams = Params().GetConsensus();
     129             : 
     130             :     // Generate dummy Sapling address
     131           1 :     auto sk = libzcash::SaplingSpendingKey::random();
     132           1 :     auto expsk = sk.expanded_spending_key();
     133           1 :     auto fvk = sk.full_viewing_key();
     134           1 :     auto pa = sk.default_address();
     135             : 
     136             :     // Set up dummy transparent address
     137           1 :     CBasicKeyStore keystore;
     138           2 :     CKey tsk = AddTestCKeyToKeyStore(keystore);
     139           1 :     auto tkeyid = tsk.GetPubKey().GetID();
     140           2 :     auto scriptPubKey = GetScriptForDestination(tkeyid);
     141           2 :     CTxDestination taddr = tkeyid;
     142             : 
     143             :     // Generate a 0.5 PIV note
     144           2 :     auto testNote = GetTestSaplingNote(pa, 59990000);
     145             : 
     146             :     // Fail if there is only a Sapling output
     147             :     // 0.5 shielded-PIV out, 0.1 t-PIV fee
     148           2 :     auto builder = TransactionBuilder(consensusParams);
     149           1 :     builder.AddSaplingOutput(fvk.ovk, pa, 50000000, {});
     150           1 :     builder.SetFee(10000000);
     151           2 :     BOOST_CHECK_EQUAL("Change cannot be negative", builder.Build().GetError());
     152             : 
     153             :     // Fail if there is only a transparent output
     154             :     // 0.5 t-PIV out, 0.1 t-PIV fee
     155           1 :     builder = TransactionBuilder(consensusParams, &keystore);
     156           1 :     builder.AddTransparentOutput(taddr, 50000000);
     157           1 :     builder.SetFee(10000000);
     158           2 :     BOOST_CHECK_EQUAL("Change cannot be negative", builder.Build().GetError());
     159             : 
     160             :     // Fails if there is insufficient input
     161             :     // 0.5 t-PIV out, 0.1 t-PIV fee, 0.59999 shielded-PIV in
     162           1 :     builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
     163           2 :     BOOST_CHECK_EQUAL("Change cannot be negative", builder.Build().GetError());
     164             : 
     165             :     // Succeeds if there is sufficient input
     166           1 :     builder.AddTransparentInput(COutPoint(), scriptPubKey, 10000);
     167           2 :     BOOST_CHECK(builder.Build().IsTx());
     168           1 : }
     169             : 
     170           2 : BOOST_AUTO_TEST_CASE(ChangeOutput)
     171             : {
     172           1 :     auto consensusParams = Params().GetConsensus();
     173             : 
     174             :     // Generate dummy Sapling address
     175           1 :     auto sk = libzcash::SaplingSpendingKey::random();
     176           1 :     auto expsk = sk.expanded_spending_key();
     177           1 :     auto pa = sk.default_address();
     178             : 
     179           2 :     auto testNote = GetTestSaplingNote(pa, 25000000);
     180             : 
     181             :     // Generate change Sapling address
     182           1 :     auto sk2 = libzcash::SaplingSpendingKey::random();
     183           1 :     auto fvkOut = sk2.full_viewing_key();
     184           1 :     auto zChangeAddr = sk2.default_address();
     185             : 
     186             :     // Set up dummy transparent address
     187           2 :     CBasicKeyStore keystore;
     188           2 :     CKey tsk = AddTestCKeyToKeyStore(keystore);
     189           1 :     auto tkeyid = tsk.GetPubKey().GetID();
     190           2 :     auto scriptPubKey = GetScriptForDestination(tkeyid);
     191           2 :     CTxDestination taddr = tkeyid;
     192             : 
     193             :     // No change address and no Sapling spends
     194           1 :     {
     195           2 :         auto builder = TransactionBuilder(consensusParams, &keystore);
     196           1 :         builder.SetFee(10000000);
     197           1 :         builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000000);
     198           2 :         BOOST_CHECK_EQUAL("Could not determine change address", builder.Build().GetError());
     199             :     }
     200             : 
     201             :     // Change to the same address as the first Sapling spend
     202           1 :     {
     203           2 :         auto builder = TransactionBuilder(consensusParams, &keystore);
     204           1 :         builder.SetFee(10000000);
     205           1 :         builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000000);
     206           1 :         builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
     207           2 :         auto tx = builder.Build().GetTxOrThrow();
     208             : 
     209           1 :         BOOST_CHECK_EQUAL(tx.vin.size(), 1);
     210           1 :         BOOST_CHECK_EQUAL(tx.vout.size(), 0);
     211           1 :         BOOST_CHECK_EQUAL(tx.sapData->vShieldedSpend.size(), 1);
     212           1 :         BOOST_CHECK_EQUAL(tx.sapData->vShieldedOutput.size(), 1);
     213           1 :         BOOST_CHECK_EQUAL(tx.sapData->valueBalance, -15000000);
     214             :     }
     215             : 
     216             :     // Change to a Sapling address
     217           1 :     {
     218           2 :         auto builder = TransactionBuilder(consensusParams, &keystore);
     219           1 :         builder.SetFee(10000000);
     220           1 :         builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000000);
     221           1 :         builder.SendChangeTo(zChangeAddr, fvkOut.ovk);
     222           2 :         auto tx = builder.Build().GetTxOrThrow();
     223             : 
     224           1 :         BOOST_CHECK_EQUAL(tx.vin.size(), 1);
     225           1 :         BOOST_CHECK_EQUAL(tx.vout.size(), 0);
     226           1 :         BOOST_CHECK_EQUAL(tx.sapData->vShieldedSpend.size(), 0);
     227           1 :         BOOST_CHECK_EQUAL(tx.sapData->vShieldedOutput.size(), 1);
     228           1 :         BOOST_CHECK_EQUAL(tx.sapData->valueBalance, -15000000);
     229             :     }
     230             : 
     231             :     // Change to a transparent address
     232           1 :     {
     233           2 :         auto builder = TransactionBuilder(consensusParams, &keystore);
     234           1 :         builder.SetFee(10000000);
     235           1 :         builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000000);
     236           1 :         builder.SendChangeTo(taddr);
     237           2 :         auto tx = builder.Build().GetTxOrThrow();
     238             : 
     239           1 :         BOOST_CHECK_EQUAL(tx.vin.size(), 1);
     240           1 :         BOOST_CHECK_EQUAL(tx.vout.size(), 1);
     241           1 :         BOOST_CHECK_EQUAL(tx.sapData->vShieldedSpend.size(), 0);
     242           1 :         BOOST_CHECK_EQUAL(tx.sapData->vShieldedOutput.size(), 0);
     243           1 :         BOOST_CHECK_EQUAL(tx.sapData->valueBalance, 0);
     244           1 :         BOOST_CHECK_EQUAL(tx.vout[0].nValue, 15000000);
     245             :     }
     246           1 : }
     247             : 
     248           2 : BOOST_AUTO_TEST_CASE(SetFee)
     249             : {
     250           1 :     auto consensusParams = Params().GetConsensus();
     251             : 
     252             :     // Generate dummy Sapling address
     253           1 :     auto sk = libzcash::SaplingSpendingKey::random();
     254           1 :     auto expsk = sk.expanded_spending_key();
     255           1 :     auto fvk = sk.full_viewing_key();
     256           1 :     auto pa = sk.default_address();
     257             : 
     258           2 :     auto testNote = GetTestSaplingNote(pa, 5 * COIN);
     259             : 
     260             :     // Configured fee (auto with SaplingOperation)
     261           1 :     {
     262           2 :         auto builder = TransactionBuilder(consensusParams);
     263           1 :         builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
     264           1 :         builder.AddSaplingOutput(fvk.ovk, pa, 3 * COIN, {});
     265             : 
     266           1 :         builder.SetFee(COIN);
     267           2 :         auto tx = builder.Build().GetTxOrThrow();
     268             : 
     269           1 :         BOOST_CHECK_EQUAL(tx.vin.size(), 0);
     270           1 :         BOOST_CHECK_EQUAL(tx.vout.size(), 0);
     271           1 :         BOOST_CHECK_EQUAL(tx.sapData->vShieldedSpend.size(), 1);
     272           1 :         BOOST_CHECK_EQUAL(tx.sapData->vShieldedOutput.size(), 2);
     273           1 :         BOOST_CHECK_EQUAL(tx.sapData->valueBalance, COIN);
     274             :     }
     275           1 : }
     276             : 
     277           2 : BOOST_AUTO_TEST_CASE(CheckSaplingTxVersion)
     278             : {
     279           2 :     auto consensusParams = Params().GetConsensus();
     280             : 
     281           1 :     auto sk = libzcash::SaplingSpendingKey::random();
     282           1 :     auto expsk = sk.expanded_spending_key();
     283           1 :     auto pk = sk.default_address();
     284             : 
     285             :     // Cannot add Sapling outputs to a non-Sapling transaction
     286           1 :     auto builder = TransactionBuilder(consensusParams);
     287           1 :     builder.SetFee(10000000);
     288           1 :     try {
     289           2 :         builder.AddSaplingOutput(uint256(), pk, 12345, {});
     290           0 :     } catch (std::runtime_error const & err) {
     291           0 :         BOOST_CHECK_EQUAL(err.what(), std::string("TransactionBuilder cannot add Sapling output to pre-Sapling transaction"));
     292           0 :     } catch(...) {
     293           0 :         BOOST_CHECK_MESSAGE(false, "Expected std::runtime_error");
     294             :     }
     295             : 
     296             :     // Cannot add Sapling spends to a non-Sapling transaction
     297           2 :     libzcash::SaplingNote note(pk, 50000);
     298           2 :     SaplingMerkleTree tree;
     299           1 :     try {
     300           2 :         builder.AddSaplingSpend(expsk, note, uint256(), tree.witness());
     301           0 :     } catch (std::runtime_error const & err) {
     302           0 :         BOOST_CHECK_EQUAL(err.what(), std::string("TransactionBuilder cannot add Sapling spend to pre-Sapling transaction"));
     303           0 :     } catch(...) {
     304           0 :         BOOST_CHECK_MESSAGE(false, "Expected std::runtime_error");
     305             :     }
     306           1 : }
     307             : 
     308             : 
     309             : BOOST_AUTO_TEST_SUITE_END()

Generated by: LCOV version 1.14