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

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