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()
|