@keep-network/tbtc-v2 0.1.1-dev.6 → 0.1.1-dev.60

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 (95) hide show
  1. package/README.adoc +12 -0
  2. package/artifacts/Bank.json +752 -0
  3. package/artifacts/Bridge.json +3962 -0
  4. package/artifacts/Deposit.json +117 -0
  5. package/artifacts/DepositSweep.json +76 -0
  6. package/artifacts/EcdsaDkgValidator.json +532 -0
  7. package/artifacts/EcdsaInactivity.json +156 -0
  8. package/artifacts/Fraud.json +154 -0
  9. package/artifacts/KeepRegistry.json +99 -0
  10. package/artifacts/KeepStake.json +286 -0
  11. package/artifacts/KeepToken.json +711 -0
  12. package/artifacts/KeepTokenStaking.json +483 -0
  13. package/artifacts/MovingFunds.json +227 -0
  14. package/artifacts/NuCypherStakingEscrow.json +256 -0
  15. package/artifacts/NuCypherToken.json +711 -0
  16. package/artifacts/RandomBeaconStub.json +141 -0
  17. package/artifacts/Redemption.json +162 -0
  18. package/artifacts/ReimbursementPool.json +509 -0
  19. package/artifacts/Relay.json +123 -0
  20. package/artifacts/SortitionPool.json +944 -0
  21. package/artifacts/T.json +1148 -0
  22. package/artifacts/TBTC.json +27 -26
  23. package/artifacts/TBTCToken.json +27 -26
  24. package/artifacts/TokenStaking.json +2288 -0
  25. package/artifacts/TokenholderGovernor.json +1795 -0
  26. package/artifacts/TokenholderTimelock.json +1058 -0
  27. package/artifacts/VendingMachine.json +30 -29
  28. package/artifacts/VendingMachineKeep.json +400 -0
  29. package/artifacts/VendingMachineNuCypher.json +400 -0
  30. package/artifacts/WalletRegistry.json +2709 -0
  31. package/artifacts/WalletRegistryGovernance.json +2364 -0
  32. package/artifacts/Wallets.json +186 -0
  33. package/artifacts/solcInputs/59994c0eff9c9c3b454733a65b82146c.json +218 -0
  34. package/build/contracts/GovernanceUtils.sol/GovernanceUtils.dbg.json +1 -1
  35. package/build/contracts/GovernanceUtils.sol/GovernanceUtils.json +2 -2
  36. package/build/contracts/bank/Bank.sol/Bank.dbg.json +1 -1
  37. package/build/contracts/bank/Bank.sol/Bank.json +20 -2
  38. package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.dbg.json +4 -0
  39. package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.json +10 -0
  40. package/build/contracts/bridge/Bridge.sol/Bridge.dbg.json +1 -1
  41. package/build/contracts/bridge/Bridge.sol/Bridge.json +2470 -132
  42. package/build/contracts/bridge/BridgeState.sol/BridgeState.dbg.json +4 -0
  43. package/build/contracts/bridge/BridgeState.sol/BridgeState.json +220 -0
  44. package/build/contracts/bridge/Deposit.sol/Deposit.dbg.json +4 -0
  45. package/build/contracts/bridge/Deposit.sol/Deposit.json +72 -0
  46. package/build/contracts/bridge/DepositSweep.sol/DepositSweep.dbg.json +4 -0
  47. package/build/contracts/bridge/DepositSweep.sol/DepositSweep.json +30 -0
  48. package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.dbg.json +4 -0
  49. package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.json +10 -0
  50. package/build/contracts/bridge/Fraud.sol/Fraud.dbg.json +4 -0
  51. package/build/contracts/bridge/Fraud.sol/Fraud.json +86 -0
  52. package/build/contracts/bridge/IRelay.sol/IRelay.dbg.json +4 -0
  53. package/build/contracts/bridge/IRelay.sol/IRelay.json +37 -0
  54. package/build/contracts/bridge/MovingFunds.sol/MovingFunds.dbg.json +4 -0
  55. package/build/contracts/bridge/MovingFunds.sol/MovingFunds.json +125 -0
  56. package/build/contracts/bridge/Redemption.sol/OutboundTx.dbg.json +4 -0
  57. package/build/contracts/bridge/Redemption.sol/OutboundTx.json +10 -0
  58. package/build/contracts/bridge/Redemption.sol/Redemption.dbg.json +4 -0
  59. package/build/contracts/bridge/Redemption.sol/Redemption.json +92 -0
  60. package/build/contracts/bridge/VendingMachine.sol/VendingMachine.dbg.json +1 -1
  61. package/build/contracts/bridge/VendingMachine.sol/VendingMachine.json +2 -2
  62. package/build/contracts/bridge/Wallets.sol/Wallets.dbg.json +4 -0
  63. package/build/contracts/bridge/Wallets.sol/Wallets.json +112 -0
  64. package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
  65. package/build/contracts/token/TBTC.sol/TBTC.json +2 -2
  66. package/build/contracts/vault/IVault.sol/IVault.dbg.json +1 -1
  67. package/build/contracts/vault/IVault.sol/IVault.json +19 -1
  68. package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +1 -1
  69. package/build/contracts/vault/TBTCVault.sol/TBTCVault.json +36 -18
  70. package/contracts/GovernanceUtils.sol +1 -1
  71. package/contracts/bank/Bank.sol +34 -18
  72. package/contracts/bridge/BitcoinTx.sol +303 -0
  73. package/contracts/bridge/Bridge.sol +1527 -247
  74. package/contracts/bridge/BridgeState.sol +698 -0
  75. package/contracts/bridge/Deposit.sol +266 -0
  76. package/contracts/bridge/DepositSweep.sol +514 -0
  77. package/contracts/bridge/EcdsaLib.sol +45 -0
  78. package/contracts/bridge/Fraud.sol +508 -0
  79. package/contracts/bridge/IRelay.sol +28 -0
  80. package/contracts/bridge/MovingFunds.sol +1034 -0
  81. package/contracts/bridge/Redemption.sol +868 -0
  82. package/contracts/bridge/VendingMachine.sol +1 -1
  83. package/contracts/bridge/Wallets.sol +550 -0
  84. package/contracts/token/TBTC.sol +1 -1
  85. package/contracts/vault/IVault.sol +32 -10
  86. package/contracts/vault/TBTCVault.sol +20 -2
  87. package/deploy/00_resolve_relay.ts +28 -0
  88. package/deploy/04_deploy_bank.ts +27 -0
  89. package/deploy/05_deploy_bridge.ts +67 -0
  90. package/deploy/06_bank_update_bridge.ts +19 -0
  91. package/deploy/07_transfer_ownership.ts +15 -0
  92. package/deploy/08_transfer_governance.ts +20 -0
  93. package/export.json +15711 -475
  94. package/package.json +27 -24
  95. package/artifacts/solcInputs/c4fd2c31cc58f5fe0cc586dd84a84b60.json +0 -125
@@ -0,0 +1,514 @@
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
+ /// @title Bridge deposit sweep
27
+ /// @notice The library handles the logic for sweeping transactions revealed to
28
+ /// the Bridge
29
+ /// @dev Bridge active wallet periodically signs a transaction that unlocks all
30
+ /// of the valid, revealed deposits above the dust threshold, combines them
31
+ /// into a single UTXO with the existing main wallet UTXO, and relocks
32
+ /// those transactions without a 30-day refund clause to the same wallet.
33
+ /// This has two main effects: it consolidates the UTXO set and it disables
34
+ /// the refund. Balances of depositors in the Bank are increased when the
35
+ /// SPV sweep proof is submitted to the Bridge.
36
+ library DepositSweep {
37
+ using BridgeState for BridgeState.Storage;
38
+ using BitcoinTx for BridgeState.Storage;
39
+
40
+ using BTCUtils for bytes;
41
+
42
+ /// @notice Represents an outcome of the sweep Bitcoin transaction
43
+ /// inputs processing.
44
+ struct DepositSweepTxInputsInfo {
45
+ // Sum of all inputs values i.e. all deposits and main UTXO value,
46
+ // if present.
47
+ uint256 inputsTotalValue;
48
+ // Addresses of depositors who performed processed deposits. Ordered in
49
+ // the same order as deposits inputs in the input vector. Size of this
50
+ // array is either equal to the number of inputs (main UTXO doesn't
51
+ // exist) or less by one (main UTXO exists and is pointed by one of
52
+ // the inputs).
53
+ address[] depositors;
54
+ // Amounts of deposits corresponding to processed deposits. Ordered in
55
+ // the same order as deposits inputs in the input vector. Size of this
56
+ // array is either equal to the number of inputs (main UTXO doesn't
57
+ // exist) or less by one (main UTXO exists and is pointed by one of
58
+ // the inputs).
59
+ uint256[] depositedAmounts;
60
+ // Values of the treasury fee corresponding to processed deposits.
61
+ // Ordered in the same order as deposits inputs in the input vector.
62
+ // Size of this array is either equal to the number of inputs (main
63
+ // UTXO doesn't exist) or less by one (main UTXO exists and is pointed
64
+ // by one of the inputs).
65
+ uint256[] treasuryFees;
66
+ }
67
+
68
+ event DepositsSwept(bytes20 walletPubKeyHash, bytes32 sweepTxHash);
69
+
70
+ /// @notice Used by the wallet to prove the BTC deposit sweep transaction
71
+ /// and to update Bank balances accordingly. Sweep is only accepted
72
+ /// if it satisfies SPV proof.
73
+ ///
74
+ /// The function is performing Bank balance updates by first
75
+ /// computing the Bitcoin fee for the sweep transaction. The fee is
76
+ /// divided evenly between all swept deposits. Each depositor
77
+ /// receives a balance in the bank equal to the amount inferred
78
+ /// during the reveal transaction, minus their fee share.
79
+ ///
80
+ /// It is possible to prove the given sweep only one time.
81
+ /// @param sweepTx Bitcoin sweep transaction data
82
+ /// @param sweepProof Bitcoin sweep proof data
83
+ /// @param mainUtxo Data of the wallet's main UTXO, as currently known on
84
+ /// the Ethereum chain. If no main UTXO exists for the given wallet,
85
+ /// this parameter is ignored
86
+ /// @dev Requirements:
87
+ /// - `sweepTx` components must match the expected structure. See
88
+ /// `BitcoinTx.Info` docs for reference. Their values must exactly
89
+ /// correspond to appropriate Bitcoin transaction fields to produce
90
+ /// a provable transaction hash.
91
+ /// - The `sweepTx` should represent a Bitcoin transaction with 1..n
92
+ /// inputs. If the wallet has no main UTXO, all n inputs should
93
+ /// correspond to P2(W)SH revealed deposits UTXOs. If the wallet has
94
+ /// an existing main UTXO, one of the n inputs must point to that
95
+ /// main UTXO and remaining n-1 inputs should correspond to P2(W)SH
96
+ /// revealed deposits UTXOs. That transaction must have only
97
+ /// one P2(W)PKH output locking funds on the 20-byte wallet public
98
+ /// key hash.
99
+ /// - `sweepProof` components must match the expected structure. See
100
+ /// `BitcoinTx.Proof` docs for reference. The `bitcoinHeaders`
101
+ /// field must contain a valid number of block headers, not less
102
+ /// than the `txProofDifficultyFactor` contract constant.
103
+ /// - `mainUtxo` components must point to the recent main UTXO
104
+ /// of the given wallet, as currently known on the Ethereum chain.
105
+ /// If there is no main UTXO, this parameter is ignored.
106
+ function submitDepositSweepProof(
107
+ BridgeState.Storage storage self,
108
+ BitcoinTx.Info calldata sweepTx,
109
+ BitcoinTx.Proof calldata sweepProof,
110
+ BitcoinTx.UTXO calldata mainUtxo
111
+ ) external {
112
+ // The actual transaction proof is performed here. After that point, we
113
+ // can assume the transaction happened on Bitcoin chain and has
114
+ // a sufficient number of confirmations as determined by
115
+ // `txProofDifficultyFactor` constant.
116
+ bytes32 sweepTxHash = self.validateProof(sweepTx, sweepProof);
117
+
118
+ // Process sweep transaction output and extract its target wallet
119
+ // public key hash and value.
120
+ (
121
+ bytes20 walletPubKeyHash,
122
+ uint64 sweepTxOutputValue
123
+ ) = processDepositSweepTxOutput(self, sweepTx.outputVector);
124
+
125
+ (
126
+ Wallets.Wallet storage wallet,
127
+ BitcoinTx.UTXO memory resolvedMainUtxo
128
+ ) = resolveDepositSweepingWallet(self, walletPubKeyHash, mainUtxo);
129
+
130
+ // Process sweep transaction inputs and extract all information needed
131
+ // to perform deposit bookkeeping.
132
+ DepositSweepTxInputsInfo
133
+ memory inputsInfo = processDepositSweepTxInputs(
134
+ self,
135
+ sweepTx.inputVector,
136
+ resolvedMainUtxo
137
+ );
138
+
139
+ // Helper variable that will hold the sum of treasury fees paid by
140
+ // all deposits.
141
+ uint256 totalTreasuryFee = 0;
142
+
143
+ // Determine the transaction fee that should be incurred by each deposit
144
+ // and the indivisible remainder that should be additionally incurred
145
+ // by the last deposit.
146
+ (
147
+ uint256 depositTxFee,
148
+ uint256 depositTxFeeRemainder
149
+ ) = depositSweepTxFeeDistribution(
150
+ inputsInfo.inputsTotalValue,
151
+ sweepTxOutputValue,
152
+ inputsInfo.depositedAmounts.length
153
+ );
154
+
155
+ // Make sure the highest value of the deposit transaction fee does not
156
+ // exceed the maximum value limited by the governable parameter.
157
+ require(
158
+ depositTxFee + depositTxFeeRemainder <= self.depositTxMaxFee,
159
+ "Transaction fee is too high"
160
+ );
161
+
162
+ // Reduce each deposit amount by treasury fee and transaction fee.
163
+ for (uint256 i = 0; i < inputsInfo.depositedAmounts.length; i++) {
164
+ // The last deposit should incur the deposit transaction fee
165
+ // remainder.
166
+ uint256 depositTxFeeIncurred = i ==
167
+ inputsInfo.depositedAmounts.length - 1
168
+ ? depositTxFee + depositTxFeeRemainder
169
+ : depositTxFee;
170
+
171
+ // There is no need to check whether
172
+ // `inputsInfo.depositedAmounts[i] - inputsInfo.treasuryFees[i] - txFee > 0`
173
+ // since the `depositDustThreshold` should force that condition
174
+ // to be always true.
175
+ inputsInfo.depositedAmounts[i] =
176
+ inputsInfo.depositedAmounts[i] -
177
+ inputsInfo.treasuryFees[i] -
178
+ depositTxFeeIncurred;
179
+ totalTreasuryFee += inputsInfo.treasuryFees[i];
180
+ }
181
+
182
+ // Record this sweep data and assign them to the wallet public key hash
183
+ // as new main UTXO. Transaction output index is always 0 as sweep
184
+ // transaction always contains only one output.
185
+ wallet.mainUtxoHash = keccak256(
186
+ abi.encodePacked(sweepTxHash, uint32(0), sweepTxOutputValue)
187
+ );
188
+
189
+ // slither-disable-next-line reentrancy-events
190
+ emit DepositsSwept(walletPubKeyHash, sweepTxHash);
191
+
192
+ // Update depositors balances in the Bank.
193
+ self.bank.increaseBalances(
194
+ inputsInfo.depositors,
195
+ inputsInfo.depositedAmounts
196
+ );
197
+ // Pass the treasury fee to the treasury address.
198
+ self.bank.increaseBalance(self.treasury, totalTreasuryFee);
199
+
200
+ // TODO: Handle deposits having `vault` set.
201
+ }
202
+
203
+ /// @notice Resolves sweeping wallet based on the provided wallet public key
204
+ /// hash. Validates the wallet state and current main UTXO, as
205
+ /// currently known on the Ethereum chain.
206
+ /// @param walletPubKeyHash public key hash of the wallet proving the sweep
207
+ /// Bitcoin transaction.
208
+ /// @param mainUtxo Data of the wallet's main UTXO, as currently known on
209
+ /// the Ethereum chain. If no main UTXO exists for the given wallet,
210
+ /// this parameter is ignored
211
+ /// @return wallet Data of the sweeping wallet.
212
+ /// @return resolvedMainUtxo The actual main UTXO of the sweeping wallet
213
+ /// resolved by cross-checking the `mainUtxo` parameter with
214
+ /// the chain state. If the validation went well, this is the
215
+ /// plain-text main UTXO corresponding to the `wallet.mainUtxoHash`.
216
+ /// @dev Requirements:
217
+ /// - Sweeping wallet must be either in Live or MovingFunds state.
218
+ /// - If the main UTXO of the sweeping wallet exists in the storage,
219
+ /// the passed `mainUTXO` parameter must be equal to the stored one.
220
+ function resolveDepositSweepingWallet(
221
+ BridgeState.Storage storage self,
222
+ bytes20 walletPubKeyHash,
223
+ BitcoinTx.UTXO calldata mainUtxo
224
+ )
225
+ internal
226
+ view
227
+ returns (
228
+ Wallets.Wallet storage wallet,
229
+ BitcoinTx.UTXO memory resolvedMainUtxo
230
+ )
231
+ {
232
+ wallet = self.registeredWallets[walletPubKeyHash];
233
+
234
+ Wallets.WalletState walletState = wallet.state;
235
+ require(
236
+ walletState == Wallets.WalletState.Live ||
237
+ walletState == Wallets.WalletState.MovingFunds,
238
+ "Wallet must be in Live or MovingFunds state"
239
+ );
240
+
241
+ // Check if the main UTXO for given wallet exists. If so, validate
242
+ // passed main UTXO data against the stored hash and use them for
243
+ // further processing. If no main UTXO exists, use empty data.
244
+ resolvedMainUtxo = BitcoinTx.UTXO(bytes32(0), 0, 0);
245
+ bytes32 mainUtxoHash = wallet.mainUtxoHash;
246
+ if (mainUtxoHash != bytes32(0)) {
247
+ require(
248
+ keccak256(
249
+ abi.encodePacked(
250
+ mainUtxo.txHash,
251
+ mainUtxo.txOutputIndex,
252
+ mainUtxo.txOutputValue
253
+ )
254
+ ) == mainUtxoHash,
255
+ "Invalid main UTXO data"
256
+ );
257
+ resolvedMainUtxo = mainUtxo;
258
+ }
259
+ }
260
+
261
+ /// @notice Processes the Bitcoin sweep transaction output vector by
262
+ /// extracting the single output and using it to gain additional
263
+ /// information required for further processing (e.g. value and
264
+ /// wallet public key hash).
265
+ /// @param sweepTxOutputVector Bitcoin sweep transaction output vector.
266
+ /// This function assumes vector's structure is valid so it must be
267
+ /// validated using e.g. `BTCUtils.validateVout` function before
268
+ /// it is passed here
269
+ /// @return walletPubKeyHash 20-byte wallet public key hash.
270
+ /// @return value 8-byte sweep transaction output value.
271
+ function processDepositSweepTxOutput(
272
+ BridgeState.Storage storage self,
273
+ bytes memory sweepTxOutputVector
274
+ ) internal view returns (bytes20 walletPubKeyHash, uint64 value) {
275
+ // To determine the total number of sweep transaction outputs, we need to
276
+ // parse the compactSize uint (VarInt) the output vector is prepended by.
277
+ // That compactSize uint encodes the number of vector elements using the
278
+ // format presented in:
279
+ // https://developer.bitcoin.org/reference/transactions.html#compactsize-unsigned-integers
280
+ // We don't need asserting the compactSize uint is parseable since it
281
+ // was already checked during `validateVout` validation.
282
+ // See `BitcoinTx.outputVector` docs for more details.
283
+ (, uint256 outputsCount) = sweepTxOutputVector.parseVarInt();
284
+ require(
285
+ outputsCount == 1,
286
+ "Sweep transaction must have a single output"
287
+ );
288
+
289
+ bytes memory output = sweepTxOutputVector.extractOutputAtIndex(0);
290
+ walletPubKeyHash = self.extractPubKeyHash(output);
291
+ value = output.extractValue();
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 processDepositSweepTxInputs(
311
+ BridgeState.Storage storage self,
312
+ bytes memory sweepTxInputVector,
313
+ BitcoinTx.UTXO memory mainUtxo
314
+ ) internal returns (DepositSweepTxInputsInfo 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
+ ) = parseDepositSweepTxInputAt(
365
+ sweepTxInputVector,
366
+ inputStartingIndex
367
+ );
368
+
369
+ Deposit.DepositRequest storage deposit = self.deposits[
370
+ uint256(
371
+ keccak256(abi.encodePacked(outpointTxHash, outpointIndex))
372
+ )
373
+ ];
374
+
375
+ if (deposit.revealedAt != 0) {
376
+ // If we entered here, that means the input was identified as
377
+ // a revealed deposit.
378
+ require(deposit.sweptAt == 0, "Deposit already swept");
379
+
380
+ if (processedDepositsCount == info.depositors.length) {
381
+ // If this condition is true, that means a deposit input
382
+ // took place of an expected main UTXO input.
383
+ // In other words, there is no expected main UTXO
384
+ // input and all inputs come from valid, revealed deposits.
385
+ revert(
386
+ "Expected main UTXO not present in sweep transaction inputs"
387
+ );
388
+ }
389
+
390
+ /* solhint-disable-next-line not-rely-on-time */
391
+ deposit.sweptAt = uint32(block.timestamp);
392
+
393
+ info.depositors[processedDepositsCount] = deposit.depositor;
394
+ info.depositedAmounts[processedDepositsCount] = deposit.amount;
395
+ info.inputsTotalValue += info.depositedAmounts[
396
+ processedDepositsCount
397
+ ];
398
+ info.treasuryFees[processedDepositsCount] = deposit.treasuryFee;
399
+
400
+ processedDepositsCount++;
401
+ } else if (
402
+ mainUtxoExpected != mainUtxoFound &&
403
+ mainUtxo.txHash == outpointTxHash &&
404
+ mainUtxo.txOutputIndex == outpointIndex
405
+ ) {
406
+ // If we entered here, that means the input was identified as
407
+ // the expected main UTXO.
408
+ info.inputsTotalValue += mainUtxo.txOutputValue;
409
+ mainUtxoFound = true;
410
+
411
+ // Main UTXO used as an input, mark it as spent.
412
+ self.spentMainUTXOs[
413
+ uint256(
414
+ keccak256(
415
+ abi.encodePacked(outpointTxHash, outpointIndex)
416
+ )
417
+ )
418
+ ] = true;
419
+ } else {
420
+ revert("Unknown input type");
421
+ }
422
+
423
+ // Make the `inputStartingIndex` pointing to the next input by
424
+ // increasing it by current input's length.
425
+ inputStartingIndex += inputLength;
426
+ }
427
+
428
+ // Construction of the input processing loop guarantees that:
429
+ // `processedDepositsCount == info.depositors.length == info.depositedAmounts.length`
430
+ // is always true at this point. We just use the first variable
431
+ // to assert the total count of swept deposit is bigger than zero.
432
+ require(
433
+ processedDepositsCount > 0,
434
+ "Sweep transaction must process at least one deposit"
435
+ );
436
+
437
+ // Assert the main UTXO was used as one of current sweep's inputs if
438
+ // it was actually expected.
439
+ require(
440
+ mainUtxoExpected == mainUtxoFound,
441
+ "Expected main UTXO not present in sweep transaction inputs"
442
+ );
443
+
444
+ return info;
445
+ }
446
+
447
+ /// @notice Parses a Bitcoin transaction input starting at the given index.
448
+ /// @param inputVector Bitcoin transaction input vector
449
+ /// @param inputStartingIndex Index the given input starts at
450
+ /// @return outpointTxHash 32-byte hash of the Bitcoin transaction which is
451
+ /// pointed in the given input's outpoint.
452
+ /// @return outpointIndex 4-byte index of the Bitcoin transaction output
453
+ /// which is pointed in the given input's outpoint.
454
+ /// @return inputLength Byte length of the given input.
455
+ /// @dev This function assumes vector's structure is valid so it must be
456
+ /// validated using e.g. `BTCUtils.validateVin` function before it
457
+ /// is passed here.
458
+ function parseDepositSweepTxInputAt(
459
+ bytes memory inputVector,
460
+ uint256 inputStartingIndex
461
+ )
462
+ internal
463
+ pure
464
+ returns (
465
+ bytes32 outpointTxHash,
466
+ uint32 outpointIndex,
467
+ uint256 inputLength
468
+ )
469
+ {
470
+ outpointTxHash = inputVector.extractInputTxIdLeAt(inputStartingIndex);
471
+
472
+ outpointIndex = BTCUtils.reverseUint32(
473
+ uint32(inputVector.extractTxIndexLeAt(inputStartingIndex))
474
+ );
475
+
476
+ inputLength = inputVector.determineInputLengthAt(inputStartingIndex);
477
+
478
+ return (outpointTxHash, outpointIndex, inputLength);
479
+ }
480
+
481
+ /// @notice Determines the distribution of the sweep transaction fee
482
+ /// over swept deposits.
483
+ /// @param sweepTxInputsTotalValue Total value of all sweep transaction inputs.
484
+ /// @param sweepTxOutputValue Value of the sweep transaction output.
485
+ /// @param depositsCount Count of the deposits swept by the sweep transaction.
486
+ /// @return depositTxFee Transaction fee per deposit determined by evenly
487
+ /// spreading the divisible part of the sweep transaction fee
488
+ /// over all deposits.
489
+ /// @return depositTxFeeRemainder The indivisible part of the sweep
490
+ /// transaction fee than cannot be distributed over all deposits.
491
+ /// @dev It is up to the caller to decide how the remainder should be
492
+ /// counted in. This function only computes its value.
493
+ function depositSweepTxFeeDistribution(
494
+ uint256 sweepTxInputsTotalValue,
495
+ uint256 sweepTxOutputValue,
496
+ uint256 depositsCount
497
+ )
498
+ internal
499
+ pure
500
+ returns (uint256 depositTxFee, uint256 depositTxFeeRemainder)
501
+ {
502
+ // The sweep transaction fee is just the difference between inputs
503
+ // amounts sum and the output amount.
504
+ uint256 sweepTxFee = sweepTxInputsTotalValue - sweepTxOutputValue;
505
+ // Compute the indivisible remainder that remains after dividing the
506
+ // sweep transaction fee over all deposits evenly.
507
+ depositTxFeeRemainder = sweepTxFee % depositsCount;
508
+ // Compute the transaction fee per deposit by dividing the sweep
509
+ // transaction fee (reduced by the remainder) by the number of deposits.
510
+ depositTxFee = (sweepTxFee - depositTxFeeRemainder) / depositsCount;
511
+
512
+ return (depositTxFee, depositTxFeeRemainder);
513
+ }
514
+ }
@@ -0,0 +1,45 @@
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 "@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol";
19
+
20
+ library EcdsaLib {
21
+ using BytesLib for bytes;
22
+
23
+ /// @notice Converts public key X and Y coordinates (32-byte each) to a
24
+ /// compressed public key (33-byte). Compressed public key is X
25
+ /// coordinate prefixed with `02` or `03` based on the Y coordinate parity.
26
+ /// It is expected that the uncompressed public key is stripped
27
+ /// (i.e. it is not prefixed with `04`).
28
+ /// @param x Wallet's public key's X coordinate.
29
+ /// @param y Wallet's public key's Y coordinate.
30
+ /// @return Compressed public key (33-byte), prefixed with `02` or `03`.
31
+ function compressPublicKey(bytes32 x, bytes32 y)
32
+ internal
33
+ pure
34
+ returns (bytes memory)
35
+ {
36
+ bytes1 prefix;
37
+ if (uint256(y) % 2 == 0) {
38
+ prefix = hex"02";
39
+ } else {
40
+ prefix = hex"03";
41
+ }
42
+
43
+ return bytes.concat(prefix, x);
44
+ }
45
+ }