@keep-network/tbtc-v2 0.1.1-dev.4 → 0.1.1-dev.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.adoc +12 -0
  2. package/artifacts/TBTC.json +19 -18
  3. package/artifacts/TBTCToken.json +19 -18
  4. package/artifacts/VendingMachine.json +20 -19
  5. package/artifacts/solcInputs/f1a50b67569d88ee54efa3e22c6b484e.json +215 -0
  6. package/build/contracts/GovernanceUtils.sol/GovernanceUtils.dbg.json +1 -1
  7. package/build/contracts/GovernanceUtils.sol/GovernanceUtils.json +2 -2
  8. package/build/contracts/bank/Bank.sol/Bank.dbg.json +1 -1
  9. package/build/contracts/bank/Bank.sol/Bank.json +20 -2
  10. package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.dbg.json +4 -0
  11. package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.json +10 -0
  12. package/build/contracts/bridge/Bridge.sol/Bridge.dbg.json +1 -1
  13. package/build/contracts/bridge/Bridge.sol/Bridge.json +1743 -64
  14. package/build/contracts/bridge/BridgeState.sol/BridgeState.dbg.json +4 -0
  15. package/build/contracts/bridge/BridgeState.sol/BridgeState.json +10 -0
  16. package/build/contracts/bridge/Deposit.sol/Deposit.dbg.json +4 -0
  17. package/build/contracts/bridge/Deposit.sol/Deposit.json +72 -0
  18. package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.dbg.json +4 -0
  19. package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.json +10 -0
  20. package/build/contracts/bridge/Frauds.sol/Frauds.dbg.json +4 -0
  21. package/build/contracts/bridge/Frauds.sol/Frauds.json +138 -0
  22. package/build/contracts/bridge/IRelay.sol/IRelay.dbg.json +4 -0
  23. package/build/contracts/bridge/IRelay.sol/IRelay.json +37 -0
  24. package/build/contracts/bridge/MovingFunds.sol/MovingFunds.dbg.json +4 -0
  25. package/build/contracts/bridge/MovingFunds.sol/MovingFunds.json +48 -0
  26. package/build/contracts/bridge/Redeem.sol/OutboundTx.dbg.json +4 -0
  27. package/build/contracts/bridge/Redeem.sol/OutboundTx.json +10 -0
  28. package/build/contracts/bridge/Redeem.sol/Redeem.dbg.json +4 -0
  29. package/build/contracts/bridge/Redeem.sol/Redeem.json +110 -0
  30. package/build/contracts/bridge/Sweep.sol/Sweep.dbg.json +4 -0
  31. package/build/contracts/bridge/Sweep.sol/Sweep.json +30 -0
  32. package/build/contracts/bridge/VendingMachine.sol/VendingMachine.dbg.json +1 -1
  33. package/build/contracts/bridge/VendingMachine.sol/VendingMachine.json +2 -2
  34. package/build/contracts/bridge/Wallets.sol/Wallets.dbg.json +4 -0
  35. package/build/contracts/bridge/Wallets.sol/Wallets.json +138 -0
  36. package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
  37. package/build/contracts/token/TBTC.sol/TBTC.json +2 -2
  38. package/build/contracts/vault/IVault.sol/IVault.dbg.json +1 -1
  39. package/build/contracts/vault/IVault.sol/IVault.json +19 -1
  40. package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +1 -1
  41. package/build/contracts/vault/TBTCVault.sol/TBTCVault.json +36 -18
  42. package/contracts/GovernanceUtils.sol +1 -1
  43. package/contracts/bank/Bank.sol +34 -18
  44. package/contracts/bridge/BitcoinTx.sol +241 -0
  45. package/contracts/bridge/Bridge.sol +980 -123
  46. package/contracts/bridge/BridgeState.sol +172 -0
  47. package/contracts/bridge/Deposit.sol +247 -0
  48. package/contracts/bridge/EcdsaLib.sol +30 -0
  49. package/contracts/bridge/Frauds.sol +529 -0
  50. package/contracts/bridge/IRelay.sol +28 -0
  51. package/contracts/bridge/MovingFunds.sol +280 -0
  52. package/contracts/bridge/Redeem.sol +849 -0
  53. package/contracts/bridge/Sweep.sol +510 -0
  54. package/contracts/bridge/VendingMachine.sol +1 -1
  55. package/contracts/bridge/Wallets.sol +591 -0
  56. package/contracts/token/TBTC.sol +1 -1
  57. package/contracts/vault/IVault.sol +32 -10
  58. package/contracts/vault/TBTCVault.sol +20 -2
  59. package/package.json +28 -24
  60. package/artifacts/solcInputs/d71966212a658480bad5748ad85b1396.json +0 -116
@@ -0,0 +1,510 @@
1
+ // SPDX-License-Identifier: MIT
2
+
3
+ // ██████████████ ▐████▌ ██████████████
4
+ // ██████████████ ▐████▌ ██████████████
5
+ // ▐████▌ ▐████▌
6
+ // ▐████▌ ▐████▌
7
+ // ██████████████ ▐████▌ ██████████████
8
+ // ██████████████ ▐████▌ ██████████████
9
+ // ▐████▌ ▐████▌
10
+ // ▐████▌ ▐████▌
11
+ // ▐████▌ ▐████▌
12
+ // ▐████▌ ▐████▌
13
+ // ▐████▌ ▐████▌
14
+ // ▐████▌ ▐████▌
15
+
16
+ pragma solidity ^0.8.9;
17
+
18
+ import {BTCUtils} from "@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol";
19
+
20
+ import "./BitcoinTx.sol";
21
+ import "./BridgeState.sol";
22
+ import "./Wallets.sol";
23
+
24
+ import "../bank/Bank.sol";
25
+
26
+ library Sweep {
27
+ using BridgeState for BridgeState.Storage;
28
+
29
+ using BTCUtils for bytes;
30
+
31
+ /// @notice Represents an outcome of the sweep Bitcoin transaction
32
+ /// inputs processing.
33
+ struct SweepTxInputsInfo {
34
+ // Sum of all inputs values i.e. all deposits and main UTXO value,
35
+ // if present.
36
+ uint256 inputsTotalValue;
37
+ // Addresses of depositors who performed processed deposits. Ordered in
38
+ // the same order as deposits inputs in the input vector. Size of this
39
+ // array is either equal to the number of inputs (main UTXO doesn't
40
+ // exist) or less by one (main UTXO exists and is pointed by one of
41
+ // the inputs).
42
+ address[] depositors;
43
+ // Amounts of deposits corresponding to processed deposits. Ordered in
44
+ // the same order as deposits inputs in the input vector. Size of this
45
+ // array is either equal to the number of inputs (main UTXO doesn't
46
+ // exist) or less by one (main UTXO exists and is pointed by one of
47
+ // the inputs).
48
+ uint256[] depositedAmounts;
49
+ // Values of the treasury fee corresponding to processed deposits.
50
+ // Ordered in the same order as deposits inputs in the input vector.
51
+ // Size of this array is either equal to the number of inputs (main
52
+ // UTXO doesn't exist) or less by one (main UTXO exists and is pointed
53
+ // by one of the inputs).
54
+ uint256[] treasuryFees;
55
+ }
56
+
57
+ event DepositsSwept(bytes20 walletPubKeyHash, bytes32 sweepTxHash);
58
+
59
+ /// @notice Used by the wallet to prove the BTC deposit sweep transaction
60
+ /// and to update Bank balances accordingly. Sweep is only accepted
61
+ /// if it satisfies SPV proof.
62
+ ///
63
+ /// The function is performing Bank balance updates by first
64
+ /// computing the Bitcoin fee for the sweep transaction. The fee is
65
+ /// divided evenly between all swept deposits. Each depositor
66
+ /// receives a balance in the bank equal to the amount inferred
67
+ /// during the reveal transaction, minus their fee share.
68
+ ///
69
+ /// It is possible to prove the given sweep only one time.
70
+ /// @param sweepTx Bitcoin sweep transaction data
71
+ /// @param sweepProof Bitcoin sweep proof data
72
+ /// @param mainUtxo Data of the wallet's main UTXO, as currently known on
73
+ /// the Ethereum chain. If no main UTXO exists for the given wallet,
74
+ /// this parameter is ignored
75
+ /// @dev Requirements:
76
+ /// - `sweepTx` components must match the expected structure. See
77
+ /// `BitcoinTx.Info` docs for reference. Their values must exactly
78
+ /// correspond to appropriate Bitcoin transaction fields to produce
79
+ /// a provable transaction hash.
80
+ /// - The `sweepTx` should represent a Bitcoin transaction with 1..n
81
+ /// inputs. If the wallet has no main UTXO, all n inputs should
82
+ /// correspond to P2(W)SH revealed deposits UTXOs. If the wallet has
83
+ /// an existing main UTXO, one of the n inputs must point to that
84
+ /// main UTXO and remaining n-1 inputs should correspond to P2(W)SH
85
+ /// revealed deposits UTXOs. That transaction must have only
86
+ /// one P2(W)PKH output locking funds on the 20-byte wallet public
87
+ /// key hash.
88
+ /// - `sweepProof` components must match the expected structure. See
89
+ /// `BitcoinTx.Proof` docs for reference. The `bitcoinHeaders`
90
+ /// field must contain a valid number of block headers, not less
91
+ /// than the `txProofDifficultyFactor` contract constant.
92
+ /// - `mainUtxo` components must point to the recent main UTXO
93
+ /// of the given wallet, as currently known on the Ethereum chain.
94
+ /// If there is no main UTXO, this parameter is ignored.
95
+ function submitSweepProof(
96
+ BridgeState.Storage storage self,
97
+ Wallets.Data storage wallets,
98
+ BitcoinTx.Info calldata sweepTx,
99
+ BitcoinTx.Proof calldata sweepProof,
100
+ BitcoinTx.UTXO calldata mainUtxo
101
+ ) external {
102
+ // TODO: Fail early if the function call gets frontrunned. See discussion:
103
+ // https://github.com/keep-network/tbtc-v2/pull/106#discussion_r801745204
104
+
105
+ // The actual transaction proof is performed here. After that point, we
106
+ // can assume the transaction happened on Bitcoin chain and has
107
+ // a sufficient number of confirmations as determined by
108
+ // `txProofDifficultyFactor` constant.
109
+ bytes32 sweepTxHash = BitcoinTx.validateProof(
110
+ sweepTx,
111
+ sweepProof,
112
+ self.proofDifficultyContext()
113
+ );
114
+
115
+ // Process sweep transaction output and extract its target wallet
116
+ // public key hash and value.
117
+ (
118
+ bytes20 walletPubKeyHash,
119
+ uint64 sweepTxOutputValue
120
+ ) = processSweepTxOutput(sweepTx.outputVector);
121
+
122
+ (
123
+ Wallets.Wallet storage wallet,
124
+ BitcoinTx.UTXO memory resolvedMainUtxo
125
+ ) = resolveSweepingWallet(wallets, walletPubKeyHash, mainUtxo);
126
+
127
+ // Process sweep transaction inputs and extract all information needed
128
+ // to perform deposit bookkeeping.
129
+ SweepTxInputsInfo memory inputsInfo = processSweepTxInputs(
130
+ self,
131
+ sweepTx.inputVector,
132
+ resolvedMainUtxo
133
+ );
134
+
135
+ // Helper variable that will hold the sum of treasury fees paid by
136
+ // all deposits.
137
+ uint256 totalTreasuryFee = 0;
138
+
139
+ // Determine the transaction fee that should be incurred by each deposit
140
+ // and the indivisible remainder that should be additionally incurred
141
+ // by the last deposit.
142
+ (
143
+ uint256 depositTxFee,
144
+ uint256 depositTxFeeRemainder
145
+ ) = sweepTxFeeDistribution(
146
+ inputsInfo.inputsTotalValue,
147
+ sweepTxOutputValue,
148
+ inputsInfo.depositedAmounts.length
149
+ );
150
+
151
+ // Make sure the highest value of the deposit transaction fee does not
152
+ // exceed the maximum value limited by the governable parameter.
153
+ require(
154
+ depositTxFee + depositTxFeeRemainder <= self.depositTxMaxFee,
155
+ "Transaction fee is too high"
156
+ );
157
+
158
+ // Reduce each deposit amount by treasury fee and transaction fee.
159
+ for (uint256 i = 0; i < inputsInfo.depositedAmounts.length; i++) {
160
+ // The last deposit should incur the deposit transaction fee
161
+ // remainder.
162
+ uint256 depositTxFeeIncurred = i ==
163
+ inputsInfo.depositedAmounts.length - 1
164
+ ? depositTxFee + depositTxFeeRemainder
165
+ : depositTxFee;
166
+
167
+ // There is no need to check whether
168
+ // `inputsInfo.depositedAmounts[i] - inputsInfo.treasuryFees[i] - txFee > 0`
169
+ // since the `depositDustThreshold` should force that condition
170
+ // to be always true.
171
+ inputsInfo.depositedAmounts[i] =
172
+ inputsInfo.depositedAmounts[i] -
173
+ inputsInfo.treasuryFees[i] -
174
+ depositTxFeeIncurred;
175
+ totalTreasuryFee += inputsInfo.treasuryFees[i];
176
+ }
177
+
178
+ // Record this sweep data and assign them to the wallet public key hash
179
+ // as new main UTXO. Transaction output index is always 0 as sweep
180
+ // transaction always contains only one output.
181
+ wallet.mainUtxoHash = keccak256(
182
+ abi.encodePacked(sweepTxHash, uint32(0), sweepTxOutputValue)
183
+ );
184
+
185
+ emit DepositsSwept(walletPubKeyHash, sweepTxHash);
186
+
187
+ // Update depositors balances in the Bank.
188
+ self.bank.increaseBalances(
189
+ inputsInfo.depositors,
190
+ inputsInfo.depositedAmounts
191
+ );
192
+ // Pass the treasury fee to the treasury address.
193
+ self.bank.increaseBalance(self.treasury, totalTreasuryFee);
194
+
195
+ // TODO: Handle deposits having `vault` set.
196
+ }
197
+
198
+ /// @notice Resolves sweeping wallet based on the provided wallet public key
199
+ /// hash. Validates the wallet state and current main UTXO, as
200
+ /// currently known on the Ethereum chain.
201
+ /// @param walletPubKeyHash public key hash of the wallet proving the sweep
202
+ /// Bitcoin transaction.
203
+ /// @param mainUtxo Data of the wallet's main UTXO, as currently known on
204
+ /// the Ethereum chain. If no main UTXO exists for the given wallet,
205
+ /// this parameter is ignored
206
+ /// @dev Requirements:
207
+ /// - Sweeping wallet must be either in Live or MovingFunds state.
208
+ /// - If the main UTXO of the sweeping wallet exists in the storage,
209
+ /// the passed `mainUTXO` parameter must be equal to the stored one.
210
+ function resolveSweepingWallet(
211
+ Wallets.Data storage wallets,
212
+ bytes20 walletPubKeyHash,
213
+ BitcoinTx.UTXO calldata mainUtxo
214
+ )
215
+ internal
216
+ returns (
217
+ Wallets.Wallet storage wallet,
218
+ BitcoinTx.UTXO memory resolvedMainUtxo
219
+ )
220
+ {
221
+ wallet = wallets.registeredWallets[walletPubKeyHash];
222
+
223
+ Wallets.WalletState walletState = wallet.state;
224
+ require(
225
+ walletState == Wallets.WalletState.Live ||
226
+ walletState == Wallets.WalletState.MovingFunds,
227
+ "Wallet must be in Live or MovingFunds state"
228
+ );
229
+
230
+ // Check if the main UTXO for given wallet exists. If so, validate
231
+ // passed main UTXO data against the stored hash and use them for
232
+ // further processing. If no main UTXO exists, use empty data.
233
+ resolvedMainUtxo = BitcoinTx.UTXO(bytes32(0), 0, 0);
234
+ bytes32 mainUtxoHash = wallet.mainUtxoHash;
235
+ if (mainUtxoHash != bytes32(0)) {
236
+ require(
237
+ keccak256(
238
+ abi.encodePacked(
239
+ mainUtxo.txHash,
240
+ mainUtxo.txOutputIndex,
241
+ mainUtxo.txOutputValue
242
+ )
243
+ ) == mainUtxoHash,
244
+ "Invalid main UTXO data"
245
+ );
246
+ resolvedMainUtxo = mainUtxo;
247
+ }
248
+ }
249
+
250
+ /// @notice Processes the Bitcoin sweep transaction output vector by
251
+ /// extracting the single output and using it to gain additional
252
+ /// information required for further processing (e.g. value and
253
+ /// wallet public key hash).
254
+ /// @param sweepTxOutputVector Bitcoin sweep transaction output vector.
255
+ /// This function assumes vector's structure is valid so it must be
256
+ /// validated using e.g. `BTCUtils.validateVout` function before
257
+ /// it is passed here
258
+ /// @return walletPubKeyHash 20-byte wallet public key hash.
259
+ /// @return value 8-byte sweep transaction output value.
260
+ function processSweepTxOutput(bytes memory sweepTxOutputVector)
261
+ internal
262
+ pure
263
+ returns (bytes20 walletPubKeyHash, uint64 value)
264
+ {
265
+ // To determine the total number of sweep transaction outputs, we need to
266
+ // parse the compactSize uint (VarInt) the output vector is prepended by.
267
+ // That compactSize uint encodes the number of vector elements using the
268
+ // format presented in:
269
+ // https://developer.bitcoin.org/reference/transactions.html#compactsize-unsigned-integers
270
+ // We don't need asserting the compactSize uint is parseable since it
271
+ // was already checked during `validateVout` validation.
272
+ // See `BitcoinTx.outputVector` docs for more details.
273
+ (, uint256 outputsCount) = sweepTxOutputVector.parseVarInt();
274
+ require(
275
+ outputsCount == 1,
276
+ "Sweep transaction must have a single output"
277
+ );
278
+
279
+ bytes memory output = sweepTxOutputVector.extractOutputAtIndex(0);
280
+ value = output.extractValue();
281
+ bytes memory walletPubKeyHashBytes = output.extractHash();
282
+ // The sweep transaction output should always be P2PKH or P2WPKH.
283
+ // In both cases, the wallet public key hash should be 20 bytes length.
284
+ require(
285
+ walletPubKeyHashBytes.length == 20,
286
+ "Wallet public key hash should have 20 bytes"
287
+ );
288
+ /* solhint-disable-next-line no-inline-assembly */
289
+ assembly {
290
+ walletPubKeyHash := mload(add(walletPubKeyHashBytes, 32))
291
+ }
292
+
293
+ return (walletPubKeyHash, value);
294
+ }
295
+
296
+ /// @notice Processes the Bitcoin sweep transaction input vector. It
297
+ /// extracts each input and tries to obtain associated deposit or
298
+ /// main UTXO data, depending on the input type. Reverts
299
+ /// if one of the inputs cannot be recognized as a pointer to a
300
+ /// revealed deposit or expected main UTXO.
301
+ /// This function also marks each processed deposit as swept.
302
+ /// @param sweepTxInputVector Bitcoin sweep transaction input vector.
303
+ /// This function assumes vector's structure is valid so it must be
304
+ /// validated using e.g. `BTCUtils.validateVin` function before
305
+ /// it is passed here
306
+ /// @param mainUtxo Data of the wallet's main UTXO. If no main UTXO
307
+ /// exists for the given the wallet, this parameter's fields should
308
+ /// be zeroed to bypass the main UTXO validation
309
+ /// @return info Outcomes of the processing.
310
+ function processSweepTxInputs(
311
+ BridgeState.Storage storage self,
312
+ bytes memory sweepTxInputVector,
313
+ BitcoinTx.UTXO memory mainUtxo
314
+ ) internal returns (SweepTxInputsInfo memory info) {
315
+ // If the passed `mainUtxo` parameter's values are zeroed, the main UTXO
316
+ // for the given wallet doesn't exist and it is not expected to be
317
+ // included in the sweep transaction input vector.
318
+ bool mainUtxoExpected = mainUtxo.txHash != bytes32(0);
319
+ bool mainUtxoFound = false;
320
+
321
+ // Determining the total number of sweep transaction inputs in the same
322
+ // way as for number of outputs. See `BitcoinTx.inputVector` docs for
323
+ // more details.
324
+ (
325
+ uint256 inputsCompactSizeUintLength,
326
+ uint256 inputsCount
327
+ ) = sweepTxInputVector.parseVarInt();
328
+
329
+ // To determine the first input starting index, we must jump over
330
+ // the compactSize uint which prepends the input vector. One byte
331
+ // must be added because `BtcUtils.parseVarInt` does not include
332
+ // compactSize uint tag in the returned length.
333
+ //
334
+ // For >= 0 && <= 252, `BTCUtils.determineVarIntDataLengthAt`
335
+ // returns `0`, so we jump over one byte of compactSize uint.
336
+ //
337
+ // For >= 253 && <= 0xffff there is `0xfd` tag,
338
+ // `BTCUtils.determineVarIntDataLengthAt` returns `2` (no
339
+ // tag byte included) so we need to jump over 1+2 bytes of
340
+ // compactSize uint.
341
+ //
342
+ // Please refer `BTCUtils` library and compactSize uint
343
+ // docs in `BitcoinTx` library for more details.
344
+ uint256 inputStartingIndex = 1 + inputsCompactSizeUintLength;
345
+
346
+ // Determine the swept deposits count. If main UTXO is NOT expected,
347
+ // all inputs should be deposits. If main UTXO is expected, one input
348
+ // should point to that main UTXO.
349
+ info.depositors = new address[](
350
+ !mainUtxoExpected ? inputsCount : inputsCount - 1
351
+ );
352
+ info.depositedAmounts = new uint256[](info.depositors.length);
353
+ info.treasuryFees = new uint256[](info.depositors.length);
354
+
355
+ // Initialize helper variables.
356
+ uint256 processedDepositsCount = 0;
357
+
358
+ // Inputs processing loop.
359
+ for (uint256 i = 0; i < inputsCount; i++) {
360
+ (
361
+ bytes32 outpointTxHash,
362
+ uint32 outpointIndex,
363
+ uint256 inputLength
364
+ ) = parseTxInputAt(sweepTxInputVector, inputStartingIndex);
365
+
366
+ Deposit.DepositRequest storage deposit = self.deposits[
367
+ uint256(
368
+ keccak256(abi.encodePacked(outpointTxHash, outpointIndex))
369
+ )
370
+ ];
371
+
372
+ if (deposit.revealedAt != 0) {
373
+ // If we entered here, that means the input was identified as
374
+ // a revealed deposit.
375
+ require(deposit.sweptAt == 0, "Deposit already swept");
376
+
377
+ if (processedDepositsCount == info.depositors.length) {
378
+ // If this condition is true, that means a deposit input
379
+ // took place of an expected main UTXO input.
380
+ // In other words, there is no expected main UTXO
381
+ // input and all inputs come from valid, revealed deposits.
382
+ revert(
383
+ "Expected main UTXO not present in sweep transaction inputs"
384
+ );
385
+ }
386
+
387
+ /* solhint-disable-next-line not-rely-on-time */
388
+ deposit.sweptAt = uint32(block.timestamp);
389
+
390
+ info.depositors[processedDepositsCount] = deposit.depositor;
391
+ info.depositedAmounts[processedDepositsCount] = deposit.amount;
392
+ info.inputsTotalValue += info.depositedAmounts[
393
+ processedDepositsCount
394
+ ];
395
+ info.treasuryFees[processedDepositsCount] = deposit.treasuryFee;
396
+
397
+ processedDepositsCount++;
398
+ } else if (
399
+ mainUtxoExpected != mainUtxoFound &&
400
+ mainUtxo.txHash == outpointTxHash
401
+ ) {
402
+ // If we entered here, that means the input was identified as
403
+ // the expected main UTXO.
404
+ info.inputsTotalValue += mainUtxo.txOutputValue;
405
+ mainUtxoFound = true;
406
+
407
+ // Main UTXO used as an input, mark it as spent.
408
+ self.spentMainUTXOs[
409
+ uint256(
410
+ keccak256(
411
+ abi.encodePacked(outpointTxHash, outpointIndex)
412
+ )
413
+ )
414
+ ] = true;
415
+ } else {
416
+ revert("Unknown input type");
417
+ }
418
+
419
+ // Make the `inputStartingIndex` pointing to the next input by
420
+ // increasing it by current input's length.
421
+ inputStartingIndex += inputLength;
422
+ }
423
+
424
+ // Construction of the input processing loop guarantees that:
425
+ // `processedDepositsCount == info.depositors.length == info.depositedAmounts.length`
426
+ // is always true at this point. We just use the first variable
427
+ // to assert the total count of swept deposit is bigger than zero.
428
+ require(
429
+ processedDepositsCount > 0,
430
+ "Sweep transaction must process at least one deposit"
431
+ );
432
+
433
+ // Assert the main UTXO was used as one of current sweep's inputs if
434
+ // it was actually expected.
435
+ require(
436
+ mainUtxoExpected == mainUtxoFound,
437
+ "Expected main UTXO not present in sweep transaction inputs"
438
+ );
439
+
440
+ return info;
441
+ }
442
+
443
+ /// @notice Parses a Bitcoin transaction input starting at the given index.
444
+ /// @param inputVector Bitcoin transaction input vector
445
+ /// @param inputStartingIndex Index the given input starts at
446
+ /// @return outpointTxHash 32-byte hash of the Bitcoin transaction which is
447
+ /// pointed in the given input's outpoint.
448
+ /// @return outpointIndex 4-byte index of the Bitcoin transaction output
449
+ /// which is pointed in the given input's outpoint.
450
+ /// @return inputLength Byte length of the given input.
451
+ /// @dev This function assumes vector's structure is valid so it must be
452
+ /// validated using e.g. `BTCUtils.validateVin` function before it
453
+ /// is passed here.
454
+ function parseTxInputAt(
455
+ bytes memory inputVector,
456
+ uint256 inputStartingIndex
457
+ )
458
+ internal
459
+ pure
460
+ returns (
461
+ bytes32 outpointTxHash,
462
+ uint32 outpointIndex,
463
+ uint256 inputLength
464
+ )
465
+ {
466
+ outpointTxHash = inputVector.extractInputTxIdLeAt(inputStartingIndex);
467
+
468
+ outpointIndex = BTCUtils.reverseUint32(
469
+ uint32(inputVector.extractTxIndexLeAt(inputStartingIndex))
470
+ );
471
+
472
+ inputLength = inputVector.determineInputLengthAt(inputStartingIndex);
473
+
474
+ return (outpointTxHash, outpointIndex, inputLength);
475
+ }
476
+
477
+ /// @notice Determines the distribution of the sweep transaction fee
478
+ /// over swept deposits.
479
+ /// @param sweepTxInputsTotalValue Total value of all sweep transaction inputs.
480
+ /// @param sweepTxOutputValue Value of the sweep transaction output.
481
+ /// @param depositsCount Count of the deposits swept by the sweep transaction.
482
+ /// @return depositTxFee Transaction fee per deposit determined by evenly
483
+ /// spreading the divisible part of the sweep transaction fee
484
+ /// over all deposits.
485
+ /// @return depositTxFeeRemainder The indivisible part of the sweep
486
+ /// transaction fee than cannot be distributed over all deposits.
487
+ /// @dev It is up to the caller to decide how the remainder should be
488
+ /// counted in. This function only computes its value.
489
+ function sweepTxFeeDistribution(
490
+ uint256 sweepTxInputsTotalValue,
491
+ uint256 sweepTxOutputValue,
492
+ uint256 depositsCount
493
+ )
494
+ internal
495
+ pure
496
+ returns (uint256 depositTxFee, uint256 depositTxFeeRemainder)
497
+ {
498
+ // The sweep transaction fee is just the difference between inputs
499
+ // amounts sum and the output amount.
500
+ uint256 sweepTxFee = sweepTxInputsTotalValue - sweepTxOutputValue;
501
+ // Compute the indivisible remainder that remains after dividing the
502
+ // sweep transaction fee over all deposits evenly.
503
+ depositTxFeeRemainder = sweepTxFee % depositsCount;
504
+ // Compute the transaction fee per deposit by dividing the sweep
505
+ // transaction fee (reduced by the remainder) by the number of deposits.
506
+ depositTxFee = (sweepTxFee - depositTxFeeRemainder) / depositsCount;
507
+
508
+ return (depositTxFee, depositTxFeeRemainder);
509
+ }
510
+ }
@@ -1,6 +1,6 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
 
3
- pragma solidity 0.8.4;
3
+ pragma solidity ^0.8.9;
4
4
 
5
5
  import "@openzeppelin/contracts/access/Ownable.sol";
6
6
  import "@openzeppelin/contracts/token/ERC20/IERC20.sol";