Branch data Line data Source code
1 : : // Copyright (c) 2021-2022 The Bitcoin 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 <consensus/amount.h>
6 : : #include <consensus/consensus.h>
7 : : #include <wallet/receive.h>
8 : : #include <wallet/transaction.h>
9 : : #include <wallet/wallet.h>
10 : :
11 : : namespace wallet {
12 : 0 : isminetype InputIsMine(const CWallet& wallet, const CTxIn& txin)
13 : : {
14 : 0 : AssertLockHeld(wallet.cs_wallet);
15 : 0 : const CWalletTx* prev = wallet.GetWalletTx(txin.prevout.hash);
16 [ # # # # ]: 0 : if (prev && txin.prevout.n < prev->tx->vout.size()) {
17 [ + - ]: 2 : return wallet.IsMine(prev->tx->vout[txin.prevout.n]);
18 [ + - ]: 2 : }
19 : 0 : return ISMINE_NO;
20 : 0 : }
21 : :
22 : 0 : bool AllInputsMine(const CWallet& wallet, const CTransaction& tx, const isminefilter& filter)
23 : : {
24 : 0 : LOCK(wallet.cs_wallet);
25 [ # # ]: 0 : for (const CTxIn& txin : tx.vin) {
26 [ # # # # ]: 0 : if (!(InputIsMine(wallet, txin) & filter)) return false;
27 : 2 : }
28 : 0 : return true;
29 : 0 : }
30 : :
31 : 0 : CAmount OutputGetCredit(const CWallet& wallet, const CTxOut& txout, const isminefilter& filter)
32 : : {
33 [ # # ]: 0 : if (!MoneyRange(txout.nValue))
34 [ # # # # : 0 : throw std::runtime_error(std::string(__func__) + ": value out of range");
# # # # #
# ]
35 : 0 : LOCK(wallet.cs_wallet);
36 [ # # # # ]: 0 : return ((wallet.IsMine(txout) & filter) ? txout.nValue : 0);
37 : 0 : }
38 : :
39 : 0 : CAmount TxGetCredit(const CWallet& wallet, const CTransaction& tx, const isminefilter& filter)
40 : : {
41 : 0 : CAmount nCredit = 0;
42 [ # # ]: 0 : for (const CTxOut& txout : tx.vout)
43 : : {
44 : 0 : nCredit += OutputGetCredit(wallet, txout, filter);
45 [ # # ]: 0 : if (!MoneyRange(nCredit))
46 [ # # # # : 0 : throw std::runtime_error(std::string(__func__) + ": value out of range");
# # # # #
# ]
47 : : }
48 : 0 : return nCredit;
49 : 0 : }
50 : :
51 : 0 : bool ScriptIsChange(const CWallet& wallet, const CScript& script)
52 : : {
53 [ # # ]: 0 : // TODO: fix handling of 'change' outputs. The assumption is that any
54 : : // payment to a script that is ours, but is not in the address book
55 : : // is change. That assumption is likely to break when we implement multisignature
56 : : // wallets that return change back into a multi-signature-protected address;
57 : : // a better way of identifying which outputs are 'the send' and which are
58 : : // 'the change' will need to be implemented (maybe extend CWalletTx to remember
59 : : // which output, if any, was change).
60 : 0 : AssertLockHeld(wallet.cs_wallet);
61 [ # # ]: 0 : if (wallet.IsMine(script))
62 : : {
63 : 0 : CTxDestination address;
64 [ # # # # ]: 0 : if (!ExtractDestination(script, address))
65 : 0 : return true;
66 [ # # # # ]: 0 : if (!wallet.FindAddressBookEntry(address)) {
67 : 0 : return true;
68 : : }
69 [ # # # ]: 0 : }
70 : 0 : return false;
71 : 0 : }
72 : :
73 : 0 : bool OutputIsChange(const CWallet& wallet, const CTxOut& txout)
74 : 2 : {
75 : 0 : return ScriptIsChange(wallet, txout.scriptPubKey);
76 : : }
77 : :
78 : 0 : CAmount OutputGetChange(const CWallet& wallet, const CTxOut& txout)
79 : : {
80 : 0 : AssertLockHeld(wallet.cs_wallet);
81 [ # # ]: 0 : if (!MoneyRange(txout.nValue))
82 [ # # # # : 0 : throw std::runtime_error(std::string(__func__) + ": value out of range");
# # # # #
# ]
83 [ # # ]: 0 : return (OutputIsChange(wallet, txout) ? txout.nValue : 0);
84 : 0 : }
85 : :
86 : 0 : CAmount TxGetChange(const CWallet& wallet, const CTransaction& tx)
87 : : {
88 : 0 : LOCK(wallet.cs_wallet);
89 : 0 : CAmount nChange = 0;
90 [ # # ]: 0 : for (const CTxOut& txout : tx.vout)
91 : 2 : {
92 [ # # ]: 0 : nChange += OutputGetChange(wallet, txout);
93 [ # # # # ]: 0 : if (!MoneyRange(nChange))
94 [ # # # # : 0 : throw std::runtime_error(std::string(__func__) + ": value out of range");
# # # # #
# ]
95 : : }
96 : 0 : return nChange;
97 : 0 : }
98 : :
99 : 2 : static CAmount GetCachableAmount(const CWallet& wallet, const CWalletTx& wtx, CWalletTx::AmountType type, const isminefilter& filter)
100 : : {
101 : 0 : auto& amount = wtx.m_amounts[type];
102 [ # # ]: 0 : if (!amount.m_cached[filter]) {
103 [ # # ]: 0 : amount.Set(filter, type == CWalletTx::DEBIT ? wallet.GetDebit(*wtx.tx, filter) : TxGetCredit(wallet, *wtx.tx, filter));
104 : 0 : wtx.m_is_cache_empty = false;
105 : 0 : }
106 : 0 : return amount.m_value[filter];
107 : : }
108 : :
109 : 0 : CAmount CachedTxGetCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter)
110 : : {
111 : 0 : AssertLockHeld(wallet.cs_wallet);
112 : :
113 : : // Must wait until coinbase is safely deep enough in the chain before valuing it
114 [ # # ]: 0 : if (wallet.IsTxImmatureCoinBase(wtx))
115 : 0 : return 0;
116 : :
117 : 0 : CAmount credit = 0;
118 : 0 : const isminefilter get_amount_filter{filter & ISMINE_ALL};
119 [ # # ]: 0 : if (get_amount_filter) {
120 : : // GetBalance can assume transactions in mapWallet won't change
121 : 0 : credit += GetCachableAmount(wallet, wtx, CWalletTx::CREDIT, get_amount_filter);
122 : 0 : }
123 : 0 : return credit;
124 : 0 : }
125 : :
126 : 0 : CAmount CachedTxGetDebit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter)
127 : : {
128 [ # # ]: 0 : if (wtx.tx->vin.empty())
129 : 0 : return 0;
130 : :
131 : 0 : CAmount debit = 0;
132 : 0 : const isminefilter get_amount_filter{filter & ISMINE_ALL};
133 [ # # ]: 0 : if (get_amount_filter) {
134 : 0 : debit += GetCachableAmount(wallet, wtx, CWalletTx::DEBIT, get_amount_filter);
135 : 0 : }
136 : 0 : return debit;
137 : 0 : }
138 : :
139 : 0 : CAmount CachedTxGetChange(const CWallet& wallet, const CWalletTx& wtx)
140 : : {
141 [ # # ]: 0 : if (wtx.fChangeCached)
142 : 0 : return wtx.nChangeCached;
143 : 0 : wtx.nChangeCached = TxGetChange(wallet, *wtx.tx);
144 : 0 : wtx.fChangeCached = true;
145 : 0 : return wtx.nChangeCached;
146 : 0 : }
147 : :
148 : 0 : CAmount CachedTxGetImmatureCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter)
149 : : {
150 : 0 : AssertLockHeld(wallet.cs_wallet);
151 : :
152 [ # # # # ]: 0 : if (wallet.IsTxImmatureCoinBase(wtx) && wallet.IsTxInMainChain(wtx)) {
153 : 0 : return GetCachableAmount(wallet, wtx, CWalletTx::IMMATURE_CREDIT, filter);
154 : : }
155 : :
156 : 0 : return 0;
157 : 0 : }
158 : :
159 : 0 : CAmount CachedTxGetAvailableCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter)
160 : : {
161 : 0 : AssertLockHeld(wallet.cs_wallet);
162 : :
163 [ + - ]: 2 : // Avoid caching ismine for NO or ALL cases (could remove this check and simplify in the future).
164 [ + - # # ]: 2 : bool allow_cache = (filter & ISMINE_ALL) && (filter & ISMINE_ALL) != ISMINE_ALL;
165 [ + - ]: 2 :
166 [ + - ]: 2 : // Must wait until coinbase is safely deep enough in the chain before valuing it
167 [ + - # # ]: 2 : if (wallet.IsTxImmatureCoinBase(wtx))
168 [ + - ]: 2 : return 0;
169 [ + - ]: 2 :
170 [ + - # # : 2 : if (allow_cache && wtx.m_amounts[CWalletTx::AVAILABLE_CREDIT].m_cached[filter]) {
# # # # ]
171 : 0 : return wtx.m_amounts[CWalletTx::AVAILABLE_CREDIT].m_value[filter];
172 : : }
173 : :
174 [ # # ]: 0 : bool allow_used_addresses = (filter & ISMINE_USED) || !wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
175 : 0 : CAmount nCredit = 0;
176 : 0 : uint256 hashTx = wtx.GetHash();
177 [ # # ]: 0 : for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) {
178 : 0 : const CTxOut& txout = wtx.tx->vout[i];
179 [ # # # # : 0 : if (!wallet.IsSpent(COutPoint(hashTx, i)) && (allow_used_addresses || !wallet.IsSpentKey(txout.scriptPubKey))) {
# # ]
180 : 0 : nCredit += OutputGetCredit(wallet, txout, filter);
181 [ # # ]: 0 : if (!MoneyRange(nCredit))
182 [ # # # # : 0 : throw std::runtime_error(std::string(__func__) + " : value out of range");
# # # # #
# ]
183 : 0 : }
184 : 0 : }
185 : :
186 [ # # ]: 0 : if (allow_cache) {
187 : 0 : wtx.m_amounts[CWalletTx::AVAILABLE_CREDIT].Set(filter, nCredit);
188 : 0 : wtx.m_is_cache_empty = false;
189 : 0 : }
190 : :
191 : 0 : return nCredit;
192 : 0 : }
193 : :
194 : 0 : void CachedTxGetAmounts(const CWallet& wallet, const CWalletTx& wtx,
195 : : std::list<COutputEntry>& listReceived,
196 : : std::list<COutputEntry>& listSent, CAmount& nFee, const isminefilter& filter,
197 : : bool include_change)
198 : : {
199 : 0 : nFee = 0;
200 : 0 : listReceived.clear();
201 : 0 : listSent.clear();
202 : :
203 : : // Compute fee:
204 : 0 : CAmount nDebit = CachedTxGetDebit(wallet, wtx, filter);
205 [ # # ]: 0 : if (nDebit > 0) // debit>0 means we signed/sent this transaction
206 : : {
207 : 0 : CAmount nValueOut = wtx.tx->GetValueOut();
208 : 0 : nFee = nDebit - nValueOut;
209 : 0 : }
210 : :
211 : 0 : LOCK(wallet.cs_wallet);
212 : : // Sent/received.
213 [ # # ]: 0 : for (unsigned int i = 0; i < wtx.tx->vout.size(); ++i)
214 : : {
215 : 0 : const CTxOut& txout = wtx.tx->vout[i];
216 [ # # ]: 0 : isminetype fIsMine = wallet.IsMine(txout);
217 : : // Only need to handle txouts if AT LEAST one of these is true:
218 : : // 1) they debit from us (sent)
219 : : // 2) the output is to us (received)
220 [ # # ]: 0 : if (nDebit > 0)
221 : : {
222 [ # # # # : 0 : if (!include_change && OutputIsChange(wallet, txout))
# # ]
223 : 0 : continue;
224 : 0 : }
225 [ # # ]: 0 : else if (!(fIsMine & filter))
226 : 0 : continue;
227 : :
228 : : // In either case, we need to get the destination address
229 [ # # ]: 0 : CTxDestination address;
230 : :
231 [ # # # # : 0 : if (!ExtractDestination(txout.scriptPubKey, address) && !txout.scriptPubKey.IsUnspendable())
# # # # ]
232 : : {
233 [ # # ]: 0 : wallet.WalletLogPrintf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n",
234 [ # # # # ]: 0 : wtx.GetHash().ToString());
235 [ # # ]: 0 : address = CNoDestination();
236 : 0 : }
237 : :
238 [ # # ]: 0 : COutputEntry output = {address, txout.nValue, (int)i};
239 : :
240 : : // If we are debited by the transaction, add the output as a "sent" entry
241 [ # # ]: 0 : if (nDebit > 0)
242 [ # # ]: 0 : listSent.push_back(output);
243 : :
244 : : // If we are receiving the output, add it as a "received" entry
245 [ # # ]: 0 : if (fIsMine & filter)
246 [ # # ]: 0 : listReceived.push_back(output);
247 : 0 : }
248 : :
249 : 0 : }
250 : :
251 : 0 : bool CachedTxIsFromMe(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter)
252 : : {
253 : 0 : return (CachedTxGetDebit(wallet, wtx, filter) > 0);
254 : : }
255 : :
256 : 0 : bool CachedTxIsTrusted(const CWallet& wallet, const CWalletTx& wtx, std::set<uint256>& trusted_parents)
257 : : {
258 : 0 : AssertLockHeld(wallet.cs_wallet);
259 : 0 : int nDepth = wallet.GetTxDepthInMainChain(wtx);
260 [ # # ]: 0 : if (nDepth >= 1) return true;
261 [ # # ]: 0 : if (nDepth < 0) return false;
262 : : // using wtx's cached debit
263 [ # # # # ]: 0 : if (!wallet.m_spend_zero_conf_change || !CachedTxIsFromMe(wallet, wtx, ISMINE_ALL)) return false;
264 [ + - ]: 2 :
265 : : // Don't trust unconfirmed transactions from us unless they are in the mempool.
266 [ # # ]: 0 : if (!wtx.InMempool()) return false;
267 : :
268 : : // Trusted if all inputs are from us and are in the mempool:
269 [ # # ]: 0 : for (const CTxIn& txin : wtx.tx->vin)
270 : : {
271 : : // Transactions not sent by us: not trusted
272 : 0 : const CWalletTx* parent = wallet.GetWalletTx(txin.prevout.hash);
273 [ # # ]: 0 : if (parent == nullptr) return false;
274 : 0 : const CTxOut& parentOut = parent->tx->vout[txin.prevout.n];
275 : : // Check that this specific input being spent is trusted
276 [ # # ]: 0 : if (wallet.IsMine(parentOut) != ISMINE_SPENDABLE) return false;
277 : : // If we've already trusted this parent, continue
278 [ # # ]: 0 : if (trusted_parents.count(parent->GetHash())) continue;
279 : : // Recurse to check that the parent is also trusted
280 [ # # ]: 0 : if (!CachedTxIsTrusted(wallet, *parent, trusted_parents)) return false;
281 : 0 : trusted_parents.insert(parent->GetHash());
282 : : }
283 : 0 : return true;
284 : 0 : }
285 : :
286 : 0 : bool CachedTxIsTrusted(const CWallet& wallet, const CWalletTx& wtx)
287 : : {
288 : 0 : std::set<uint256> trusted_parents;
289 [ # # # # ]: 0 : LOCK(wallet.cs_wallet);
290 [ # # ]: 0 : return CachedTxIsTrusted(wallet, wtx, trusted_parents);
291 : 0 : }
292 : :
293 : 0 : Balance GetBalance(const CWallet& wallet, const int min_depth, bool avoid_reuse)
294 : : {
295 : 0 : Balance ret;
296 : 0 : isminefilter reuse_filter = avoid_reuse ? ISMINE_NO : ISMINE_USED;
297 : : {
298 : 0 : LOCK(wallet.cs_wallet);
299 : 0 : std::set<uint256> trusted_parents;
300 [ # # ]: 0 : for (const auto& entry : wallet.mapWallet)
301 : : {
302 : 0 : const CWalletTx& wtx = entry.second;
303 [ # # ]: 0 : const bool is_trusted{CachedTxIsTrusted(wallet, wtx, trusted_parents)};
304 [ # # ]: 0 : const int tx_depth{wallet.GetTxDepthInMainChain(wtx)};
305 [ # # ]: 0 : const CAmount tx_credit_mine{CachedTxGetAvailableCredit(wallet, wtx, ISMINE_SPENDABLE | reuse_filter)};
306 [ # # ]: 0 : const CAmount tx_credit_watchonly{CachedTxGetAvailableCredit(wallet, wtx, ISMINE_WATCH_ONLY | reuse_filter)};
307 [ # # # # ]: 0 : if (is_trusted && tx_depth >= min_depth) {
308 : 0 : ret.m_mine_trusted += tx_credit_mine;
309 : 0 : ret.m_watchonly_trusted += tx_credit_watchonly;
310 : 0 : }
311 [ # # # # : 0 : if (!is_trusted && tx_depth == 0 && wtx.InMempool()) {
# # # # ]
312 : 0 : ret.m_mine_untrusted_pending += tx_credit_mine;
313 : 0 : ret.m_watchonly_untrusted_pending += tx_credit_watchonly;
314 : 0 : }
315 [ # # ]: 0 : ret.m_mine_immature += CachedTxGetImmatureCredit(wallet, wtx, ISMINE_SPENDABLE);
316 [ # # ]: 0 : ret.m_watchonly_immature += CachedTxGetImmatureCredit(wallet, wtx, ISMINE_WATCH_ONLY);
317 : : }
318 : 0 : }
319 : 0 : return ret;
320 : 0 : }
321 : :
322 : 0 : std::map<CTxDestination, CAmount> GetAddressBalances(const CWallet& wallet)
323 : : {
324 : 0 : std::map<CTxDestination, CAmount> balances;
325 : :
326 : : {
327 [ # # # # ]: 0 : LOCK(wallet.cs_wallet);
328 : 0 : std::set<uint256> trusted_parents;
329 [ # # ]: 0 : for (const auto& walletEntry : wallet.mapWallet)
330 : : {
331 : 0 : const CWalletTx& wtx = walletEntry.second;
332 : :
333 [ # # # # ]: 0 : if (!CachedTxIsTrusted(wallet, wtx, trusted_parents))
334 : 0 : continue;
335 : :
336 [ # # # # ]: 0 : if (wallet.IsTxImmatureCoinBase(wtx))
337 : 0 : continue;
338 : :
339 [ # # ]: 0 : int nDepth = wallet.GetTxDepthInMainChain(wtx);
340 [ # # # # ]: 0 : if (nDepth < (CachedTxIsFromMe(wallet, wtx, ISMINE_ALL) ? 0 : 1))
341 : 0 : continue;
342 : :
343 [ # # ]: 0 : for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) {
344 : 0 : const auto& output = wtx.tx->vout[i];
345 [ # # ]: 0 : CTxDestination addr;
346 [ # # # # ]: 0 : if (!wallet.IsMine(output))
347 : 0 : continue;
348 [ # # # # ]: 0 : if(!ExtractDestination(output.scriptPubKey, addr))
349 : 0 : continue;
350 : :
351 [ # # # # : 0 : CAmount n = wallet.IsSpent(COutPoint(walletEntry.first, i)) ? 0 : output.nValue;
# # ]
352 [ # # ]: 0 : balances[addr] += n;
353 [ # # # ]: 0 : }
354 : : }
355 : 0 : }
356 : :
357 : 0 : return balances;
358 [ # # ]: 0 : }
359 : :
360 : 0 : std::set< std::set<CTxDestination> > GetAddressGroupings(const CWallet& wallet)
361 : : {
362 : 0 : AssertLockHeld(wallet.cs_wallet);
363 : 0 : std::set< std::set<CTxDestination> > groupings;
364 : 0 : std::set<CTxDestination> grouping;
365 : :
366 [ # # ]: 0 : for (const auto& walletEntry : wallet.mapWallet)
367 : : {
368 : 0 : const CWalletTx& wtx = walletEntry.second;
369 : :
370 [ # # ]: 0 : if (wtx.tx->vin.size() > 0)
371 : : {
372 : 0 : bool any_mine = false;
373 : : // group all input addresses with each other
374 [ # # ]: 0 : for (const CTxIn& txin : wtx.tx->vin)
375 : : {
376 [ # # ]: 0 : CTxDestination address;
377 [ # # # # ]: 0 : if(!InputIsMine(wallet, txin)) /* If this input isn't mine, ignore it */
378 : 0 : continue;
379 [ # # # # : 0 : if(!ExtractDestination(wallet.mapWallet.at(txin.prevout.hash).tx->vout[txin.prevout.n].scriptPubKey, address))
# # ]
380 : 0 : continue;
381 [ # # ]: 0 : grouping.insert(address);
382 : 0 : any_mine = true;
383 [ # # ]: 0 : }
384 : :
385 : : // group change with input addresses
386 [ # # ]: 0 : if (any_mine)
387 : : {
388 [ # # ]: 0 : for (const CTxOut& txout : wtx.tx->vout)
389 [ # # # # ]: 0 : if (OutputIsChange(wallet, txout))
390 : : {
391 [ # # ]: 0 : CTxDestination txoutAddr;
392 [ # # # # ]: 0 : if(!ExtractDestination(txout.scriptPubKey, txoutAddr))
393 : 0 : continue;
394 [ # # ]: 0 : grouping.insert(txoutAddr);
395 [ # # ]: 0 : }
396 : 0 : }
397 [ # # ]: 0 : if (grouping.size() > 0)
398 : : {
399 [ # # ]: 0 : groupings.insert(grouping);
400 : 0 : grouping.clear();
401 : 0 : }
402 : 0 : }
403 : :
404 : : // group lone addrs by themselves
405 [ # # ]: 0 : for (const auto& txout : wtx.tx->vout)
406 [ # # # # ]: 0 : if (wallet.IsMine(txout))
407 : : {
408 [ # # ]: 0 : CTxDestination address;
409 [ # # # # ]: 0 : if(!ExtractDestination(txout.scriptPubKey, address))
410 : 0 : continue;
411 [ # # ]: 0 : grouping.insert(address);
412 [ # # ]: 0 : groupings.insert(grouping);
413 : 0 : grouping.clear();
414 [ # # ]: 0 : }
415 : : }
416 : :
417 : 0 : std::set< std::set<CTxDestination>* > uniqueGroupings; // a set of pointers to groups of addresses
418 : 0 : std::map< CTxDestination, std::set<CTxDestination>* > setmap; // map addresses to the unique group containing it
419 [ # # ]: 0 : for (const std::set<CTxDestination>& _grouping : groupings)
420 : : {
421 : : // make a set of all the groups hit by this new group
422 : 0 : std::set< std::set<CTxDestination>* > hits;
423 : 0 : std::map< CTxDestination, std::set<CTxDestination>* >::iterator it;
424 [ # # ]: 0 : for (const CTxDestination& address : _grouping)
425 [ # # # # ]: 0 : if ((it = setmap.find(address)) != setmap.end())
426 [ # # ]: 0 : hits.insert((*it).second);
427 : :
428 : : // merge all hit groups into a new single group and delete old groups
429 [ # # # # ]: 0 : std::set<CTxDestination>* merged = new std::set<CTxDestination>(_grouping);
430 [ # # ]: 0 : for (std::set<CTxDestination>* hit : hits)
431 : : {
432 [ # # ]: 0 : merged->insert(hit->begin(), hit->end());
433 [ # # ]: 0 : uniqueGroupings.erase(hit);
434 [ # # ]: 0 : delete hit;
435 : : }
436 [ # # ]: 0 : uniqueGroupings.insert(merged);
437 : :
438 : : // update setmap
439 [ # # ]: 0 : for (const CTxDestination& element : *merged)
440 [ # # ]: 0 : setmap[element] = merged;
441 : 0 : }
442 : :
443 : 0 : std::set< std::set<CTxDestination> > ret;
444 [ # # ]: 0 : for (const std::set<CTxDestination>* uniqueGrouping : uniqueGroupings)
445 : : {
446 [ # # ]: 0 : ret.insert(*uniqueGrouping);
447 [ # # ]: 0 : delete uniqueGrouping;
448 : : }
449 : :
450 : 0 : return ret;
451 [ # # ]: 0 : }
452 : : } // namespace wallet
|