@keep-network/tbtc-v2 0.1.1-dev.10 → 0.1.1-dev.11
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.
- package/artifacts/TBTC.json +10 -10
- package/artifacts/TBTCToken.json +10 -10
- package/artifacts/VendingMachine.json +11 -11
- package/artifacts/solcInputs/{524094faac10a04084fcc411e06dab84.json → 50a502fc3a2e83304e38e355c5860eb6.json} +10 -4
- package/build/contracts/GovernanceUtils.sol/GovernanceUtils.dbg.json +1 -1
- package/build/contracts/bank/Bank.sol/Bank.dbg.json +1 -1
- package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.dbg.json +1 -1
- package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.json +2 -2
- package/build/contracts/bridge/Bridge.sol/Bridge.dbg.json +1 -1
- package/build/contracts/bridge/Bridge.sol/Bridge.json +180 -42
- package/build/contracts/bridge/Bridge.sol/IRelay.dbg.json +4 -0
- package/build/contracts/bridge/Bridge.sol/IRelay.json +37 -0
- package/build/contracts/bridge/VendingMachine.sol/VendingMachine.dbg.json +1 -1
- package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
- package/build/contracts/vault/IVault.sol/IVault.dbg.json +1 -1
- package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +1 -1
- package/contracts/bridge/BitcoinTx.sol +21 -0
- package/contracts/bridge/Bridge.sol +503 -45
- package/package.json +2 -2
|
@@ -19,9 +19,25 @@ import "@openzeppelin/contracts/access/Ownable.sol";
|
|
|
19
19
|
|
|
20
20
|
import {BTCUtils} from "@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol";
|
|
21
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";
|
|
22
25
|
|
|
26
|
+
import "../bank/Bank.sol";
|
|
23
27
|
import "./BitcoinTx.sol";
|
|
24
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
|
+
|
|
25
41
|
/// @title Bitcoin Bridge
|
|
26
42
|
/// @notice Bridge manages BTC deposit and redemption flow and is increasing and
|
|
27
43
|
/// decreasing balances in the Bank as a result of BTC deposit and
|
|
@@ -43,13 +59,16 @@ import "./BitcoinTx.sol";
|
|
|
43
59
|
/// @dev Bridge is an upgradeable component of the Bank.
|
|
44
60
|
contract Bridge is Ownable {
|
|
45
61
|
using BTCUtils for bytes;
|
|
62
|
+
using BTCUtils for uint256;
|
|
46
63
|
using BytesLib for bytes;
|
|
64
|
+
using ValidateSPV for bytes;
|
|
65
|
+
using ValidateSPV for bytes32;
|
|
47
66
|
|
|
48
67
|
/// @notice Represents data which must be revealed by the depositor during
|
|
49
68
|
/// deposit reveal.
|
|
50
69
|
struct RevealInfo {
|
|
51
70
|
// Index of the funding output belonging to the funding transaction.
|
|
52
|
-
|
|
71
|
+
uint32 fundingOutputIndex;
|
|
53
72
|
// Ethereum depositor address.
|
|
54
73
|
address depositor;
|
|
55
74
|
// The blinding factor as 8 bytes. Byte endianness doesn't matter
|
|
@@ -77,16 +96,31 @@ contract Bridge is Ownable {
|
|
|
77
96
|
struct DepositInfo {
|
|
78
97
|
// Ethereum depositor address.
|
|
79
98
|
address depositor;
|
|
80
|
-
// Deposit amount in satoshi
|
|
81
|
-
|
|
82
|
-
bytes8 amount;
|
|
99
|
+
// Deposit amount in satoshi.
|
|
100
|
+
uint64 amount;
|
|
83
101
|
// UNIX timestamp the deposit was revealed at.
|
|
84
102
|
uint32 revealedAt;
|
|
85
103
|
// Address of the Bank vault the deposit is routed to.
|
|
86
104
|
// Optional, can be 0x0.
|
|
87
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;
|
|
88
110
|
}
|
|
89
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
|
+
|
|
90
124
|
/// @notice Indicates if the vault with the given address is trusted or not.
|
|
91
125
|
/// Depositors can route their revealed deposits only to trusted
|
|
92
126
|
/// vaults and have trusted vaults notified about new deposits as
|
|
@@ -96,20 +130,27 @@ contract Bridge is Ownable {
|
|
|
96
130
|
/// address.
|
|
97
131
|
mapping(address => bool) public isVaultTrusted;
|
|
98
132
|
|
|
99
|
-
/// @notice Collection of all
|
|
133
|
+
/// @notice Collection of all revealed deposits indexed by
|
|
100
134
|
/// keccak256(fundingTxHash | fundingOutputIndex).
|
|
101
|
-
/// The fundingTxHash is LE bytes32 and fundingOutputIndex an
|
|
135
|
+
/// The fundingTxHash is LE bytes32 and fundingOutputIndex an uint32.
|
|
102
136
|
/// This mapping may contain valid and invalid deposits and the
|
|
103
137
|
/// wallet is responsible for validating them before attempting to
|
|
104
138
|
/// execute a sweep.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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);
|
|
108
148
|
|
|
109
149
|
event DepositRevealed(
|
|
110
150
|
bytes32 fundingTxHash,
|
|
111
|
-
|
|
151
|
+
uint32 fundingOutputIndex,
|
|
112
152
|
address depositor,
|
|
153
|
+
uint64 amount,
|
|
113
154
|
bytes8 blindingFactor,
|
|
114
155
|
bytes20 walletPubKeyHash,
|
|
115
156
|
bytes20 refundPubKeyHash,
|
|
@@ -117,7 +158,21 @@ contract Bridge is Ownable {
|
|
|
117
158
|
address vault
|
|
118
159
|
);
|
|
119
160
|
|
|
120
|
-
event
|
|
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
|
+
}
|
|
121
176
|
|
|
122
177
|
/// @notice Allows the Governance to mark the given vault address as trusted
|
|
123
178
|
/// or no longer trusted. Vaults are not trusted by default.
|
|
@@ -255,7 +310,7 @@ contract Bridge is Ownable {
|
|
|
255
310
|
.hash256();
|
|
256
311
|
|
|
257
312
|
DepositInfo storage deposit =
|
|
258
|
-
|
|
313
|
+
deposits[
|
|
259
314
|
uint256(
|
|
260
315
|
keccak256(
|
|
261
316
|
abi.encodePacked(
|
|
@@ -267,15 +322,9 @@ contract Bridge is Ownable {
|
|
|
267
322
|
];
|
|
268
323
|
require(deposit.revealedAt == 0, "Deposit already revealed");
|
|
269
324
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
// First 8 bytes (little-endian) of the funding output represents
|
|
274
|
-
// its value. To take the value, we need to jump over the first
|
|
275
|
-
// word determining the array length, load the array, and trim it
|
|
276
|
-
// by putting it to a bytes8.
|
|
277
|
-
fundingOutputAmount := mload(add(fundingOutput, 32))
|
|
278
|
-
}
|
|
325
|
+
uint64 fundingOutputAmount = fundingOutput.extractValue();
|
|
326
|
+
|
|
327
|
+
// TODO: Check the amount against the dust threshold.
|
|
279
328
|
|
|
280
329
|
deposit.amount = fundingOutputAmount;
|
|
281
330
|
deposit.depositor = reveal.depositor;
|
|
@@ -287,6 +336,7 @@ contract Bridge is Ownable {
|
|
|
287
336
|
fundingTxHash,
|
|
288
337
|
reveal.fundingOutputIndex,
|
|
289
338
|
reveal.depositor,
|
|
339
|
+
fundingOutputAmount,
|
|
290
340
|
reveal.blindingFactor,
|
|
291
341
|
reveal.walletPubKeyHash,
|
|
292
342
|
reveal.refundPubKeyHash,
|
|
@@ -306,35 +356,443 @@ contract Bridge is Ownable {
|
|
|
306
356
|
/// during the reveal transaction, minus their fee share.
|
|
307
357
|
///
|
|
308
358
|
/// It is possible to prove the given sweep only one time.
|
|
309
|
-
/// @param sweepTx Bitcoin sweep transaction data
|
|
310
|
-
/// @param
|
|
311
|
-
/// @param
|
|
312
|
-
///
|
|
313
|
-
///
|
|
314
|
-
|
|
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(
|
|
315
385
|
BitcoinTx.Info calldata sweepTx,
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
bytes memory bitcoinHeaders
|
|
386
|
+
BitcoinTx.Proof calldata sweepProof,
|
|
387
|
+
BitcoinTx.UTXO calldata mainUtxo
|
|
319
388
|
) external {
|
|
320
|
-
// TODO
|
|
321
|
-
//
|
|
322
|
-
|
|
323
|
-
//
|
|
324
|
-
//
|
|
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.
|
|
325
664
|
//
|
|
326
|
-
//
|
|
327
|
-
//
|
|
328
|
-
// equal to the amount by which Bank balances were increased.
|
|
665
|
+
// For >= 0 && <= 252, `BTCUtils.determineVarIntDataLengthAt`
|
|
666
|
+
// returns `0`, so we jump over one byte of compactSize uint.
|
|
329
667
|
//
|
|
330
|
-
//
|
|
331
|
-
//
|
|
332
|
-
//
|
|
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.
|
|
333
672
|
//
|
|
334
|
-
//
|
|
335
|
-
//
|
|
336
|
-
|
|
337
|
-
|
|
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);
|
|
338
796
|
}
|
|
339
797
|
|
|
340
798
|
// TODO It is possible a malicious wallet can sweep deposits that can not
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@keep-network/tbtc-v2",
|
|
3
|
-
"version": "0.1.1-dev.
|
|
3
|
+
"version": "0.1.1-dev.11+main.af5309c1c7457e9f1989cea4dc8b3df15077028f",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"files": [
|
|
6
6
|
"artifacts/",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"prepublishOnly": "./scripts/prepare-artifacts.sh --network $npm_config_network"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@keep-network/bitcoin-spv-sol": "3.
|
|
29
|
+
"@keep-network/bitcoin-spv-sol": "3.2.0-solc-0.8",
|
|
30
30
|
"@keep-network/tbtc": ">1.1.2-dev <1.1.2-pre",
|
|
31
31
|
"@openzeppelin/contracts": "^4.1.0",
|
|
32
32
|
"@tenderly/hardhat-tenderly": "^1.0.12",
|