@keep-network/tbtc-v2 0.1.1-dev.0 → 0.1.1-dev.12

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 (30) hide show
  1. package/artifacts/TBTC.json +24 -24
  2. package/artifacts/TBTCToken.json +24 -24
  3. package/artifacts/VendingMachine.json +17 -17
  4. package/artifacts/solcInputs/258c81d1106a1d8dae0e3424749db907.json +134 -0
  5. package/build/contracts/GovernanceUtils.sol/GovernanceUtils.dbg.json +1 -1
  6. package/build/contracts/bank/Bank.sol/Bank.dbg.json +4 -0
  7. package/build/contracts/bank/Bank.sol/Bank.json +537 -0
  8. package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.dbg.json +4 -0
  9. package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.json +10 -0
  10. package/build/contracts/bridge/Bridge.sol/Bridge.dbg.json +4 -0
  11. package/build/contracts/bridge/Bridge.sol/Bridge.json +473 -0
  12. package/build/contracts/bridge/Bridge.sol/IRelay.dbg.json +4 -0
  13. package/build/contracts/bridge/Bridge.sol/IRelay.json +37 -0
  14. package/build/contracts/bridge/VendingMachine.sol/VendingMachine.dbg.json +1 -1
  15. package/build/contracts/bridge/VendingMachine.sol/VendingMachine.json +2 -2
  16. package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
  17. package/build/contracts/token/TBTC.sol/TBTC.json +4 -4
  18. package/build/contracts/vault/IVault.sol/IVault.dbg.json +4 -0
  19. package/build/contracts/vault/IVault.sol/IVault.json +47 -0
  20. package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +4 -0
  21. package/build/contracts/vault/TBTCVault.sol/TBTCVault.json +181 -0
  22. package/contracts/bank/Bank.sol +389 -0
  23. package/contracts/bridge/BitcoinTx.sol +125 -0
  24. package/contracts/bridge/Bridge.sol +804 -0
  25. package/contracts/vault/IVault.sol +60 -0
  26. package/contracts/vault/TBTCVault.sol +146 -0
  27. package/deploy/00_resolve_tbtc_v1_token.ts +1 -1
  28. package/export.json +4 -4
  29. package/package.json +25 -15
  30. package/artifacts/solcInputs/7cc3eda3cb3ff2522d18b5e7b31ea228.json +0 -104
@@ -0,0 +1,804 @@
1
+ // SPDX-License-Identifier: MIT
2
+
3
+ // ██████████████ ▐████▌ ██████████████
4
+ // ██████████████ ▐████▌ ██████████████
5
+ // ▐████▌ ▐████▌
6
+ // ▐████▌ ▐████▌
7
+ // ██████████████ ▐████▌ ██████████████
8
+ // ██████████████ ▐████▌ ██████████████
9
+ // ▐████▌ ▐████▌
10
+ // ▐████▌ ▐████▌
11
+ // ▐████▌ ▐████▌
12
+ // ▐████▌ ▐████▌
13
+ // ▐████▌ ▐████▌
14
+ // ▐████▌ ▐████▌
15
+
16
+ pragma solidity 0.8.4;
17
+
18
+ import "@openzeppelin/contracts/access/Ownable.sol";
19
+
20
+ import {BTCUtils} from "@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol";
21
+ import {BytesLib} from "@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol";
22
+ import {
23
+ ValidateSPV
24
+ } from "@keep-network/bitcoin-spv-sol/contracts/ValidateSPV.sol";
25
+
26
+ import "../bank/Bank.sol";
27
+ import "./BitcoinTx.sol";
28
+
29
+ /// @title Interface for the Bitcoin relay
30
+ /// @notice Contains only the methods needed by tBTC v2. The Bitcoin relay
31
+ /// provides the difficulty of the previous and current epoch. One
32
+ /// difficulty epoch spans 2016 blocks.
33
+ interface IRelay {
34
+ /// @notice Returns the difficulty of the current epoch.
35
+ function getCurrentEpochDifficulty() external view returns (uint256);
36
+
37
+ /// @notice Returns the difficulty of the previous epoch.
38
+ function getPrevEpochDifficulty() external view returns (uint256);
39
+ }
40
+
41
+ /// @title Bitcoin Bridge
42
+ /// @notice Bridge manages BTC deposit and redemption flow and is increasing and
43
+ /// decreasing balances in the Bank as a result of BTC deposit and
44
+ /// redemption operations performed by depositors and redeemers.
45
+ ///
46
+ /// Depositors send BTC funds to the most recently created off-chain
47
+ /// ECDSA wallet of the bridge using pay-to-script-hash (P2SH) or
48
+ /// pay-to-witness-script-hash (P2WSH) containing hashed information
49
+ /// about the depositor’s Ethereum address. Then, the depositor reveals
50
+ /// their Ethereum address along with their deposit blinding factor,
51
+ /// refund public key hash and refund locktime to the Bridge on Ethereum
52
+ /// chain. The off-chain ECDSA wallet listens for these sorts of
53
+ /// messages and when it gets one, it checks the Bitcoin network to make
54
+ /// sure the deposit lines up. If it does, the off-chain ECDSA wallet
55
+ /// may decide to pick the deposit transaction for sweeping, and when
56
+ /// the sweep operation is confirmed on the Bitcoin network, the ECDSA
57
+ /// wallet informs the Bridge about the sweep increasing appropriate
58
+ /// balances in the Bank.
59
+ /// @dev Bridge is an upgradeable component of the Bank.
60
+ contract Bridge is Ownable {
61
+ using BTCUtils for bytes;
62
+ using BTCUtils for uint256;
63
+ using BytesLib for bytes;
64
+ using ValidateSPV for bytes;
65
+ using ValidateSPV for bytes32;
66
+
67
+ /// @notice Represents data which must be revealed by the depositor during
68
+ /// deposit reveal.
69
+ struct RevealInfo {
70
+ // Index of the funding output belonging to the funding transaction.
71
+ uint32 fundingOutputIndex;
72
+ // Ethereum depositor address.
73
+ address depositor;
74
+ // The blinding factor as 8 bytes. Byte endianness doesn't matter
75
+ // as this factor is not interpreted as uint.
76
+ bytes8 blindingFactor;
77
+ // The compressed Bitcoin public key (33 bytes and 02 or 03 prefix)
78
+ // of the deposit's wallet hashed in the HASH160 Bitcoin opcode style.
79
+ bytes20 walletPubKeyHash;
80
+ // The compressed Bitcoin public key (33 bytes and 02 or 03 prefix)
81
+ // that can be used to make the deposit refund after the refund
82
+ // locktime passes. Hashed in the HASH160 Bitcoin opcode style.
83
+ bytes20 refundPubKeyHash;
84
+ // The refund locktime (4-byte LE). Interpreted according to locktime
85
+ // parsing rules described in:
86
+ // https://developer.bitcoin.org/devguide/transactions.html#locktime-and-sequence-number
87
+ // and used with OP_CHECKLOCKTIMEVERIFY opcode as described in:
88
+ // https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki
89
+ bytes4 refundLocktime;
90
+ // Address of the Bank vault to which the deposit is routed to.
91
+ // Optional, can be 0x0. The vault must be trusted by the Bridge.
92
+ address vault;
93
+ }
94
+
95
+ /// @notice Represents tBTC deposit data.
96
+ struct DepositInfo {
97
+ // Ethereum depositor address.
98
+ address depositor;
99
+ // Deposit amount in satoshi.
100
+ uint64 amount;
101
+ // UNIX timestamp the deposit was revealed at.
102
+ uint32 revealedAt;
103
+ // Address of the Bank vault the deposit is routed to.
104
+ // Optional, can be 0x0.
105
+ address vault;
106
+ // UNIX timestamp the deposit was swept at. Note this is not the
107
+ // time when the deposit was swept on the Bitcoin chain but actually
108
+ // the time when the sweep proof was delivered to the Ethereum chain.
109
+ uint32 sweptAt;
110
+ }
111
+
112
+ /// @notice The number of confirmations on the Bitcoin chain required to
113
+ /// successfully evaluate an SPV proof.
114
+ uint256 public immutable txProofDifficultyFactor;
115
+
116
+ // TODO: Revisit whether it should be updatable or not.
117
+ /// @notice Address of the Bank this Bridge belongs to.
118
+ Bank public immutable bank;
119
+
120
+ /// TODO: Make it updatable.
121
+ /// @notice Handle to the Bitcoin relay.
122
+ IRelay public immutable relay;
123
+
124
+ /// @notice Indicates if the vault with the given address is trusted or not.
125
+ /// Depositors can route their revealed deposits only to trusted
126
+ /// vaults and have trusted vaults notified about new deposits as
127
+ /// soon as these deposits get swept. Vaults not trusted by the
128
+ /// Bridge can still be used by Bank balance owners on their own
129
+ /// responsibility - anyone can approve their Bank balance to any
130
+ /// address.
131
+ mapping(address => bool) public isVaultTrusted;
132
+
133
+ /// @notice Collection of all revealed deposits indexed by
134
+ /// keccak256(fundingTxHash | fundingOutputIndex).
135
+ /// The fundingTxHash is LE bytes32 and fundingOutputIndex an uint32.
136
+ /// This mapping may contain valid and invalid deposits and the
137
+ /// wallet is responsible for validating them before attempting to
138
+ /// execute a sweep.
139
+ mapping(uint256 => DepositInfo) public deposits;
140
+
141
+ /// @notice Maps the wallet public key hash (computed using HASH160 opcode)
142
+ /// to the latest wallet's main UTXO computed as
143
+ /// keccak256(txHash | txOutputIndex | txOutputValue). The `tx`
144
+ /// prefix refers to the transaction which created that main UTXO.
145
+ mapping(bytes20 => bytes32) public mainUtxos;
146
+
147
+ event VaultStatusUpdated(address indexed vault, bool isTrusted);
148
+
149
+ event DepositRevealed(
150
+ bytes32 fundingTxHash,
151
+ uint32 fundingOutputIndex,
152
+ address depositor,
153
+ uint64 amount,
154
+ bytes8 blindingFactor,
155
+ bytes20 walletPubKeyHash,
156
+ bytes20 refundPubKeyHash,
157
+ bytes4 refundLocktime,
158
+ address vault
159
+ );
160
+
161
+ event DepositsSwept(bytes20 walletPubKeyHash, bytes32 sweepTxHash);
162
+
163
+ constructor(
164
+ address _bank,
165
+ address _relay,
166
+ uint256 _txProofDifficultyFactor
167
+ ) {
168
+ require(_bank != address(0), "Bank address cannot be zero");
169
+ bank = Bank(_bank);
170
+
171
+ require(_relay != address(0), "Relay address cannot be zero");
172
+ relay = IRelay(_relay);
173
+
174
+ txProofDifficultyFactor = _txProofDifficultyFactor;
175
+ }
176
+
177
+ /// @notice Allows the Governance to mark the given vault address as trusted
178
+ /// or no longer trusted. Vaults are not trusted by default.
179
+ /// Trusted vault must meet the following criteria:
180
+ /// - `IVault.receiveBalanceIncrease` must have a known, low gas
181
+ /// cost.
182
+ /// - `IVault.receiveBalanceIncrease` must never revert.
183
+ /// @dev Without restricting reveal only to trusted vaults, malicious
184
+ /// vaults not meeting the criteria would be able to nuke sweep proof
185
+ /// transactions executed by ECDSA wallet with deposits routed to
186
+ /// them.
187
+ /// @param vault The address of the vault
188
+ /// @param isTrusted flag indicating whether the vault is trusted or not
189
+ /// @dev Can only be called by the Governance.
190
+ function setVaultStatus(address vault, bool isTrusted) external onlyOwner {
191
+ isVaultTrusted[vault] = isTrusted;
192
+ emit VaultStatusUpdated(vault, isTrusted);
193
+ }
194
+
195
+ /// @notice Used by the depositor to reveal information about their P2(W)SH
196
+ /// Bitcoin deposit to the Bridge on Ethereum chain. The off-chain
197
+ /// wallet listens for revealed deposit events and may decide to
198
+ /// include the revealed deposit in the next executed sweep.
199
+ /// Information about the Bitcoin deposit can be revealed before or
200
+ /// after the Bitcoin transaction with P2(W)SH deposit is mined on
201
+ /// the Bitcoin chain. Worth noting, the gas cost of this function
202
+ /// scales with the number of P2(W)SH transaction inputs and
203
+ /// outputs. The deposit may be routed to one of the trusted vaults.
204
+ /// When a deposit is routed to a vault, vault gets notified when
205
+ /// the deposit gets swept and it may execute the appropriate action.
206
+ /// @param fundingTx Bitcoin funding transaction data, see `BitcoinTx.Info`
207
+ /// @param reveal Deposit reveal data, see `RevealInfo struct
208
+ /// @dev Requirements:
209
+ /// - `reveal.vault` must be 0x0 or point to a trusted vault
210
+ /// - `reveal.fundingOutputIndex` must point to the actual P2(W)SH
211
+ /// output of the BTC deposit transaction
212
+ /// - `reveal.depositor` must be the Ethereum address used in the
213
+ /// P2(W)SH BTC deposit transaction,
214
+ /// - `reveal.blindingFactor` must be the blinding factor used in the
215
+ /// P2(W)SH BTC deposit transaction,
216
+ /// - `reveal.walletPubKeyHash` must be the wallet pub key hash used in
217
+ /// the P2(W)SH BTC deposit transaction,
218
+ /// - `reveal.refundPubKeyHash` must be the refund pub key hash used in
219
+ /// the P2(W)SH BTC deposit transaction,
220
+ /// - `reveal.refundLocktime` must be the refund locktime used in the
221
+ /// P2(W)SH BTC deposit transaction,
222
+ /// - BTC deposit for the given `fundingTxHash`, `fundingOutputIndex`
223
+ /// can be revealed only one time.
224
+ ///
225
+ /// If any of these requirements is not met, the wallet _must_ refuse
226
+ /// to sweep the deposit and the depositor has to wait until the
227
+ /// deposit script unlocks to receive their BTC back.
228
+ function revealDeposit(
229
+ BitcoinTx.Info calldata fundingTx,
230
+ RevealInfo calldata reveal
231
+ ) external {
232
+ require(
233
+ reveal.vault == address(0) || isVaultTrusted[reveal.vault],
234
+ "Vault is not trusted"
235
+ );
236
+
237
+ bytes memory expectedScript =
238
+ abi.encodePacked(
239
+ hex"14", // Byte length of depositor Ethereum address.
240
+ reveal.depositor,
241
+ hex"75", // OP_DROP
242
+ hex"08", // Byte length of blinding factor value.
243
+ reveal.blindingFactor,
244
+ hex"75", // OP_DROP
245
+ hex"76", // OP_DUP
246
+ hex"a9", // OP_HASH160
247
+ hex"14", // Byte length of a compressed Bitcoin public key hash.
248
+ reveal.walletPubKeyHash,
249
+ hex"87", // OP_EQUAL
250
+ hex"63", // OP_IF
251
+ hex"ac", // OP_CHECKSIG
252
+ hex"67", // OP_ELSE
253
+ hex"76", // OP_DUP
254
+ hex"a9", // OP_HASH160
255
+ hex"14", // Byte length of a compressed Bitcoin public key hash.
256
+ reveal.refundPubKeyHash,
257
+ hex"88", // OP_EQUALVERIFY
258
+ hex"04", // Byte length of refund locktime value.
259
+ reveal.refundLocktime,
260
+ hex"b1", // OP_CHECKLOCKTIMEVERIFY
261
+ hex"75", // OP_DROP
262
+ hex"ac", // OP_CHECKSIG
263
+ hex"68" // OP_ENDIF
264
+ );
265
+
266
+ bytes memory fundingOutput =
267
+ fundingTx.outputVector.extractOutputAtIndex(
268
+ reveal.fundingOutputIndex
269
+ );
270
+ bytes memory fundingOutputHash = fundingOutput.extractHash();
271
+
272
+ if (fundingOutputHash.length == 20) {
273
+ // A 20-byte output hash is used by P2SH. That hash is constructed
274
+ // by applying OP_HASH160 on the locking script. A 20-byte output
275
+ // hash is used as well by P2PKH and P2WPKH (OP_HASH160 on the
276
+ // public key). However, since we compare the actual output hash
277
+ // with an expected locking script hash, this check will succeed only
278
+ // for P2SH transaction type with expected script hash value. For
279
+ // P2PKH and P2WPKH, it will fail on the output hash comparison with
280
+ // the expected locking script hash.
281
+ require(
282
+ keccak256(fundingOutputHash) ==
283
+ keccak256(expectedScript.hash160()),
284
+ "Wrong 20-byte script hash"
285
+ );
286
+ } else if (fundingOutputHash.length == 32) {
287
+ // A 32-byte output hash is used by P2WSH. That hash is constructed
288
+ // by applying OP_SHA256 on the locking script.
289
+ require(
290
+ fundingOutputHash.toBytes32() == sha256(expectedScript),
291
+ "Wrong 32-byte script hash"
292
+ );
293
+ } else {
294
+ revert("Wrong script hash length");
295
+ }
296
+
297
+ // Resulting TX hash is in native Bitcoin little-endian format.
298
+ bytes32 fundingTxHash =
299
+ abi
300
+ .encodePacked(
301
+ fundingTx
302
+ .version,
303
+ fundingTx
304
+ .inputVector,
305
+ fundingTx
306
+ .outputVector,
307
+ fundingTx
308
+ .locktime
309
+ )
310
+ .hash256();
311
+
312
+ DepositInfo storage deposit =
313
+ deposits[
314
+ uint256(
315
+ keccak256(
316
+ abi.encodePacked(
317
+ fundingTxHash,
318
+ reveal.fundingOutputIndex
319
+ )
320
+ )
321
+ )
322
+ ];
323
+ require(deposit.revealedAt == 0, "Deposit already revealed");
324
+
325
+ uint64 fundingOutputAmount = fundingOutput.extractValue();
326
+
327
+ // TODO: Check the amount against the dust threshold.
328
+
329
+ deposit.amount = fundingOutputAmount;
330
+ deposit.depositor = reveal.depositor;
331
+ /* solhint-disable-next-line not-rely-on-time */
332
+ deposit.revealedAt = uint32(block.timestamp);
333
+ deposit.vault = reveal.vault;
334
+
335
+ emit DepositRevealed(
336
+ fundingTxHash,
337
+ reveal.fundingOutputIndex,
338
+ reveal.depositor,
339
+ fundingOutputAmount,
340
+ reveal.blindingFactor,
341
+ reveal.walletPubKeyHash,
342
+ reveal.refundPubKeyHash,
343
+ reveal.refundLocktime,
344
+ reveal.vault
345
+ );
346
+ }
347
+
348
+ /// @notice Used by the wallet to prove the BTC deposit sweep transaction
349
+ /// and to update Bank balances accordingly. Sweep is only accepted
350
+ /// if it satisfies SPV proof.
351
+ ///
352
+ /// The function is performing Bank balance updates by first
353
+ /// computing the Bitcoin fee for the sweep transaction. The fee is
354
+ /// divided evenly between all swept deposits. Each depositor
355
+ /// receives a balance in the bank equal to the amount inferred
356
+ /// during the reveal transaction, minus their fee share.
357
+ ///
358
+ /// It is possible to prove the given sweep only one time.
359
+ /// @param sweepTx Bitcoin sweep transaction data
360
+ /// @param sweepProof Bitcoin sweep proof data
361
+ /// @param mainUtxo Data of the wallet's main UTXO, as currently known on
362
+ /// the Ethereum chain. If no main UTXO exists for the given wallet,
363
+ /// this parameter is ignored
364
+ /// @dev Requirements:
365
+ /// - `sweepTx` components must match the expected structure. See
366
+ /// `BitcoinTx.Info` docs for reference. Their values must exactly
367
+ /// correspond to appropriate Bitcoin transaction fields to produce
368
+ /// a provable transaction hash.
369
+ /// - The `sweepTx` should represent a Bitcoin transaction with 1..n
370
+ /// inputs. If the wallet has no main UTXO, all n inputs should
371
+ /// correspond to P2(W)SH revealed deposits UTXOs. If the wallet has
372
+ /// an existing main UTXO, one of the n inputs must point to that
373
+ /// main UTXO and remaining n-1 inputs should correspond to P2(W)SH
374
+ /// revealed deposits UTXOs. That transaction must have only
375
+ /// one P2(W)PKH output locking funds on the 20-byte wallet public
376
+ /// key hash.
377
+ /// - `sweepProof` components must match the expected structure. See
378
+ /// `BitcoinTx.Proof` docs for reference. The `bitcoinHeaders`
379
+ /// field must contain a valid number of block headers, not less
380
+ /// than the `txProofDifficultyFactor` contract constant.
381
+ /// - `mainUtxo` components must point to the recent main UTXO
382
+ /// of the given wallet, as currently known on the Ethereum chain.
383
+ /// If there is no main UTXO, this parameter is ignored.
384
+ function submitSweepProof(
385
+ BitcoinTx.Info calldata sweepTx,
386
+ BitcoinTx.Proof calldata sweepProof,
387
+ BitcoinTx.UTXO calldata mainUtxo
388
+ ) external {
389
+ // TODO: Fail early if the function call gets frontrunned. See discussion:
390
+ // https://github.com/keep-network/tbtc-v2/pull/106#discussion_r801745204
391
+
392
+ // The actual transaction proof is performed here. After that point, we
393
+ // can assume the transaction happened on Bitcoin chain and has
394
+ // a sufficient number of confirmations as determined by
395
+ // `txProofDifficultyFactor` constant.
396
+ bytes32 sweepTxHash = validateSweepTxProof(sweepTx, sweepProof);
397
+
398
+ // Process sweep transaction output and extract its target wallet
399
+ // public key hash and value.
400
+ (bytes20 walletPubKeyHash, uint64 sweepTxOutputValue) =
401
+ processSweepTxOutput(sweepTx.outputVector);
402
+
403
+ // TODO: Validate if `walletPubKeyHash` is a known and active wallet.
404
+
405
+ // Check if the main UTXO for given wallet exists. If so, validate
406
+ // passed main UTXO data against the stored hash and use them for
407
+ // further processing. If no main UTXO exists, use empty data.
408
+ BitcoinTx.UTXO memory resolvedMainUtxo =
409
+ BitcoinTx.UTXO(bytes32(0), 0, 0);
410
+ bytes32 mainUtxoHash = mainUtxos[walletPubKeyHash];
411
+ if (mainUtxoHash != bytes32(0)) {
412
+ require(
413
+ keccak256(
414
+ abi.encodePacked(
415
+ mainUtxo.txHash,
416
+ mainUtxo.txOutputIndex,
417
+ mainUtxo.txOutputValue
418
+ )
419
+ ) == mainUtxoHash,
420
+ "Invalid main UTXO data"
421
+ );
422
+ resolvedMainUtxo = mainUtxo;
423
+ }
424
+
425
+ // Process sweep transaction inputs and extract their value sum and
426
+ // all information needed to perform deposit bookkeeping.
427
+ (
428
+ uint256 sweepTxInputsValue,
429
+ address[] memory depositors,
430
+ uint256[] memory depositedAmounts
431
+ ) = processSweepTxInputs(sweepTx.inputVector, resolvedMainUtxo);
432
+
433
+ // Compute the sweep transaction fee which is a difference between
434
+ // inputs amounts sum and the output amount.
435
+ // TODO: Check fee against max fee.
436
+ uint256 fee = sweepTxInputsValue - sweepTxOutputValue;
437
+ // Calculate fee share by dividing the total fee by deposits count.
438
+ // TODO: Deal with precision loss by having the last depositor pay
439
+ // the higher fee than others if there is a change, just like it has
440
+ // been proposed for the redemption flow. See:
441
+ // https://github.com/keep-network/tbtc-v2/pull/128#discussion_r800555359.
442
+ uint256 feeShare = fee / depositedAmounts.length;
443
+ // Reduce each deposit amount by fee share value.
444
+ for (uint256 i = 0; i < depositedAmounts.length; i++) {
445
+ // We don't have to check if `feeShare` is bigger than the amount
446
+ // since we have the dust threshold preventing against too small
447
+ // deposits amounts.
448
+ depositedAmounts[i] -= feeShare;
449
+ }
450
+
451
+ // Record this sweep data and assign them to the wallet public key hash
452
+ // as new main UTXO. Transaction output index is always 0 as sweep
453
+ // transaction always contains only one output.
454
+ mainUtxos[walletPubKeyHash] = keccak256(
455
+ abi.encodePacked(sweepTxHash, uint32(0), sweepTxOutputValue)
456
+ );
457
+
458
+ emit DepositsSwept(walletPubKeyHash, sweepTxHash);
459
+
460
+ // Update depositors balances in the Bank.
461
+ bank.increaseBalances(depositors, depositedAmounts);
462
+
463
+ // TODO: Handle deposits having `vault` set.
464
+ }
465
+
466
+ /// @notice Validates the SPV proof of the Bitcoin sweep transaction.
467
+ /// Reverts in case the validation or proof verification fail.
468
+ /// @param sweepTx Bitcoin sweep transaction data
469
+ /// @param sweepProof Bitcoin sweep proof data
470
+ /// @return sweepTxHash Proven 32-byte sweep transaction hash.
471
+ function validateSweepTxProof(
472
+ BitcoinTx.Info calldata sweepTx,
473
+ BitcoinTx.Proof calldata sweepProof
474
+ ) internal view returns (bytes32 sweepTxHash) {
475
+ require(
476
+ sweepTx.inputVector.validateVin(),
477
+ "Invalid input vector provided"
478
+ );
479
+ require(
480
+ sweepTx.outputVector.validateVout(),
481
+ "Invalid output vector provided"
482
+ );
483
+
484
+ sweepTxHash = abi
485
+ .encodePacked(
486
+ sweepTx
487
+ .version,
488
+ sweepTx
489
+ .inputVector,
490
+ sweepTx
491
+ .outputVector,
492
+ sweepTx
493
+ .locktime
494
+ )
495
+ .hash256();
496
+
497
+ checkProofFromTxHash(sweepTxHash, sweepProof);
498
+
499
+ return sweepTxHash;
500
+ }
501
+
502
+ /// @notice Checks the given Bitcoin transaction hash against the SPV proof.
503
+ /// Reverts in case the check fails.
504
+ /// @param txHash 32-byte hash of the checked Bitcoin transaction
505
+ /// @param proof Bitcoin proof data
506
+ function checkProofFromTxHash(
507
+ bytes32 txHash,
508
+ BitcoinTx.Proof calldata proof
509
+ ) internal view {
510
+ require(
511
+ txHash.prove(
512
+ proof.bitcoinHeaders.extractMerkleRootLE(),
513
+ proof.merkleProof,
514
+ proof.txIndexInBlock
515
+ ),
516
+ "Tx merkle proof is not valid for provided header and tx hash"
517
+ );
518
+
519
+ evaluateProofDifficulty(proof.bitcoinHeaders);
520
+ }
521
+
522
+ /// @notice Evaluates the given Bitcoin proof difficulty against the actual
523
+ /// Bitcoin chain difficulty provided by the relay oracle.
524
+ /// Reverts in case the evaluation fails.
525
+ /// @param bitcoinHeaders Bitcoin headers chain being part of the SPV
526
+ /// proof. Used to extract the observed proof difficulty
527
+ function evaluateProofDifficulty(bytes memory bitcoinHeaders)
528
+ internal
529
+ view
530
+ {
531
+ uint256 requestedDiff = 0;
532
+ uint256 currentDiff = relay.getCurrentEpochDifficulty();
533
+ uint256 previousDiff = relay.getPrevEpochDifficulty();
534
+ uint256 firstHeaderDiff =
535
+ bitcoinHeaders.extractTarget().calculateDifficulty();
536
+
537
+ if (firstHeaderDiff == currentDiff) {
538
+ requestedDiff = currentDiff;
539
+ } else if (firstHeaderDiff == previousDiff) {
540
+ requestedDiff = previousDiff;
541
+ } else {
542
+ revert("Not at current or previous difficulty");
543
+ }
544
+
545
+ uint256 observedDiff = bitcoinHeaders.validateHeaderChain();
546
+
547
+ require(
548
+ observedDiff != ValidateSPV.getErrBadLength(),
549
+ "Invalid length of the headers chain"
550
+ );
551
+ require(
552
+ observedDiff != ValidateSPV.getErrInvalidChain(),
553
+ "Invalid headers chain"
554
+ );
555
+ require(
556
+ observedDiff != ValidateSPV.getErrLowWork(),
557
+ "Insufficient work in a header"
558
+ );
559
+
560
+ require(
561
+ observedDiff >= requestedDiff * txProofDifficultyFactor,
562
+ "Insufficient accumulated difficulty in header chain"
563
+ );
564
+ }
565
+
566
+ /// @notice Processes the Bitcoin sweep transaction output vector by
567
+ /// extracting the single output and using it to gain additional
568
+ /// information required for further processing (e.g. value and
569
+ /// wallet public key hash).
570
+ /// @param sweepTxOutputVector Bitcoin sweep transaction output vector.
571
+ /// This function assumes vector's structure is valid so it must be
572
+ /// validated using e.g. `BTCUtils.validateVout` function before
573
+ /// it is passed here
574
+ /// @return walletPubKeyHash 20-byte wallet public key hash.
575
+ /// @return value 8-byte sweep transaction output value.
576
+ function processSweepTxOutput(bytes memory sweepTxOutputVector)
577
+ internal
578
+ pure
579
+ returns (bytes20 walletPubKeyHash, uint64 value)
580
+ {
581
+ // To determine the total number of sweep transaction outputs, we need to
582
+ // parse the compactSize uint (VarInt) the output vector is prepended by.
583
+ // That compactSize uint encodes the number of vector elements using the
584
+ // format presented in:
585
+ // https://developer.bitcoin.org/reference/transactions.html#compactsize-unsigned-integers
586
+ // We don't need asserting the compactSize uint is parseable since it
587
+ // was already checked during `validateVout` validation.
588
+ // See `BitcoinTx.outputVector` docs for more details.
589
+ (, uint256 outputsCount) = sweepTxOutputVector.parseVarInt();
590
+ require(
591
+ outputsCount == 1,
592
+ "Sweep transaction must have a single output"
593
+ );
594
+
595
+ bytes memory output = sweepTxOutputVector.extractOutputAtIndex(0);
596
+ value = output.extractValue();
597
+ bytes memory walletPubKeyHashBytes = output.extractHash();
598
+ // The sweep transaction output should always be P2PKH or P2WPKH.
599
+ // In both cases, the wallet public key hash should be 20 bytes length.
600
+ require(
601
+ walletPubKeyHashBytes.length == 20,
602
+ "Wallet public key hash should have 20 bytes"
603
+ );
604
+ /* solhint-disable-next-line no-inline-assembly */
605
+ assembly {
606
+ walletPubKeyHash := mload(add(walletPubKeyHashBytes, 32))
607
+ }
608
+
609
+ return (walletPubKeyHash, value);
610
+ }
611
+
612
+ /// @notice Processes the Bitcoin sweep transaction input vector. It
613
+ /// extracts each input and tries to obtain associated deposit or
614
+ /// main UTXO data, depending on the input type. Reverts
615
+ /// if one of the inputs cannot be recognized as a pointer to a
616
+ /// revealed deposit or expected main UTXO.
617
+ /// This function also marks each processed deposit as swept.
618
+ /// @param sweepTxInputVector Bitcoin sweep transaction input vector.
619
+ /// This function assumes vector's structure is valid so it must be
620
+ /// validated using e.g. `BTCUtils.validateVin` function before
621
+ /// it is passed here
622
+ /// @param mainUtxo Data of the wallet's main UTXO. If no main UTXO
623
+ /// exists for the given the wallet, this parameter's fields should
624
+ /// be zeroed to bypass the main UTXO validation
625
+ /// @return inputsTotalValue Sum of all inputs values i.e. all deposits and
626
+ /// main UTXO value, if present.
627
+ /// @return depositors Addresses of depositors who performed processed
628
+ /// deposits. Ordered in the same order as deposits inputs in the
629
+ /// input vector. Size of this array is either equal to the
630
+ /// number of inputs (main UTXO doesn't exist) or less by one
631
+ /// (main UTXO exists and is pointed by one of the inputs).
632
+ /// @return depositedAmounts Amounts of deposits corresponding to processed
633
+ /// deposits. Ordered in the same order as deposits inputs in the
634
+ /// input vector. Size of this array is either equal to the
635
+ /// number of inputs (main UTXO doesn't exist) or less by one
636
+ /// (main UTXO exists and is pointed by one of the inputs).
637
+ function processSweepTxInputs(
638
+ bytes memory sweepTxInputVector,
639
+ BitcoinTx.UTXO memory mainUtxo
640
+ )
641
+ internal
642
+ returns (
643
+ uint256 inputsTotalValue,
644
+ address[] memory depositors,
645
+ uint256[] memory depositedAmounts
646
+ )
647
+ {
648
+ // If the passed `mainUtxo` parameter's values are zeroed, the main UTXO
649
+ // for the given wallet doesn't exist and it is not expected to be
650
+ // included in the sweep transaction input vector.
651
+ bool mainUtxoExpected = mainUtxo.txHash != bytes32(0);
652
+ bool mainUtxoFound = false;
653
+
654
+ // Determining the total number of sweep transaction inputs in the same
655
+ // way as for number of outputs. See `BitcoinTx.inputVector` docs for
656
+ // more details.
657
+ (uint256 inputsCompactSizeUintLength, uint256 inputsCount) =
658
+ sweepTxInputVector.parseVarInt();
659
+
660
+ // To determine the first input starting index, we must jump over
661
+ // the compactSize uint which prepends the input vector. One byte
662
+ // must be added because `BtcUtils.parseVarInt` does not include
663
+ // compactSize uint tag in the returned length.
664
+ //
665
+ // For >= 0 && <= 252, `BTCUtils.determineVarIntDataLengthAt`
666
+ // returns `0`, so we jump over one byte of compactSize uint.
667
+ //
668
+ // For >= 253 && <= 0xffff there is `0xfd` tag,
669
+ // `BTCUtils.determineVarIntDataLengthAt` returns `2` (no
670
+ // tag byte included) so we need to jump over 1+2 bytes of
671
+ // compactSize uint.
672
+ //
673
+ // Please refer `BTCUtils` library and compactSize uint
674
+ // docs in `BitcoinTx` library for more details.
675
+ uint256 inputStartingIndex = 1 + inputsCompactSizeUintLength;
676
+
677
+ // Determine the swept deposits count. If main UTXO is NOT expected,
678
+ // all inputs should be deposits. If main UTXO is expected, one input
679
+ // should point to that main UTXO.
680
+ depositors = new address[](
681
+ !mainUtxoExpected ? inputsCount : inputsCount - 1
682
+ );
683
+ depositedAmounts = new uint256[](depositors.length);
684
+
685
+ // Initialize helper variables.
686
+ uint256 processedDepositsCount = 0;
687
+
688
+ // Inputs processing loop.
689
+ for (uint256 i = 0; i < inputsCount; i++) {
690
+ // Check if we are at the end of the input vector.
691
+ if (inputStartingIndex >= sweepTxInputVector.length) {
692
+ break;
693
+ }
694
+
695
+ (bytes32 inputTxHash, uint32 inputTxIndex, uint256 inputLength) =
696
+ parseTxInputAt(sweepTxInputVector, inputStartingIndex);
697
+
698
+ DepositInfo storage deposit =
699
+ deposits[
700
+ uint256(
701
+ keccak256(abi.encodePacked(inputTxHash, inputTxIndex))
702
+ )
703
+ ];
704
+
705
+ if (deposit.revealedAt != 0) {
706
+ // If we entered here, that means the input was identified as
707
+ // a revealed deposit.
708
+ require(deposit.sweptAt == 0, "Deposit already swept");
709
+
710
+ if (processedDepositsCount == depositors.length) {
711
+ // If this condition is true, that means a deposit input
712
+ // took place of an expected main UTXO input.
713
+ // In other words, there is no expected main UTXO
714
+ // input and all inputs come from valid, revealed deposits.
715
+ revert(
716
+ "Expected main UTXO not present in sweep transaction inputs"
717
+ );
718
+ }
719
+
720
+ /* solhint-disable-next-line not-rely-on-time */
721
+ deposit.sweptAt = uint32(block.timestamp);
722
+
723
+ depositors[processedDepositsCount] = deposit.depositor;
724
+ depositedAmounts[processedDepositsCount] = deposit.amount;
725
+ inputsTotalValue += depositedAmounts[processedDepositsCount];
726
+
727
+ processedDepositsCount++;
728
+ } else if (
729
+ mainUtxoExpected != mainUtxoFound &&
730
+ mainUtxo.txHash == inputTxHash
731
+ ) {
732
+ // If we entered here, that means the input was identified as
733
+ // the expected main UTXO.
734
+ inputsTotalValue += mainUtxo.txOutputValue;
735
+ mainUtxoFound = true;
736
+ } else {
737
+ revert("Unknown input type");
738
+ }
739
+
740
+ // Make the `inputStartingIndex` pointing to the next input by
741
+ // increasing it by current input's length.
742
+ inputStartingIndex += inputLength;
743
+ }
744
+
745
+ // Construction of the input processing loop guarantees that:
746
+ // `processedDepositsCount == depositors.length == depositedAmounts.length`
747
+ // is always true at this point. We just use the first variable
748
+ // to assert the total count of swept deposit is bigger than zero.
749
+ require(
750
+ processedDepositsCount > 0,
751
+ "Sweep transaction must process at least one deposit"
752
+ );
753
+
754
+ // Assert the main UTXO was used as one of current sweep's inputs if
755
+ // it was actually expected.
756
+ require(
757
+ mainUtxoExpected == mainUtxoFound,
758
+ "Expected main UTXO not present in sweep transaction inputs"
759
+ );
760
+
761
+ return (inputsTotalValue, depositors, depositedAmounts);
762
+ }
763
+
764
+ /// @notice Parses a Bitcoin transaction input starting at the given index.
765
+ /// @param inputVector Bitcoin transaction input vector
766
+ /// @param inputStartingIndex Index the given input starts at
767
+ /// @return inputTxHash 32-byte hash of the Bitcoin transaction which is
768
+ /// pointed in the given input's outpoint.
769
+ /// @return inputTxIndex 4-byte index of the Bitcoin transaction output
770
+ /// which is pointed in the given input's outpoint.
771
+ /// @return inputLength Byte length of the given input.
772
+ /// @dev This function assumes vector's structure is valid so it must be
773
+ /// validated using e.g. `BTCUtils.validateVin` function before it
774
+ /// is passed here.
775
+ function parseTxInputAt(
776
+ bytes memory inputVector,
777
+ uint256 inputStartingIndex
778
+ )
779
+ internal
780
+ pure
781
+ returns (
782
+ bytes32 inputTxHash,
783
+ uint32 inputTxIndex,
784
+ uint256 inputLength
785
+ )
786
+ {
787
+ inputTxHash = inputVector.extractInputTxIdLeAt(inputStartingIndex);
788
+
789
+ inputTxIndex = BTCUtils.reverseUint32(
790
+ uint32(inputVector.extractTxIndexLeAt(inputStartingIndex))
791
+ );
792
+
793
+ inputLength = inputVector.determineInputLengthAt(inputStartingIndex);
794
+
795
+ return (inputTxHash, inputTxIndex, inputLength);
796
+ }
797
+
798
+ // TODO It is possible a malicious wallet can sweep deposits that can not
799
+ // be later proved on Ethereum. For example, a deposit with
800
+ // an incorrect amount revealed. We need to provide a function for honest
801
+ // depositors, next to sweep, to prove their swept balances on Ethereum
802
+ // selectively, based on deposits they have earlier received.
803
+ // (UPDATE PR #90: Is it still the case since amounts are inferred?)
804
+ }