@keep-network/tbtc-v2 0.1.1-dev.8 → 0.1.1-dev.82

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 (112) hide show
  1. package/README.adoc +12 -0
  2. package/artifacts/Bank.json +817 -0
  3. package/artifacts/Bridge.json +2644 -0
  4. package/artifacts/Deposit.json +117 -0
  5. package/artifacts/DepositSweep.json +77 -0
  6. package/artifacts/EcdsaDkgValidator.json +532 -0
  7. package/artifacts/EcdsaInactivity.json +156 -0
  8. package/artifacts/EcdsaSortitionPool.json +1004 -0
  9. package/artifacts/Fraud.json +164 -0
  10. package/artifacts/KeepRegistry.json +99 -0
  11. package/artifacts/KeepStake.json +286 -0
  12. package/artifacts/KeepToken.json +711 -0
  13. package/artifacts/KeepTokenStaking.json +483 -0
  14. package/artifacts/MovingFunds.json +249 -0
  15. package/artifacts/NuCypherStakingEscrow.json +256 -0
  16. package/artifacts/NuCypherToken.json +711 -0
  17. package/artifacts/RandomBeaconStub.json +141 -0
  18. package/artifacts/Redemption.json +162 -0
  19. package/artifacts/ReimbursementPool.json +509 -0
  20. package/artifacts/Relay.json +123 -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/TBTCVault.json +462 -0
  25. package/artifacts/TokenStaking.json +2288 -0
  26. package/artifacts/TokenholderGovernor.json +1795 -0
  27. package/artifacts/TokenholderTimelock.json +1058 -0
  28. package/artifacts/VendingMachine.json +30 -29
  29. package/artifacts/VendingMachineKeep.json +400 -0
  30. package/artifacts/VendingMachineNuCypher.json +400 -0
  31. package/artifacts/WalletRegistry.json +1843 -0
  32. package/artifacts/WalletRegistryGovernance.json +2754 -0
  33. package/artifacts/Wallets.json +186 -0
  34. package/artifacts/solcInputs/5cd0a97e230d515eacf46fb60ea8963a.json +311 -0
  35. package/build/contracts/GovernanceUtils.sol/GovernanceUtils.dbg.json +1 -1
  36. package/build/contracts/GovernanceUtils.sol/GovernanceUtils.json +2 -2
  37. package/build/contracts/bank/Bank.sol/Bank.dbg.json +1 -1
  38. package/build/contracts/bank/Bank.sol/Bank.json +27 -4
  39. package/build/contracts/bank/IReceiveBalanceApproval.sol/IReceiveBalanceApproval.dbg.json +4 -0
  40. package/build/contracts/bank/IReceiveBalanceApproval.sol/IReceiveBalanceApproval.json +34 -0
  41. package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.dbg.json +1 -1
  42. package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.json +2 -2
  43. package/build/contracts/bridge/Bridge.sol/Bridge.dbg.json +1 -1
  44. package/build/contracts/bridge/Bridge.sol/Bridge.json +2516 -196
  45. package/build/contracts/bridge/BridgeState.sol/BridgeState.dbg.json +4 -0
  46. package/build/contracts/bridge/BridgeState.sol/BridgeState.json +226 -0
  47. package/build/contracts/bridge/Deposit.sol/Deposit.dbg.json +4 -0
  48. package/build/contracts/bridge/Deposit.sol/Deposit.json +72 -0
  49. package/build/contracts/bridge/DepositSweep.sol/DepositSweep.dbg.json +4 -0
  50. package/build/contracts/bridge/DepositSweep.sol/DepositSweep.json +30 -0
  51. package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.dbg.json +4 -0
  52. package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.json +10 -0
  53. package/build/contracts/bridge/Fraud.sol/Fraud.dbg.json +4 -0
  54. package/build/contracts/bridge/Fraud.sol/Fraud.json +86 -0
  55. package/build/contracts/bridge/Heartbeat.sol/Heartbeat.dbg.json +4 -0
  56. package/build/contracts/bridge/Heartbeat.sol/Heartbeat.json +10 -0
  57. package/build/contracts/bridge/IRelay.sol/IRelay.dbg.json +4 -0
  58. package/build/contracts/bridge/IRelay.sol/IRelay.json +37 -0
  59. package/build/contracts/bridge/MovingFunds.sol/MovingFunds.dbg.json +4 -0
  60. package/build/contracts/bridge/MovingFunds.sol/MovingFunds.json +138 -0
  61. package/build/contracts/bridge/Redemption.sol/OutboundTx.dbg.json +4 -0
  62. package/build/contracts/bridge/Redemption.sol/OutboundTx.json +10 -0
  63. package/build/contracts/bridge/Redemption.sol/Redemption.dbg.json +4 -0
  64. package/build/contracts/bridge/Redemption.sol/Redemption.json +92 -0
  65. package/build/contracts/bridge/VendingMachine.sol/VendingMachine.dbg.json +1 -1
  66. package/build/contracts/bridge/VendingMachine.sol/VendingMachine.json +2 -2
  67. package/build/contracts/bridge/Wallets.sol/Wallets.dbg.json +4 -0
  68. package/build/contracts/bridge/Wallets.sol/Wallets.json +112 -0
  69. package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
  70. package/build/contracts/token/TBTC.sol/TBTC.json +2 -2
  71. package/build/contracts/vault/DonationVault.sol/DonationVault.dbg.json +4 -0
  72. package/build/contracts/vault/DonationVault.sol/DonationVault.json +108 -0
  73. package/build/contracts/vault/IVault.sol/IVault.dbg.json +1 -1
  74. package/build/contracts/vault/IVault.sol/IVault.json +24 -1
  75. package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +1 -1
  76. package/build/contracts/vault/TBTCVault.sol/TBTCVault.json +137 -18
  77. package/contracts/GovernanceUtils.sol +4 -4
  78. package/contracts/bank/Bank.sol +119 -57
  79. package/contracts/bank/IReceiveBalanceApproval.sol +45 -0
  80. package/contracts/bridge/BitcoinTx.sol +232 -10
  81. package/contracts/bridge/Bridge.sol +1601 -244
  82. package/contracts/bridge/BridgeState.sol +739 -0
  83. package/contracts/bridge/Deposit.sol +269 -0
  84. package/contracts/bridge/DepositSweep.sol +571 -0
  85. package/contracts/bridge/EcdsaLib.sol +45 -0
  86. package/contracts/bridge/Fraud.sol +604 -0
  87. package/contracts/bridge/Heartbeat.sol +112 -0
  88. package/contracts/bridge/IRelay.sol +28 -0
  89. package/contracts/bridge/MovingFunds.sol +1089 -0
  90. package/contracts/bridge/Redemption.sol +867 -0
  91. package/contracts/bridge/VendingMachine.sol +1 -1
  92. package/contracts/bridge/Wallets.sol +553 -0
  93. package/contracts/hardhat-dependency-compiler/.hardhat-dependency-compiler +1 -0
  94. package/contracts/hardhat-dependency-compiler/@keep-network/ecdsa/contracts/WalletRegistry.sol +3 -0
  95. package/contracts/hardhat-dependency-compiler/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol +3 -0
  96. package/contracts/hardhat-dependency-compiler/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +3 -0
  97. package/contracts/token/TBTC.sol +1 -1
  98. package/contracts/vault/DonationVault.sol +125 -0
  99. package/contracts/vault/IVault.sol +19 -13
  100. package/contracts/vault/TBTCVault.sol +69 -19
  101. package/deploy/00_resolve_relay.ts +28 -0
  102. package/deploy/04_deploy_bank.ts +27 -0
  103. package/deploy/05_deploy_bridge.ts +80 -0
  104. package/deploy/06_deploy_tbtc_vault.ts +30 -0
  105. package/deploy/07_bank_update_bridge.ts +19 -0
  106. package/deploy/08_transfer_ownership.ts +15 -0
  107. package/deploy/09_transfer_governance.ts +20 -0
  108. package/deploy/10_transfer_proxy_admin_ownership.ts +30 -0
  109. package/deploy/11_deploy_proxy_admin_with_deputy.ts +33 -0
  110. package/export.json +15993 -475
  111. package/package.json +32 -25
  112. package/artifacts/solcInputs/4cf328e09411ac69d75a3c381680bc2c.json +0 -128
@@ -0,0 +1,571 @@
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 temporary information needed during the processing
43
+ /// of the deposit sweep Bitcoin transaction inputs. This structure
44
+ /// is an internal one and should not be exported outside of the
45
+ /// deposit sweep transaction processing code.
46
+ /// @dev Allows to mitigate "stack too deep" errors on EVM.
47
+ struct DepositSweepTxInputsProcessingInfo {
48
+ // Input vector of the deposit sweep Bitcoin transaction. It is
49
+ // assumed the vector's structure is valid so it must be validated
50
+ // using e.g. `BTCUtils.validateVin` function before being used
51
+ // during the processing. The validation is usually done as part
52
+ // of the `BitcoinTx.validateProof` call that checks the SPV proof.
53
+ bytes sweepTxInputVector;
54
+ // Data of the wallet's main UTXO. If no main UTXO exists for the given
55
+ // sweeping wallet, this parameter's fields should be zeroed to bypass
56
+ // the main UTXO validation
57
+ BitcoinTx.UTXO mainUtxo;
58
+ // Address of the vault where all swept deposits should be routed to.
59
+ // It is used to validate whether all swept deposits have been revealed
60
+ // with the same `vault` parameter. It is an optional parameter.
61
+ // Set to zero address if deposits are not routed to a vault.
62
+ address vault;
63
+ // This struct doesn't contain `__gap` property as the structure is not
64
+ // stored, it is used as a function's memory argument.
65
+ }
66
+
67
+ /// @notice Represents an outcome of the sweep Bitcoin transaction
68
+ /// inputs processing.
69
+ struct DepositSweepTxInputsInfo {
70
+ // Sum of all inputs values i.e. all deposits and main UTXO value,
71
+ // if present.
72
+ uint256 inputsTotalValue;
73
+ // Addresses of depositors who performed processed deposits. Ordered in
74
+ // the same order as deposits inputs in the input vector. Size of this
75
+ // array is either equal to the number of inputs (main UTXO doesn't
76
+ // exist) or less by one (main UTXO exists and is pointed by one of
77
+ // the inputs).
78
+ address[] depositors;
79
+ // Amounts of deposits corresponding to processed deposits. Ordered in
80
+ // the same order as deposits inputs in the input vector. Size of this
81
+ // array is either equal to the number of inputs (main UTXO doesn't
82
+ // exist) or less by one (main UTXO exists and is pointed by one of
83
+ // the inputs).
84
+ uint256[] depositedAmounts;
85
+ // Values of the treasury fee corresponding to processed deposits.
86
+ // Ordered in the same order as deposits inputs in the input vector.
87
+ // Size of this array is either equal to the number of inputs (main
88
+ // UTXO doesn't exist) or less by one (main UTXO exists and is pointed
89
+ // by one of the inputs).
90
+ uint256[] treasuryFees;
91
+ // This struct doesn't contain `__gap` property as the structure is not
92
+ // stored, it is used as a function's memory argument.
93
+ }
94
+
95
+ event DepositsSwept(bytes20 walletPubKeyHash, bytes32 sweepTxHash);
96
+
97
+ /// @notice Used by the wallet to prove the BTC deposit sweep transaction
98
+ /// and to update Bank balances accordingly. Sweep is only accepted
99
+ /// if it satisfies SPV proof.
100
+ ///
101
+ /// The function is performing Bank balance updates by first
102
+ /// computing the Bitcoin fee for the sweep transaction. The fee is
103
+ /// divided evenly between all swept deposits. Each depositor
104
+ /// receives a balance in the bank equal to the amount inferred
105
+ /// during the reveal transaction, minus their fee share.
106
+ ///
107
+ /// It is possible to prove the given sweep only one time.
108
+ /// @param sweepTx Bitcoin sweep transaction data.
109
+ /// @param sweepProof Bitcoin sweep proof data.
110
+ /// @param mainUtxo Data of the wallet's main UTXO, as currently known on
111
+ /// the Ethereum chain. If no main UTXO exists for the given wallet,
112
+ /// this parameter is ignored.
113
+ /// @param vault Optional address of the vault where all swept deposits
114
+ /// should be routed to. All deposits swept as part of the transaction
115
+ /// must have their `vault` parameters set to the same address.
116
+ /// If this parameter is set to an address of a trusted vault, swept
117
+ /// deposits are routed to that vault.
118
+ /// If this parameter is set to the zero address or to an address
119
+ /// of a non-trusted vault, swept deposits are not routed to a
120
+ /// vault but depositors' balances are increased in the Bank
121
+ /// individually.
122
+ /// @dev Requirements:
123
+ /// - `sweepTx` components must match the expected structure. See
124
+ /// `BitcoinTx.Info` docs for reference. Their values must exactly
125
+ /// correspond to appropriate Bitcoin transaction fields to produce
126
+ /// a provable transaction hash,
127
+ /// - The `sweepTx` should represent a Bitcoin transaction with 1..n
128
+ /// inputs. If the wallet has no main UTXO, all n inputs should
129
+ /// correspond to P2(W)SH revealed deposits UTXOs. If the wallet has
130
+ /// an existing main UTXO, one of the n inputs must point to that
131
+ /// main UTXO and remaining n-1 inputs should correspond to P2(W)SH
132
+ /// revealed deposits UTXOs. That transaction must have only
133
+ /// one P2(W)PKH output locking funds on the 20-byte wallet public
134
+ /// key hash,
135
+ /// - All revealed deposits that are swept by `sweepTx` must have
136
+ /// their `vault` parameters set to the same address as the address
137
+ /// passed in the `vault` function parameter,
138
+ /// - `sweepProof` components must match the expected structure. See
139
+ /// `BitcoinTx.Proof` docs for reference. The `bitcoinHeaders`
140
+ /// field must contain a valid number of block headers, not less
141
+ /// than the `txProofDifficultyFactor` contract constant,
142
+ /// - `mainUtxo` components must point to the recent main UTXO
143
+ /// of the given wallet, as currently known on the Ethereum chain.
144
+ /// If there is no main UTXO, this parameter is ignored.
145
+ function submitDepositSweepProof(
146
+ BridgeState.Storage storage self,
147
+ BitcoinTx.Info calldata sweepTx,
148
+ BitcoinTx.Proof calldata sweepProof,
149
+ BitcoinTx.UTXO calldata mainUtxo,
150
+ address vault
151
+ ) external {
152
+ // The actual transaction proof is performed here. After that point, we
153
+ // can assume the transaction happened on Bitcoin chain and has
154
+ // a sufficient number of confirmations as determined by
155
+ // `txProofDifficultyFactor` constant.
156
+ bytes32 sweepTxHash = self.validateProof(sweepTx, sweepProof);
157
+
158
+ // Process sweep transaction output and extract its target wallet
159
+ // public key hash and value.
160
+ (
161
+ bytes20 walletPubKeyHash,
162
+ uint64 sweepTxOutputValue
163
+ ) = processDepositSweepTxOutput(self, sweepTx.outputVector);
164
+
165
+ (
166
+ Wallets.Wallet storage wallet,
167
+ BitcoinTx.UTXO memory resolvedMainUtxo
168
+ ) = resolveDepositSweepingWallet(self, walletPubKeyHash, mainUtxo);
169
+
170
+ // Process sweep transaction inputs and extract all information needed
171
+ // to perform deposit bookkeeping.
172
+ DepositSweepTxInputsInfo
173
+ memory inputsInfo = processDepositSweepTxInputs(
174
+ self,
175
+ DepositSweepTxInputsProcessingInfo(
176
+ sweepTx.inputVector,
177
+ resolvedMainUtxo,
178
+ vault
179
+ )
180
+ );
181
+
182
+ // Helper variable that will hold the sum of treasury fees paid by
183
+ // all deposits.
184
+ uint256 totalTreasuryFee = 0;
185
+
186
+ // Determine the transaction fee that should be incurred by each deposit
187
+ // and the indivisible remainder that should be additionally incurred
188
+ // by the last deposit.
189
+ (
190
+ uint256 depositTxFee,
191
+ uint256 depositTxFeeRemainder
192
+ ) = depositSweepTxFeeDistribution(
193
+ inputsInfo.inputsTotalValue,
194
+ sweepTxOutputValue,
195
+ inputsInfo.depositedAmounts.length
196
+ );
197
+
198
+ // Make sure the highest value of the deposit transaction fee does not
199
+ // exceed the maximum value limited by the governable parameter.
200
+ require(
201
+ depositTxFee + depositTxFeeRemainder <= self.depositTxMaxFee,
202
+ "Transaction fee is too high"
203
+ );
204
+
205
+ // Reduce each deposit amount by treasury fee and transaction fee.
206
+ for (uint256 i = 0; i < inputsInfo.depositedAmounts.length; i++) {
207
+ // The last deposit should incur the deposit transaction fee
208
+ // remainder.
209
+ uint256 depositTxFeeIncurred = i ==
210
+ inputsInfo.depositedAmounts.length - 1
211
+ ? depositTxFee + depositTxFeeRemainder
212
+ : depositTxFee;
213
+
214
+ // There is no need to check whether
215
+ // `inputsInfo.depositedAmounts[i] - inputsInfo.treasuryFees[i] - txFee > 0`
216
+ // since the `depositDustThreshold` should force that condition
217
+ // to be always true.
218
+ inputsInfo.depositedAmounts[i] =
219
+ inputsInfo.depositedAmounts[i] -
220
+ inputsInfo.treasuryFees[i] -
221
+ depositTxFeeIncurred;
222
+ totalTreasuryFee += inputsInfo.treasuryFees[i];
223
+ }
224
+
225
+ // Record this sweep data and assign them to the wallet public key hash
226
+ // as new main UTXO. Transaction output index is always 0 as sweep
227
+ // transaction always contains only one output.
228
+ wallet.mainUtxoHash = keccak256(
229
+ abi.encodePacked(sweepTxHash, uint32(0), sweepTxOutputValue)
230
+ );
231
+
232
+ // slither-disable-next-line reentrancy-events
233
+ emit DepositsSwept(walletPubKeyHash, sweepTxHash);
234
+
235
+ if (vault != address(0) && self.isVaultTrusted[vault]) {
236
+ // If the `vault` address is not zero and belongs to a trusted
237
+ // vault, route the deposits to that vault.
238
+ self.bank.increaseBalanceAndCall(
239
+ vault,
240
+ inputsInfo.depositors,
241
+ inputsInfo.depositedAmounts
242
+ );
243
+ } else {
244
+ // If the `vault` address is zero or belongs to a non-trusted
245
+ // vault, increase balances in the Bank individually for each
246
+ // depositor.
247
+ self.bank.increaseBalances(
248
+ inputsInfo.depositors,
249
+ inputsInfo.depositedAmounts
250
+ );
251
+ }
252
+
253
+ // Pass the treasury fee to the treasury address.
254
+ self.bank.increaseBalance(self.treasury, totalTreasuryFee);
255
+ }
256
+
257
+ /// @notice Resolves sweeping wallet based on the provided wallet public key
258
+ /// hash. Validates the wallet state and current main UTXO, as
259
+ /// currently known on the Ethereum chain.
260
+ /// @param walletPubKeyHash public key hash of the wallet proving the sweep
261
+ /// Bitcoin transaction.
262
+ /// @param mainUtxo Data of the wallet's main UTXO, as currently known on
263
+ /// the Ethereum chain. If no main UTXO exists for the given wallet,
264
+ /// this parameter is ignored.
265
+ /// @return wallet Data of the sweeping wallet.
266
+ /// @return resolvedMainUtxo The actual main UTXO of the sweeping wallet
267
+ /// resolved by cross-checking the `mainUtxo` parameter with
268
+ /// the chain state. If the validation went well, this is the
269
+ /// plain-text main UTXO corresponding to the `wallet.mainUtxoHash`.
270
+ /// @dev Requirements:
271
+ /// - Sweeping wallet must be either in Live or MovingFunds state,
272
+ /// - If the main UTXO of the sweeping wallet exists in the storage,
273
+ /// the passed `mainUTXO` parameter must be equal to the stored one.
274
+ function resolveDepositSweepingWallet(
275
+ BridgeState.Storage storage self,
276
+ bytes20 walletPubKeyHash,
277
+ BitcoinTx.UTXO calldata mainUtxo
278
+ )
279
+ internal
280
+ view
281
+ returns (
282
+ Wallets.Wallet storage wallet,
283
+ BitcoinTx.UTXO memory resolvedMainUtxo
284
+ )
285
+ {
286
+ wallet = self.registeredWallets[walletPubKeyHash];
287
+
288
+ Wallets.WalletState walletState = wallet.state;
289
+ require(
290
+ walletState == Wallets.WalletState.Live ||
291
+ walletState == Wallets.WalletState.MovingFunds,
292
+ "Wallet must be in Live or MovingFunds state"
293
+ );
294
+
295
+ // Check if the main UTXO for given wallet exists. If so, validate
296
+ // passed main UTXO data against the stored hash and use them for
297
+ // further processing. If no main UTXO exists, use empty data.
298
+ resolvedMainUtxo = BitcoinTx.UTXO(bytes32(0), 0, 0);
299
+ bytes32 mainUtxoHash = wallet.mainUtxoHash;
300
+ if (mainUtxoHash != bytes32(0)) {
301
+ require(
302
+ keccak256(
303
+ abi.encodePacked(
304
+ mainUtxo.txHash,
305
+ mainUtxo.txOutputIndex,
306
+ mainUtxo.txOutputValue
307
+ )
308
+ ) == mainUtxoHash,
309
+ "Invalid main UTXO data"
310
+ );
311
+ resolvedMainUtxo = mainUtxo;
312
+ }
313
+ }
314
+
315
+ /// @notice Processes the Bitcoin sweep transaction output vector by
316
+ /// extracting the single output and using it to gain additional
317
+ /// information required for further processing (e.g. value and
318
+ /// wallet public key hash).
319
+ /// @param sweepTxOutputVector Bitcoin sweep transaction output vector.
320
+ /// This function assumes vector's structure is valid so it must be
321
+ /// validated using e.g. `BTCUtils.validateVout` function before
322
+ /// it is passed here.
323
+ /// @return walletPubKeyHash 20-byte wallet public key hash.
324
+ /// @return value 8-byte sweep transaction output value.
325
+ function processDepositSweepTxOutput(
326
+ BridgeState.Storage storage self,
327
+ bytes memory sweepTxOutputVector
328
+ ) internal view returns (bytes20 walletPubKeyHash, uint64 value) {
329
+ // To determine the total number of sweep transaction outputs, we need to
330
+ // parse the compactSize uint (VarInt) the output vector is prepended by.
331
+ // That compactSize uint encodes the number of vector elements using the
332
+ // format presented in:
333
+ // https://developer.bitcoin.org/reference/transactions.html#compactsize-unsigned-integers
334
+ // We don't need asserting the compactSize uint is parseable since it
335
+ // was already checked during `validateVout` validation.
336
+ // See `BitcoinTx.outputVector` docs for more details.
337
+ (, uint256 outputsCount) = sweepTxOutputVector.parseVarInt();
338
+ require(
339
+ outputsCount == 1,
340
+ "Sweep transaction must have a single output"
341
+ );
342
+
343
+ bytes memory output = sweepTxOutputVector.extractOutputAtIndex(0);
344
+ walletPubKeyHash = self.extractPubKeyHash(output);
345
+ value = output.extractValue();
346
+
347
+ return (walletPubKeyHash, value);
348
+ }
349
+
350
+ /// @notice Processes the Bitcoin sweep transaction input vector. It
351
+ /// extracts each input and tries to obtain associated deposit or
352
+ /// main UTXO data, depending on the input type. Reverts
353
+ /// if one of the inputs cannot be recognized as a pointer to a
354
+ /// revealed deposit or expected main UTXO.
355
+ /// This function also marks each processed deposit as swept.
356
+ /// @return resultInfo Outcomes of the processing.
357
+ function processDepositSweepTxInputs(
358
+ BridgeState.Storage storage self,
359
+ DepositSweepTxInputsProcessingInfo memory processInfo
360
+ ) internal returns (DepositSweepTxInputsInfo memory resultInfo) {
361
+ // If the passed `mainUtxo` parameter's values are zeroed, the main UTXO
362
+ // for the given wallet doesn't exist and it is not expected to be
363
+ // included in the sweep transaction input vector.
364
+ bool mainUtxoExpected = processInfo.mainUtxo.txHash != bytes32(0);
365
+ bool mainUtxoFound = false;
366
+
367
+ // Determining the total number of sweep transaction inputs in the same
368
+ // way as for number of outputs. See `BitcoinTx.inputVector` docs for
369
+ // more details.
370
+ (uint256 inputsCompactSizeUintLength, uint256 inputsCount) = processInfo
371
+ .sweepTxInputVector
372
+ .parseVarInt();
373
+
374
+ // To determine the first input starting index, we must jump over
375
+ // the compactSize uint which prepends the input vector. One byte
376
+ // must be added because `BtcUtils.parseVarInt` does not include
377
+ // compactSize uint tag in the returned length.
378
+ //
379
+ // For >= 0 && <= 252, `BTCUtils.determineVarIntDataLengthAt`
380
+ // returns `0`, so we jump over one byte of compactSize uint.
381
+ //
382
+ // For >= 253 && <= 0xffff there is `0xfd` tag,
383
+ // `BTCUtils.determineVarIntDataLengthAt` returns `2` (no
384
+ // tag byte included) so we need to jump over 1+2 bytes of
385
+ // compactSize uint.
386
+ //
387
+ // Please refer `BTCUtils` library and compactSize uint
388
+ // docs in `BitcoinTx` library for more details.
389
+ uint256 inputStartingIndex = 1 + inputsCompactSizeUintLength;
390
+
391
+ // Determine the swept deposits count. If main UTXO is NOT expected,
392
+ // all inputs should be deposits. If main UTXO is expected, one input
393
+ // should point to that main UTXO.
394
+ resultInfo.depositors = new address[](
395
+ !mainUtxoExpected ? inputsCount : inputsCount - 1
396
+ );
397
+ resultInfo.depositedAmounts = new uint256[](
398
+ resultInfo.depositors.length
399
+ );
400
+ resultInfo.treasuryFees = new uint256[](resultInfo.depositors.length);
401
+
402
+ // Initialize helper variables.
403
+ uint256 processedDepositsCount = 0;
404
+
405
+ // Inputs processing loop.
406
+ for (uint256 i = 0; i < inputsCount; i++) {
407
+ (
408
+ bytes32 outpointTxHash,
409
+ uint32 outpointIndex,
410
+ uint256 inputLength
411
+ ) = parseDepositSweepTxInputAt(
412
+ processInfo.sweepTxInputVector,
413
+ inputStartingIndex
414
+ );
415
+
416
+ Deposit.DepositRequest storage deposit = self.deposits[
417
+ uint256(
418
+ keccak256(abi.encodePacked(outpointTxHash, outpointIndex))
419
+ )
420
+ ];
421
+
422
+ if (deposit.revealedAt != 0) {
423
+ // If we entered here, that means the input was identified as
424
+ // a revealed deposit.
425
+ require(deposit.sweptAt == 0, "Deposit already swept");
426
+
427
+ require(
428
+ deposit.vault == processInfo.vault,
429
+ "Deposit should be routed to another vault"
430
+ );
431
+
432
+ if (processedDepositsCount == resultInfo.depositors.length) {
433
+ // If this condition is true, that means a deposit input
434
+ // took place of an expected main UTXO input.
435
+ // In other words, there is no expected main UTXO
436
+ // input and all inputs come from valid, revealed deposits.
437
+ revert(
438
+ "Expected main UTXO not present in sweep transaction inputs"
439
+ );
440
+ }
441
+
442
+ /* solhint-disable-next-line not-rely-on-time */
443
+ deposit.sweptAt = uint32(block.timestamp);
444
+
445
+ resultInfo.depositors[processedDepositsCount] = deposit
446
+ .depositor;
447
+ resultInfo.depositedAmounts[processedDepositsCount] = deposit
448
+ .amount;
449
+ resultInfo.inputsTotalValue += resultInfo.depositedAmounts[
450
+ processedDepositsCount
451
+ ];
452
+ resultInfo.treasuryFees[processedDepositsCount] = deposit
453
+ .treasuryFee;
454
+
455
+ processedDepositsCount++;
456
+ } else if (
457
+ mainUtxoExpected != mainUtxoFound &&
458
+ processInfo.mainUtxo.txHash == outpointTxHash &&
459
+ processInfo.mainUtxo.txOutputIndex == outpointIndex
460
+ ) {
461
+ // If we entered here, that means the input was identified as
462
+ // the expected main UTXO.
463
+ resultInfo.inputsTotalValue += processInfo
464
+ .mainUtxo
465
+ .txOutputValue;
466
+ mainUtxoFound = true;
467
+
468
+ // Main UTXO used as an input, mark it as spent.
469
+ self.spentMainUTXOs[
470
+ uint256(
471
+ keccak256(
472
+ abi.encodePacked(outpointTxHash, outpointIndex)
473
+ )
474
+ )
475
+ ] = true;
476
+ } else {
477
+ revert("Unknown input type");
478
+ }
479
+
480
+ // Make the `inputStartingIndex` pointing to the next input by
481
+ // increasing it by current input's length.
482
+ inputStartingIndex += inputLength;
483
+ }
484
+
485
+ // Construction of the input processing loop guarantees that:
486
+ // `processedDepositsCount == resultInfo.depositors.length == resultInfo.depositedAmounts.length`
487
+ // is always true at this point. We just use the first variable
488
+ // to assert the total count of swept deposit is bigger than zero.
489
+ require(
490
+ processedDepositsCount > 0,
491
+ "Sweep transaction must process at least one deposit"
492
+ );
493
+
494
+ // Assert the main UTXO was used as one of current sweep's inputs if
495
+ // it was actually expected.
496
+ require(
497
+ mainUtxoExpected == mainUtxoFound,
498
+ "Expected main UTXO not present in sweep transaction inputs"
499
+ );
500
+
501
+ return resultInfo;
502
+ }
503
+
504
+ /// @notice Parses a Bitcoin transaction input starting at the given index.
505
+ /// @param inputVector Bitcoin transaction input vector.
506
+ /// @param inputStartingIndex Index the given input starts at.
507
+ /// @return outpointTxHash 32-byte hash of the Bitcoin transaction which is
508
+ /// pointed in the given input's outpoint.
509
+ /// @return outpointIndex 4-byte index of the Bitcoin transaction output
510
+ /// which is pointed in the given input's outpoint.
511
+ /// @return inputLength Byte length of the given input.
512
+ /// @dev This function assumes vector's structure is valid so it must be
513
+ /// validated using e.g. `BTCUtils.validateVin` function before it
514
+ /// is passed here.
515
+ function parseDepositSweepTxInputAt(
516
+ bytes memory inputVector,
517
+ uint256 inputStartingIndex
518
+ )
519
+ internal
520
+ pure
521
+ returns (
522
+ bytes32 outpointTxHash,
523
+ uint32 outpointIndex,
524
+ uint256 inputLength
525
+ )
526
+ {
527
+ outpointTxHash = inputVector.extractInputTxIdLeAt(inputStartingIndex);
528
+
529
+ outpointIndex = BTCUtils.reverseUint32(
530
+ uint32(inputVector.extractTxIndexLeAt(inputStartingIndex))
531
+ );
532
+
533
+ inputLength = inputVector.determineInputLengthAt(inputStartingIndex);
534
+
535
+ return (outpointTxHash, outpointIndex, inputLength);
536
+ }
537
+
538
+ /// @notice Determines the distribution of the sweep transaction fee
539
+ /// over swept deposits.
540
+ /// @param sweepTxInputsTotalValue Total value of all sweep transaction inputs.
541
+ /// @param sweepTxOutputValue Value of the sweep transaction output.
542
+ /// @param depositsCount Count of the deposits swept by the sweep transaction.
543
+ /// @return depositTxFee Transaction fee per deposit determined by evenly
544
+ /// spreading the divisible part of the sweep transaction fee
545
+ /// over all deposits.
546
+ /// @return depositTxFeeRemainder The indivisible part of the sweep
547
+ /// transaction fee than cannot be distributed over all deposits.
548
+ /// @dev It is up to the caller to decide how the remainder should be
549
+ /// counted in. This function only computes its value.
550
+ function depositSweepTxFeeDistribution(
551
+ uint256 sweepTxInputsTotalValue,
552
+ uint256 sweepTxOutputValue,
553
+ uint256 depositsCount
554
+ )
555
+ internal
556
+ pure
557
+ returns (uint256 depositTxFee, uint256 depositTxFeeRemainder)
558
+ {
559
+ // The sweep transaction fee is just the difference between inputs
560
+ // amounts sum and the output amount.
561
+ uint256 sweepTxFee = sweepTxInputsTotalValue - sweepTxOutputValue;
562
+ // Compute the indivisible remainder that remains after dividing the
563
+ // sweep transaction fee over all deposits evenly.
564
+ depositTxFeeRemainder = sweepTxFee % depositsCount;
565
+ // Compute the transaction fee per deposit by dividing the sweep
566
+ // transaction fee (reduced by the remainder) by the number of deposits.
567
+ depositTxFee = (sweepTxFee - depositTxFeeRemainder) / depositsCount;
568
+
569
+ return (depositTxFee, depositTxFeeRemainder);
570
+ }
571
+ }
@@ -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
+ }