@keep-network/tbtc-v2 0.1.1-dev.10 → 0.1.1-dev.100

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 (115) hide show
  1. package/README.adoc +12 -0
  2. package/artifacts/Bank.json +807 -0
  3. package/artifacts/Bridge.json +2300 -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 +174 -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 +691 -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 +31 -30
  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/8b65103759482b36742c6820fa66b63b.json +314 -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 +10 -5
  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 +2549 -198
  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 +5 -0
  75. package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +1 -1
  76. package/build/contracts/vault/TBTCVault.sol/TBTCVault.json +273 -5
  77. package/contracts/GovernanceUtils.sol +4 -4
  78. package/contracts/bank/Bank.sol +113 -66
  79. package/contracts/bank/IReceiveBalanceApproval.sol +45 -0
  80. package/contracts/bridge/BitcoinTx.sol +267 -10
  81. package/contracts/bridge/Bridge.sol +1698 -245
  82. package/contracts/bridge/BridgeState.sol +768 -0
  83. package/contracts/bridge/Deposit.sol +269 -0
  84. package/contracts/bridge/DepositSweep.sol +574 -0
  85. package/contracts/bridge/EcdsaLib.sol +45 -0
  86. package/contracts/bridge/Fraud.sol +579 -0
  87. package/contracts/bridge/Heartbeat.sol +112 -0
  88. package/contracts/bridge/IRelay.sol +28 -0
  89. package/contracts/bridge/MovingFunds.sol +1077 -0
  90. package/contracts/bridge/Redemption.sol +1058 -0
  91. package/contracts/bridge/VendingMachine.sol +2 -2
  92. package/contracts/bridge/Wallets.sol +719 -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 +6 -22
  100. package/contracts/vault/TBTCVault.sol +188 -29
  101. package/deploy/00_resolve_relay.ts +28 -0
  102. package/deploy/{03_transfer_roles.ts → 03_transfer_vending_machine_roles.ts} +1 -1
  103. package/deploy/04_deploy_bank.ts +27 -0
  104. package/deploy/05_deploy_bridge.ts +80 -0
  105. package/deploy/06_deploy_tbtc_vault.ts +30 -0
  106. package/deploy/07_bank_update_bridge.ts +19 -0
  107. package/deploy/08_transfer_bank_ownership.ts +15 -0
  108. package/deploy/09_transfer_tbtc_vault_ownership.ts +15 -0
  109. package/deploy/10_transfer_bridge_governance.ts +20 -0
  110. package/deploy/11_initialize_wallet_owner.ts +18 -0
  111. package/deploy/11_transfer_proxy_admin_ownership.ts +30 -0
  112. package/deploy/12_deploy_proxy_admin_with_deputy.ts +33 -0
  113. package/export.json +15771 -443
  114. package/package.json +34 -26
  115. package/artifacts/solcInputs/524094faac10a04084fcc411e06dab84.json +0 -128
@@ -0,0 +1,574 @@
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
+ // Wallet state validation is performed in the
153
+ // `resolveDepositSweepingWallet` function.
154
+
155
+ // The actual transaction proof is performed here. After that point, we
156
+ // can assume the transaction happened on Bitcoin chain and has
157
+ // a sufficient number of confirmations as determined by
158
+ // `txProofDifficultyFactor` constant.
159
+ bytes32 sweepTxHash = self.validateProof(sweepTx, sweepProof);
160
+
161
+ // Process sweep transaction output and extract its target wallet
162
+ // public key hash and value.
163
+ (
164
+ bytes20 walletPubKeyHash,
165
+ uint64 sweepTxOutputValue
166
+ ) = processDepositSweepTxOutput(self, sweepTx.outputVector);
167
+
168
+ (
169
+ Wallets.Wallet storage wallet,
170
+ BitcoinTx.UTXO memory resolvedMainUtxo
171
+ ) = resolveDepositSweepingWallet(self, walletPubKeyHash, mainUtxo);
172
+
173
+ // Process sweep transaction inputs and extract all information needed
174
+ // to perform deposit bookkeeping.
175
+ DepositSweepTxInputsInfo
176
+ memory inputsInfo = processDepositSweepTxInputs(
177
+ self,
178
+ DepositSweepTxInputsProcessingInfo(
179
+ sweepTx.inputVector,
180
+ resolvedMainUtxo,
181
+ vault
182
+ )
183
+ );
184
+
185
+ // Helper variable that will hold the sum of treasury fees paid by
186
+ // all deposits.
187
+ uint256 totalTreasuryFee = 0;
188
+
189
+ // Determine the transaction fee that should be incurred by each deposit
190
+ // and the indivisible remainder that should be additionally incurred
191
+ // by the last deposit.
192
+ (
193
+ uint256 depositTxFee,
194
+ uint256 depositTxFeeRemainder
195
+ ) = depositSweepTxFeeDistribution(
196
+ inputsInfo.inputsTotalValue,
197
+ sweepTxOutputValue,
198
+ inputsInfo.depositedAmounts.length
199
+ );
200
+
201
+ // Make sure the highest value of the deposit transaction fee does not
202
+ // exceed the maximum value limited by the governable parameter.
203
+ require(
204
+ depositTxFee + depositTxFeeRemainder <= self.depositTxMaxFee,
205
+ "Transaction fee is too high"
206
+ );
207
+
208
+ // Reduce each deposit amount by treasury fee and transaction fee.
209
+ for (uint256 i = 0; i < inputsInfo.depositedAmounts.length; i++) {
210
+ // The last deposit should incur the deposit transaction fee
211
+ // remainder.
212
+ uint256 depositTxFeeIncurred = i ==
213
+ inputsInfo.depositedAmounts.length - 1
214
+ ? depositTxFee + depositTxFeeRemainder
215
+ : depositTxFee;
216
+
217
+ // There is no need to check whether
218
+ // `inputsInfo.depositedAmounts[i] - inputsInfo.treasuryFees[i] - txFee > 0`
219
+ // since the `depositDustThreshold` should force that condition
220
+ // to be always true.
221
+ inputsInfo.depositedAmounts[i] =
222
+ inputsInfo.depositedAmounts[i] -
223
+ inputsInfo.treasuryFees[i] -
224
+ depositTxFeeIncurred;
225
+ totalTreasuryFee += inputsInfo.treasuryFees[i];
226
+ }
227
+
228
+ // Record this sweep data and assign them to the wallet public key hash
229
+ // as new main UTXO. Transaction output index is always 0 as sweep
230
+ // transaction always contains only one output.
231
+ wallet.mainUtxoHash = keccak256(
232
+ abi.encodePacked(sweepTxHash, uint32(0), sweepTxOutputValue)
233
+ );
234
+
235
+ // slither-disable-next-line reentrancy-events
236
+ emit DepositsSwept(walletPubKeyHash, sweepTxHash);
237
+
238
+ if (vault != address(0) && self.isVaultTrusted[vault]) {
239
+ // If the `vault` address is not zero and belongs to a trusted
240
+ // vault, route the deposits to that vault.
241
+ self.bank.increaseBalanceAndCall(
242
+ vault,
243
+ inputsInfo.depositors,
244
+ inputsInfo.depositedAmounts
245
+ );
246
+ } else {
247
+ // If the `vault` address is zero or belongs to a non-trusted
248
+ // vault, increase balances in the Bank individually for each
249
+ // depositor.
250
+ self.bank.increaseBalances(
251
+ inputsInfo.depositors,
252
+ inputsInfo.depositedAmounts
253
+ );
254
+ }
255
+
256
+ // Pass the treasury fee to the treasury address.
257
+ self.bank.increaseBalance(self.treasury, totalTreasuryFee);
258
+ }
259
+
260
+ /// @notice Resolves sweeping wallet based on the provided wallet public key
261
+ /// hash. Validates the wallet state and current main UTXO, as
262
+ /// currently known on the Ethereum chain.
263
+ /// @param walletPubKeyHash public key hash of the wallet proving the sweep
264
+ /// Bitcoin transaction.
265
+ /// @param mainUtxo Data of the wallet's main UTXO, as currently known on
266
+ /// the Ethereum chain. If no main UTXO exists for the given wallet,
267
+ /// this parameter is ignored.
268
+ /// @return wallet Data of the sweeping wallet.
269
+ /// @return resolvedMainUtxo The actual main UTXO of the sweeping wallet
270
+ /// resolved by cross-checking the `mainUtxo` parameter with
271
+ /// the chain state. If the validation went well, this is the
272
+ /// plain-text main UTXO corresponding to the `wallet.mainUtxoHash`.
273
+ /// @dev Requirements:
274
+ /// - Sweeping wallet must be either in Live or MovingFunds state,
275
+ /// - If the main UTXO of the sweeping wallet exists in the storage,
276
+ /// the passed `mainUTXO` parameter must be equal to the stored one.
277
+ function resolveDepositSweepingWallet(
278
+ BridgeState.Storage storage self,
279
+ bytes20 walletPubKeyHash,
280
+ BitcoinTx.UTXO calldata mainUtxo
281
+ )
282
+ internal
283
+ view
284
+ returns (
285
+ Wallets.Wallet storage wallet,
286
+ BitcoinTx.UTXO memory resolvedMainUtxo
287
+ )
288
+ {
289
+ wallet = self.registeredWallets[walletPubKeyHash];
290
+
291
+ Wallets.WalletState walletState = wallet.state;
292
+ require(
293
+ walletState == Wallets.WalletState.Live ||
294
+ walletState == Wallets.WalletState.MovingFunds,
295
+ "Wallet must be in Live or MovingFunds state"
296
+ );
297
+
298
+ // Check if the main UTXO for given wallet exists. If so, validate
299
+ // passed main UTXO data against the stored hash and use them for
300
+ // further processing. If no main UTXO exists, use empty data.
301
+ resolvedMainUtxo = BitcoinTx.UTXO(bytes32(0), 0, 0);
302
+ bytes32 mainUtxoHash = wallet.mainUtxoHash;
303
+ if (mainUtxoHash != bytes32(0)) {
304
+ require(
305
+ keccak256(
306
+ abi.encodePacked(
307
+ mainUtxo.txHash,
308
+ mainUtxo.txOutputIndex,
309
+ mainUtxo.txOutputValue
310
+ )
311
+ ) == mainUtxoHash,
312
+ "Invalid main UTXO data"
313
+ );
314
+ resolvedMainUtxo = mainUtxo;
315
+ }
316
+ }
317
+
318
+ /// @notice Processes the Bitcoin sweep transaction output vector by
319
+ /// extracting the single output and using it to gain additional
320
+ /// information required for further processing (e.g. value and
321
+ /// wallet public key hash).
322
+ /// @param sweepTxOutputVector Bitcoin sweep transaction output vector.
323
+ /// This function assumes vector's structure is valid so it must be
324
+ /// validated using e.g. `BTCUtils.validateVout` function before
325
+ /// it is passed here.
326
+ /// @return walletPubKeyHash 20-byte wallet public key hash.
327
+ /// @return value 8-byte sweep transaction output value.
328
+ function processDepositSweepTxOutput(
329
+ BridgeState.Storage storage self,
330
+ bytes memory sweepTxOutputVector
331
+ ) internal view returns (bytes20 walletPubKeyHash, uint64 value) {
332
+ // To determine the total number of sweep transaction outputs, we need to
333
+ // parse the compactSize uint (VarInt) the output vector is prepended by.
334
+ // That compactSize uint encodes the number of vector elements using the
335
+ // format presented in:
336
+ // https://developer.bitcoin.org/reference/transactions.html#compactsize-unsigned-integers
337
+ // We don't need asserting the compactSize uint is parseable since it
338
+ // was already checked during `validateVout` validation.
339
+ // See `BitcoinTx.outputVector` docs for more details.
340
+ (, uint256 outputsCount) = sweepTxOutputVector.parseVarInt();
341
+ require(
342
+ outputsCount == 1,
343
+ "Sweep transaction must have a single output"
344
+ );
345
+
346
+ bytes memory output = sweepTxOutputVector.extractOutputAtIndex(0);
347
+ walletPubKeyHash = self.extractPubKeyHash(output);
348
+ value = output.extractValue();
349
+
350
+ return (walletPubKeyHash, value);
351
+ }
352
+
353
+ /// @notice Processes the Bitcoin sweep transaction input vector. It
354
+ /// extracts each input and tries to obtain associated deposit or
355
+ /// main UTXO data, depending on the input type. Reverts
356
+ /// if one of the inputs cannot be recognized as a pointer to a
357
+ /// revealed deposit or expected main UTXO.
358
+ /// This function also marks each processed deposit as swept.
359
+ /// @return resultInfo Outcomes of the processing.
360
+ function processDepositSweepTxInputs(
361
+ BridgeState.Storage storage self,
362
+ DepositSweepTxInputsProcessingInfo memory processInfo
363
+ ) internal returns (DepositSweepTxInputsInfo memory resultInfo) {
364
+ // If the passed `mainUtxo` parameter's values are zeroed, the main UTXO
365
+ // for the given wallet doesn't exist and it is not expected to be
366
+ // included in the sweep transaction input vector.
367
+ bool mainUtxoExpected = processInfo.mainUtxo.txHash != bytes32(0);
368
+ bool mainUtxoFound = false;
369
+
370
+ // Determining the total number of sweep transaction inputs in the same
371
+ // way as for number of outputs. See `BitcoinTx.inputVector` docs for
372
+ // more details.
373
+ (uint256 inputsCompactSizeUintLength, uint256 inputsCount) = processInfo
374
+ .sweepTxInputVector
375
+ .parseVarInt();
376
+
377
+ // To determine the first input starting index, we must jump over
378
+ // the compactSize uint which prepends the input vector. One byte
379
+ // must be added because `BtcUtils.parseVarInt` does not include
380
+ // compactSize uint tag in the returned length.
381
+ //
382
+ // For >= 0 && <= 252, `BTCUtils.determineVarIntDataLengthAt`
383
+ // returns `0`, so we jump over one byte of compactSize uint.
384
+ //
385
+ // For >= 253 && <= 0xffff there is `0xfd` tag,
386
+ // `BTCUtils.determineVarIntDataLengthAt` returns `2` (no
387
+ // tag byte included) so we need to jump over 1+2 bytes of
388
+ // compactSize uint.
389
+ //
390
+ // Please refer `BTCUtils` library and compactSize uint
391
+ // docs in `BitcoinTx` library for more details.
392
+ uint256 inputStartingIndex = 1 + inputsCompactSizeUintLength;
393
+
394
+ // Determine the swept deposits count. If main UTXO is NOT expected,
395
+ // all inputs should be deposits. If main UTXO is expected, one input
396
+ // should point to that main UTXO.
397
+ resultInfo.depositors = new address[](
398
+ !mainUtxoExpected ? inputsCount : inputsCount - 1
399
+ );
400
+ resultInfo.depositedAmounts = new uint256[](
401
+ resultInfo.depositors.length
402
+ );
403
+ resultInfo.treasuryFees = new uint256[](resultInfo.depositors.length);
404
+
405
+ // Initialize helper variables.
406
+ uint256 processedDepositsCount = 0;
407
+
408
+ // Inputs processing loop.
409
+ for (uint256 i = 0; i < inputsCount; i++) {
410
+ (
411
+ bytes32 outpointTxHash,
412
+ uint32 outpointIndex,
413
+ uint256 inputLength
414
+ ) = parseDepositSweepTxInputAt(
415
+ processInfo.sweepTxInputVector,
416
+ inputStartingIndex
417
+ );
418
+
419
+ Deposit.DepositRequest storage deposit = self.deposits[
420
+ uint256(
421
+ keccak256(abi.encodePacked(outpointTxHash, outpointIndex))
422
+ )
423
+ ];
424
+
425
+ if (deposit.revealedAt != 0) {
426
+ // If we entered here, that means the input was identified as
427
+ // a revealed deposit.
428
+ require(deposit.sweptAt == 0, "Deposit already swept");
429
+
430
+ require(
431
+ deposit.vault == processInfo.vault,
432
+ "Deposit should be routed to another vault"
433
+ );
434
+
435
+ if (processedDepositsCount == resultInfo.depositors.length) {
436
+ // If this condition is true, that means a deposit input
437
+ // took place of an expected main UTXO input.
438
+ // In other words, there is no expected main UTXO
439
+ // input and all inputs come from valid, revealed deposits.
440
+ revert(
441
+ "Expected main UTXO not present in sweep transaction inputs"
442
+ );
443
+ }
444
+
445
+ /* solhint-disable-next-line not-rely-on-time */
446
+ deposit.sweptAt = uint32(block.timestamp);
447
+
448
+ resultInfo.depositors[processedDepositsCount] = deposit
449
+ .depositor;
450
+ resultInfo.depositedAmounts[processedDepositsCount] = deposit
451
+ .amount;
452
+ resultInfo.inputsTotalValue += resultInfo.depositedAmounts[
453
+ processedDepositsCount
454
+ ];
455
+ resultInfo.treasuryFees[processedDepositsCount] = deposit
456
+ .treasuryFee;
457
+
458
+ processedDepositsCount++;
459
+ } else if (
460
+ mainUtxoExpected != mainUtxoFound &&
461
+ processInfo.mainUtxo.txHash == outpointTxHash &&
462
+ processInfo.mainUtxo.txOutputIndex == outpointIndex
463
+ ) {
464
+ // If we entered here, that means the input was identified as
465
+ // the expected main UTXO.
466
+ resultInfo.inputsTotalValue += processInfo
467
+ .mainUtxo
468
+ .txOutputValue;
469
+ mainUtxoFound = true;
470
+
471
+ // Main UTXO used as an input, mark it as spent.
472
+ self.spentMainUTXOs[
473
+ uint256(
474
+ keccak256(
475
+ abi.encodePacked(outpointTxHash, outpointIndex)
476
+ )
477
+ )
478
+ ] = true;
479
+ } else {
480
+ revert("Unknown input type");
481
+ }
482
+
483
+ // Make the `inputStartingIndex` pointing to the next input by
484
+ // increasing it by current input's length.
485
+ inputStartingIndex += inputLength;
486
+ }
487
+
488
+ // Construction of the input processing loop guarantees that:
489
+ // `processedDepositsCount == resultInfo.depositors.length == resultInfo.depositedAmounts.length`
490
+ // is always true at this point. We just use the first variable
491
+ // to assert the total count of swept deposit is bigger than zero.
492
+ require(
493
+ processedDepositsCount > 0,
494
+ "Sweep transaction must process at least one deposit"
495
+ );
496
+
497
+ // Assert the main UTXO was used as one of current sweep's inputs if
498
+ // it was actually expected.
499
+ require(
500
+ mainUtxoExpected == mainUtxoFound,
501
+ "Expected main UTXO not present in sweep transaction inputs"
502
+ );
503
+
504
+ return resultInfo;
505
+ }
506
+
507
+ /// @notice Parses a Bitcoin transaction input starting at the given index.
508
+ /// @param inputVector Bitcoin transaction input vector.
509
+ /// @param inputStartingIndex Index the given input starts at.
510
+ /// @return outpointTxHash 32-byte hash of the Bitcoin transaction which is
511
+ /// pointed in the given input's outpoint.
512
+ /// @return outpointIndex 4-byte index of the Bitcoin transaction output
513
+ /// which is pointed in the given input's outpoint.
514
+ /// @return inputLength Byte length of the given input.
515
+ /// @dev This function assumes vector's structure is valid so it must be
516
+ /// validated using e.g. `BTCUtils.validateVin` function before it
517
+ /// is passed here.
518
+ function parseDepositSweepTxInputAt(
519
+ bytes memory inputVector,
520
+ uint256 inputStartingIndex
521
+ )
522
+ internal
523
+ pure
524
+ returns (
525
+ bytes32 outpointTxHash,
526
+ uint32 outpointIndex,
527
+ uint256 inputLength
528
+ )
529
+ {
530
+ outpointTxHash = inputVector.extractInputTxIdLeAt(inputStartingIndex);
531
+
532
+ outpointIndex = BTCUtils.reverseUint32(
533
+ uint32(inputVector.extractTxIndexLeAt(inputStartingIndex))
534
+ );
535
+
536
+ inputLength = inputVector.determineInputLengthAt(inputStartingIndex);
537
+
538
+ return (outpointTxHash, outpointIndex, inputLength);
539
+ }
540
+
541
+ /// @notice Determines the distribution of the sweep transaction fee
542
+ /// over swept deposits.
543
+ /// @param sweepTxInputsTotalValue Total value of all sweep transaction inputs.
544
+ /// @param sweepTxOutputValue Value of the sweep transaction output.
545
+ /// @param depositsCount Count of the deposits swept by the sweep transaction.
546
+ /// @return depositTxFee Transaction fee per deposit determined by evenly
547
+ /// spreading the divisible part of the sweep transaction fee
548
+ /// over all deposits.
549
+ /// @return depositTxFeeRemainder The indivisible part of the sweep
550
+ /// transaction fee than cannot be distributed over all deposits.
551
+ /// @dev It is up to the caller to decide how the remainder should be
552
+ /// counted in. This function only computes its value.
553
+ function depositSweepTxFeeDistribution(
554
+ uint256 sweepTxInputsTotalValue,
555
+ uint256 sweepTxOutputValue,
556
+ uint256 depositsCount
557
+ )
558
+ internal
559
+ pure
560
+ returns (uint256 depositTxFee, uint256 depositTxFeeRemainder)
561
+ {
562
+ // The sweep transaction fee is just the difference between inputs
563
+ // amounts sum and the output amount.
564
+ uint256 sweepTxFee = sweepTxInputsTotalValue - sweepTxOutputValue;
565
+ // Compute the indivisible remainder that remains after dividing the
566
+ // sweep transaction fee over all deposits evenly.
567
+ depositTxFeeRemainder = sweepTxFee % depositsCount;
568
+ // Compute the transaction fee per deposit by dividing the sweep
569
+ // transaction fee (reduced by the remainder) by the number of deposits.
570
+ depositTxFee = (sweepTxFee - depositTxFeeRemainder) / depositsCount;
571
+
572
+ return (depositTxFee, depositTxFeeRemainder);
573
+ }
574
+ }
@@ -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
+ }