@keep-network/tbtc-v2 0.1.1-dev.24 → 0.1.1-dev.27

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 (25) hide show
  1. package/artifacts/TBTC.json +10 -10
  2. package/artifacts/TBTCToken.json +10 -10
  3. package/artifacts/VendingMachine.json +11 -11
  4. package/artifacts/solcInputs/{2676c70e1dffa939dbf0519ef3304b34.json → e103ea0f293e8ca60f7bd00f669fc831.json} +10 -4
  5. package/build/contracts/GovernanceUtils.sol/GovernanceUtils.dbg.json +1 -1
  6. package/build/contracts/bank/Bank.sol/Bank.dbg.json +1 -1
  7. package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.dbg.json +1 -1
  8. package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.json +2 -2
  9. package/build/contracts/bridge/Bridge.sol/Bridge.dbg.json +1 -1
  10. package/build/contracts/bridge/Bridge.sol/Bridge.json +499 -18
  11. package/build/contracts/bridge/Bridge.sol/IRelay.dbg.json +1 -1
  12. package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.dbg.json +1 -1
  13. package/build/contracts/bridge/Frauds.sol/Frauds.dbg.json +4 -0
  14. package/build/contracts/bridge/Frauds.sol/Frauds.json +138 -0
  15. package/build/contracts/bridge/VendingMachine.sol/VendingMachine.dbg.json +1 -1
  16. package/build/contracts/bridge/Wallets.sol/Wallets.dbg.json +1 -1
  17. package/build/contracts/bridge/Wallets.sol/Wallets.json +53 -2
  18. package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
  19. package/build/contracts/vault/IVault.sol/IVault.dbg.json +1 -1
  20. package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +1 -1
  21. package/contracts/bridge/BitcoinTx.sol +10 -0
  22. package/contracts/bridge/Bridge.sol +304 -88
  23. package/contracts/bridge/Frauds.sol +531 -0
  24. package/contracts/bridge/Wallets.sol +173 -5
  25. package/package.json +1 -1
@@ -25,6 +25,7 @@ import "../bank/Bank.sol";
25
25
  import "./BitcoinTx.sol";
26
26
  import "./EcdsaLib.sol";
27
27
  import "./Wallets.sol";
28
+ import "./Frauds.sol";
28
29
 
29
30
  /// @title Interface for the Bitcoin relay
30
31
  /// @notice Contains only the methods needed by tBTC v2. The Bitcoin relay
@@ -61,6 +62,7 @@ contract Bridge is Ownable, EcdsaWalletOwner {
61
62
  using BTCUtils for bytes;
62
63
  using BTCUtils for uint256;
63
64
  using BytesLib for bytes;
65
+ using Frauds for Frauds.Data;
64
66
  using Wallets for Wallets.Data;
65
67
 
66
68
  /// @notice Represents data which must be revealed by the depositor during
@@ -261,6 +263,16 @@ contract Bridge is Ownable, EcdsaWalletOwner {
261
263
  /// validating them before attempting to execute a sweep.
262
264
  mapping(uint256 => DepositRequest) public deposits;
263
265
 
266
+ //TODO: Remember to update this map when implementing transferring funds
267
+ // between wallets (insert the main UTXO that was used as the input).
268
+ /// @notice Collection of main UTXOs that are honestly spent indexed by
269
+ /// keccak256(fundingTxHash | fundingOutputIndex). The fundingTxHash
270
+ /// is bytes32 (ordered as in Bitcoin internally) and
271
+ /// fundingOutputIndex an uint32. A main UTXO is considered honestly
272
+ /// spent if it was used as an input of a transaction that have been
273
+ /// proven in the Bridge.
274
+ mapping(uint256 => bool) public spentMainUTXOs;
275
+
264
276
  /// @notice Collection of all pending redemption requests indexed by
265
277
  /// redemption key built as
266
278
  /// keccak256(walletPubKeyHash | redeemerOutputScript). The
@@ -275,8 +287,6 @@ contract Bridge is Ownable, EcdsaWalletOwner {
275
287
  /// successfully
276
288
  /// - `notifyRedemptionTimeout` in case the request was reported
277
289
  /// to be timed out
278
- /// - `submitRedemptionFraudProof` in case the request was handled
279
- /// in an fraudulent way amount-wise.
280
290
  mapping(uint256 => RedemptionRequest) public pendingRedemptions;
281
291
 
282
292
  /// @notice Collection of all timed out redemptions requests indexed by
@@ -297,6 +307,10 @@ contract Bridge is Ownable, EcdsaWalletOwner {
297
307
  // slither-disable-next-line uninitialized-state
298
308
  mapping(uint256 => RedemptionRequest) public timedOutRedemptions;
299
309
 
310
+ /// @notice Contains parameters related to frauds and the collection of all
311
+ /// submitted fraud challenges.
312
+ Frauds.Data internal frauds;
313
+
300
314
  /// @notice State related with wallets.
301
315
  Wallets.Data internal wallets;
302
316
 
@@ -307,6 +321,8 @@ contract Bridge is Ownable, EcdsaWalletOwner {
307
321
  uint64 newMaxBtcBalance
308
322
  );
309
323
 
324
+ event WalletMaxAgeUpdated(uint32 newMaxAge);
325
+
310
326
  event NewWalletRequested();
311
327
 
312
328
  event NewWalletRegistered(
@@ -314,6 +330,16 @@ contract Bridge is Ownable, EcdsaWalletOwner {
314
330
  bytes20 indexed walletPubKeyHash
315
331
  );
316
332
 
333
+ event WalletMovingFunds(
334
+ bytes32 indexed ecdsaWalletID,
335
+ bytes20 indexed walletPubKeyHash
336
+ );
337
+
338
+ event WalletClosed(
339
+ bytes32 indexed ecdsaWalletID,
340
+ bytes20 indexed walletPubKeyHash
341
+ );
342
+
317
343
  event WalletTerminated(
318
344
  bytes32 indexed ecdsaWalletID,
319
345
  bytes20 indexed walletPubKeyHash
@@ -321,6 +347,20 @@ contract Bridge is Ownable, EcdsaWalletOwner {
321
347
 
322
348
  event VaultStatusUpdated(address indexed vault, bool isTrusted);
323
349
 
350
+ event FraudSlashingAmountUpdated(uint256 newFraudSlashingAmount);
351
+
352
+ event FraudNotifierRewardMultiplierUpdated(
353
+ uint256 newFraudNotifierRewardMultiplier
354
+ );
355
+
356
+ event FraudChallengeDefeatTimeoutUpdated(
357
+ uint256 newFraudChallengeDefeatTimeout
358
+ );
359
+
360
+ event FraudChallengeDepositAmountUpdated(
361
+ uint256 newFraudChallengeDepositAmount
362
+ );
363
+
324
364
  event DepositRevealed(
325
365
  bytes32 fundingTxHash,
326
366
  uint32 fundingOutputIndex,
@@ -349,6 +389,21 @@ contract Bridge is Ownable, EcdsaWalletOwner {
349
389
  bytes32 redemptionTxHash
350
390
  );
351
391
 
392
+ event FraudChallengeSubmitted(
393
+ bytes20 walletPublicKeyHash,
394
+ bytes32 sighash,
395
+ uint8 v,
396
+ bytes32 r,
397
+ bytes32 s
398
+ );
399
+
400
+ event FraudChallengeDefeated(bytes20 walletPublicKeyHash, bytes32 sighash);
401
+
402
+ event FraudChallengeDefeatTimedOut(
403
+ bytes20 walletPublicKeyHash,
404
+ bytes32 sighash
405
+ );
406
+
352
407
  constructor(
353
408
  address _bank,
354
409
  address _relay,
@@ -375,17 +430,23 @@ contract Bridge is Ownable, EcdsaWalletOwner {
375
430
  redemptionTreasuryFeeDivisor = 2000; // 1/2000 == 5bps == 0.05% == 0.0005
376
431
  redemptionTxMaxFee = 1000; // 1000 satoshi
377
432
  redemptionTimeout = 172800; // 48 hours
433
+ frauds.setSlashingAmount(10000 * 1e18); // 10000 T
434
+ frauds.setNotifierRewardMultiplier(100); // 100%
435
+ frauds.setChallengeDefeatTimeout(7 days);
436
+ frauds.setChallengeDepositAmount(2 ether);
378
437
 
379
438
  // TODO: Revisit initial values.
380
439
  wallets.init(_ecdsaWalletRegistry);
381
440
  wallets.setCreationPeriod(1 weeks);
382
441
  wallets.setBtcBalanceRange(1 * 1e8, 10 * 1e8); // [1 BTC, 10 BTC]
442
+ wallets.setMaxAge(26 weeks); // ~6 months
383
443
  }
384
444
 
385
445
  /// @notice Updates parameters used by the `Wallets` library.
386
446
  /// @param creationPeriod New value of the wallet creation period
387
447
  /// @param minBtcBalance New value of the minimum BTC balance
388
448
  /// @param maxBtcBalance New value of the maximum BTC balance
449
+ /// @param maxAge New value of the wallet maximum age
389
450
  /// @dev Requirements:
390
451
  /// - Caller must be the contract owner.
391
452
  /// - Minimum BTC balance must be greater than zero
@@ -393,29 +454,34 @@ contract Bridge is Ownable, EcdsaWalletOwner {
393
454
  function updateWalletsParameters(
394
455
  uint32 creationPeriod,
395
456
  uint64 minBtcBalance,
396
- uint64 maxBtcBalance
457
+ uint64 maxBtcBalance,
458
+ uint32 maxAge
397
459
  ) external onlyOwner {
398
460
  wallets.setCreationPeriod(creationPeriod);
399
461
  wallets.setBtcBalanceRange(minBtcBalance, maxBtcBalance);
462
+ wallets.setMaxAge(maxAge);
400
463
  }
401
464
 
402
465
  /// @return creationPeriod Value of the wallet creation period
403
466
  /// @return minBtcBalance Value of the minimum BTC balance
404
467
  /// @return maxBtcBalance Value of the maximum BTC balance
468
+ /// @return maxAge Value of the wallet max age
405
469
  function getWalletsParameters()
406
470
  external
407
471
  view
408
472
  returns (
409
473
  uint32 creationPeriod,
410
474
  uint64 minBtcBalance,
411
- uint64 maxBtcBalance
475
+ uint64 maxBtcBalance,
476
+ uint32 maxAge
412
477
  )
413
478
  {
414
479
  creationPeriod = wallets.creationPeriod;
415
480
  minBtcBalance = wallets.minBtcBalance;
416
481
  maxBtcBalance = wallets.maxBtcBalance;
482
+ maxAge = wallets.maxAge;
417
483
 
418
- return (creationPeriod, minBtcBalance, maxBtcBalance);
484
+ return (creationPeriod, minBtcBalance, maxBtcBalance, maxAge);
419
485
  }
420
486
 
421
487
  /// @notice Allows the Governance to mark the given vault address as trusted
@@ -478,13 +544,41 @@ contract Bridge is Ownable, EcdsaWalletOwner {
478
544
  wallets.registerNewWallet(ecdsaWalletID, publicKeyX, publicKeyY);
479
545
  }
480
546
 
481
- // TODO: Documentation.
547
+ /// @notice A callback function that is called by the ECDSA Wallet Registry
548
+ /// once a wallet heartbeat failure is detected.
549
+ /// @param publicKeyX Wallet's public key's X coordinate
550
+ /// @param publicKeyY Wallet's public key's Y coordinate
551
+ /// @dev Requirements:
552
+ /// - The only caller authorized to call this function is `registry`
553
+ /// - Wallet must be in Live state
482
554
  function __ecdsaWalletHeartbeatFailedCallback(
483
- bytes32 ecdsaWalletID,
555
+ bytes32,
484
556
  bytes32 publicKeyX,
485
557
  bytes32 publicKeyY
486
558
  ) external override {
487
- // TODO: Implementation.
559
+ wallets.notifyWalletHeartbeatFailed(publicKeyX, publicKeyY);
560
+ }
561
+
562
+ /// @notice Notifies that the wallet is either old enough or has too few
563
+ /// satoshis left and qualifies to be closed.
564
+ /// @param walletPubKeyHash 20-byte public key hash of the wallet
565
+ /// @param walletMainUtxo Data of the wallet's main UTXO, as currently
566
+ /// known on the Ethereum chain.
567
+ /// @dev Requirements:
568
+ /// - Wallet must not be set as the current active wallet
569
+ /// - Wallet must exceed the wallet maximum age OR the wallet BTC
570
+ /// balance must be lesser than the minimum threshold. If the latter
571
+ /// case is true, the `walletMainUtxo` components must point to the
572
+ /// recent main UTXO of the given wallet, as currently known on the
573
+ /// Ethereum chain. If the wallet has no main UTXO, this parameter
574
+ /// can be empty as it is ignored since the wallet balance is
575
+ /// assumed to be zero.
576
+ /// - Wallet must be in Live state
577
+ function notifyCloseableWallet(
578
+ bytes20 walletPubKeyHash,
579
+ BitcoinTx.UTXO calldata walletMainUtxo
580
+ ) external {
581
+ wallets.notifyCloseableWallet(walletPubKeyHash, walletMainUtxo);
488
582
  }
489
583
 
490
584
  /// @notice Gets details about a registered wallet.
@@ -988,6 +1082,15 @@ contract Bridge is Ownable, EcdsaWalletOwner {
988
1082
  // the expected main UTXO.
989
1083
  info.inputsTotalValue += mainUtxo.txOutputValue;
990
1084
  mainUtxoFound = true;
1085
+
1086
+ // Main UTXO used as an input, mark it as spent.
1087
+ spentMainUTXOs[
1088
+ uint256(
1089
+ keccak256(
1090
+ abi.encodePacked(outpointTxHash, outpointIndex)
1091
+ )
1092
+ )
1093
+ ] = true;
991
1094
  } else {
992
1095
  revert("Unknown input type");
993
1096
  }
@@ -1353,6 +1456,187 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1353
1456
  bank.transferBalance(treasury, outputsInfo.totalTreasuryFee);
1354
1457
  }
1355
1458
 
1459
+ /// @notice Submits a fraud challenge indicating that a UTXO being under
1460
+ /// wallet control was unlocked by the wallet but was not used
1461
+ /// according to the protocol rules. That means the wallet signed
1462
+ /// a transaction input pointing to that UTXO and there is a unique
1463
+ /// sighash and signature pair associated with that input. This
1464
+ /// function uses those parameters to create a fraud accusation that
1465
+ /// proves a given transaction input unlocking the given UTXO was
1466
+ /// actually signed by the wallet. This function cannot determine
1467
+ /// whether the transaction was actually broadcast and the input was
1468
+ /// consumed in a fraudulent way so it just opens a challenge period
1469
+ /// during which the wallet can defeat the challenge by submitting
1470
+ /// proof of a transaction that consumes the given input according
1471
+ /// to protocol rules. To prevent spurious allegations, the caller
1472
+ /// must deposit ETH that is returned back upon justified fraud
1473
+ /// challenge or confiscated otherwise.
1474
+ /// @param walletPublicKey The public key of the wallet in the uncompressed
1475
+ /// and unprefixed format (64 bytes)
1476
+ /// @param sighash The hash that was used to produce the ECDSA signature
1477
+ /// that is the subject of the fraud claim. This hash is constructed
1478
+ /// by applying double SHA-256 over a serialized subset of the
1479
+ /// transaction. The exact subset used as hash preimage depends on
1480
+ /// the transaction input the signature is produced for. See BIP-143
1481
+ /// for reference
1482
+ /// @param signature Bitcoin signature in the R/S/V format
1483
+ /// @dev Requirements:
1484
+ /// - Wallet behind `walletPubKey` must be in `Live` or `MovingFunds`
1485
+ /// state
1486
+ /// - The challenger must send appropriate amount of ETH used as
1487
+ /// fraud challenge deposit
1488
+ /// - The signature (represented by r, s and v) must be generated by
1489
+ /// the wallet behind `walletPubKey` during signing of `sighash`
1490
+ /// - Wallet can be challenged for the given signature only once
1491
+ /// TODO: Consider using wallet public key in the X/Y form to avoid slicing.
1492
+ function submitFraudChallenge(
1493
+ bytes calldata walletPublicKey,
1494
+ bytes32 sighash,
1495
+ BitcoinTx.RSVSignature calldata signature
1496
+ ) external payable {
1497
+ bytes memory compressedWalletPublicKey = EcdsaLib.compressPublicKey(
1498
+ walletPublicKey.slice32(0),
1499
+ walletPublicKey.slice32(32)
1500
+ );
1501
+ bytes20 walletPubKeyHash = compressedWalletPublicKey.hash160View();
1502
+
1503
+ Wallets.Wallet storage wallet = wallets.registeredWallets[
1504
+ walletPubKeyHash
1505
+ ];
1506
+
1507
+ require(
1508
+ wallet.state == Wallets.WalletState.Live ||
1509
+ wallet.state == Wallets.WalletState.MovingFunds,
1510
+ "Wallet is neither in Live nor MovingFunds state"
1511
+ );
1512
+
1513
+ frauds.submitFraudChallenge(
1514
+ walletPublicKey,
1515
+ walletPubKeyHash,
1516
+ sighash,
1517
+ signature
1518
+ );
1519
+ }
1520
+
1521
+ /// @notice Allows to defeat a pending fraud challenge against a wallet if
1522
+ /// the transaction that spends the UTXO follows the protocol rules.
1523
+ /// In order to defeat the challenge the same `walletPublicKey` and
1524
+ /// signature (represented by `r`, `s` and `v`) must be provided as
1525
+ /// were used in the fraud challenge. Additionally a preimage must
1526
+ /// be provided which was used to calculate the sighash during input
1527
+ /// signing. The fraud challenge defeat attempt will only succeed if
1528
+ /// the inputs in the preimage are considered honestly spent by the
1529
+ /// wallet. Therefore the transaction spending the UTXO must be
1530
+ /// proven in the Bridge before a challenge defeat is called.
1531
+ /// If successfully defeated, the fraud challenge is marked as
1532
+ /// resolved and the amount of ether deposited by the challenger is
1533
+ /// sent to the treasury.
1534
+ /// @param walletPublicKey The public key of the wallet in the uncompressed
1535
+ /// and unprefixed format (64 bytes)
1536
+ /// @param preimage The preimage which produces sighash used to generate the
1537
+ /// ECDSA signature that is the subject of the fraud claim. It is a
1538
+ /// serialized subset of the transaction. The exact subset used as
1539
+ /// the preimage depends on the transaction input the signature is
1540
+ /// produced for. See BIP-143 for reference
1541
+ /// @param witness Flag indicating whether the preimage was produced for a
1542
+ /// witness input. True for witness, false for non-witness input
1543
+ /// @dev Requirements:
1544
+ /// - `walletPublicKey` and `sighash` calculated as `hash256(preimage)`
1545
+ /// must identify an open fraud challenge
1546
+ /// - the preimage must be a valid preimage of a transaction generated
1547
+ /// according to the protocol rules and already proved in the Bridge
1548
+ /// - before a defeat attempt is made the transaction that spends the
1549
+ /// given UTXO must be proven in the Bridge
1550
+ /// TODO: Consider using wallet public key in the X/Y form to avoid slicing.
1551
+ function defeatFraudChallenge(
1552
+ bytes calldata walletPublicKey,
1553
+ bytes calldata preimage,
1554
+ bool witness
1555
+ ) external {
1556
+ uint256 utxoKey = frauds.unwrapChallenge(
1557
+ walletPublicKey,
1558
+ preimage,
1559
+ witness
1560
+ );
1561
+
1562
+ // Check that the UTXO key identifies a correctly spent UTXO.
1563
+ require(
1564
+ deposits[utxoKey].sweptAt > 0 || spentMainUTXOs[utxoKey],
1565
+ "Spent UTXO not found among correctly spent UTXOs"
1566
+ );
1567
+
1568
+ frauds.defeatChallenge(walletPublicKey, preimage, treasury);
1569
+ }
1570
+
1571
+ /// @notice Notifies about defeat timeout for the given fraud challenge.
1572
+ /// Can be called only if there was a fraud challenge identified by
1573
+ /// the provided `walletPublicKey` and `sighash` and it was not
1574
+ /// defeated on time. The amount of time that needs to pass after
1575
+ /// a fraud challenge is reported is indicated by the
1576
+ /// `challengeDefeatTimeout`. After a successful fraud challenge
1577
+ /// defeat timeout notification the fraud challenge is marked as
1578
+ /// resolved, the stake of each operator is slashed, the ether
1579
+ /// deposited is returned to the challenger and the challenger is
1580
+ /// rewarded.
1581
+ /// @param walletPublicKey The public key of the wallet in the uncompressed
1582
+ /// and unprefixed format (64 bytes)
1583
+ /// @param sighash The hash that was used to produce the ECDSA signature
1584
+ /// that is the subject of the fraud claim. This hash is constructed
1585
+ /// by applying double SHA-256 over a serialized subset of the
1586
+ /// transaction. The exact subset used as hash preimage depends on
1587
+ /// the transaction input the signature is produced for. See BIP-143
1588
+ /// for reference
1589
+ /// @dev Requirements:
1590
+ /// - `walletPublicKey`and `sighash` must identify an open fraud
1591
+ /// challenge
1592
+ /// - the amount of time indicated by `challengeDefeatTimeout` must
1593
+ /// pass after the challenge was reported
1594
+ /// TODO: Consider using wallet public key in the X/Y form to avoid slicing.
1595
+ function notifyFraudChallengeDefeatTimeout(
1596
+ bytes calldata walletPublicKey,
1597
+ bytes32 sighash
1598
+ ) external {
1599
+ frauds.notifyFraudChallengeDefeatTimeout(walletPublicKey, sighash);
1600
+ }
1601
+
1602
+ /// @notice Returns parameters used by the `Frauds` library.
1603
+ /// @return slashingAmount Value of the slashing amount
1604
+ /// @return notifierRewardMultiplier Value of the notifier reward multiplier
1605
+ /// @return challengeDefeatTimeout Value of the challenge defeat timeout
1606
+ /// @return challengeDepositAmount Value of the challenge deposit amount
1607
+ function getFraudParameters()
1608
+ external
1609
+ view
1610
+ returns (
1611
+ uint256 slashingAmount,
1612
+ uint256 notifierRewardMultiplier,
1613
+ uint256 challengeDefeatTimeout,
1614
+ uint256 challengeDepositAmount
1615
+ )
1616
+ {
1617
+ slashingAmount = frauds.slashingAmount;
1618
+ notifierRewardMultiplier = frauds.notifierRewardMultiplier;
1619
+ challengeDefeatTimeout = frauds.challengeDefeatTimeout;
1620
+ challengeDepositAmount = frauds.challengeDepositAmount;
1621
+
1622
+ return (
1623
+ slashingAmount,
1624
+ notifierRewardMultiplier,
1625
+ challengeDefeatTimeout,
1626
+ challengeDepositAmount
1627
+ );
1628
+ }
1629
+
1630
+ /// @notice Returns the fraud challenge identified by the given key built
1631
+ /// as keccak256(walletPublicKey|sighash|v|r|s).
1632
+ function fraudChallenges(uint256 challengeKey)
1633
+ external
1634
+ view
1635
+ returns (Frauds.FraudChallenge memory)
1636
+ {
1637
+ return frauds.challenges[challengeKey];
1638
+ }
1639
+
1356
1640
  /// @notice Validates whether the redemption Bitcoin transaction input
1357
1641
  /// vector contains a single input referring to the wallet's main
1358
1642
  /// UTXO. Reverts in case the validation fails.
@@ -1369,7 +1653,7 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1369
1653
  bytes memory redemptionTxInputVector,
1370
1654
  BitcoinTx.UTXO calldata mainUtxo,
1371
1655
  bytes20 walletPubKeyHash
1372
- ) internal view {
1656
+ ) internal {
1373
1657
  // Assert that main UTXO for passed wallet exists in storage.
1374
1658
  bytes32 mainUtxoHash = wallets
1375
1659
  .registeredWallets[walletPubKeyHash]
@@ -1400,6 +1684,15 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1400
1684
  mainUtxo.txOutputIndex == redemptionTxOutpointIndex,
1401
1685
  "Redemption transaction input must point to the wallet's main UTXO"
1402
1686
  );
1687
+
1688
+ // Main UTXO used as an input, mark it as spent.
1689
+ spentMainUTXOs[
1690
+ uint256(
1691
+ keccak256(
1692
+ abi.encodePacked(mainUtxo.txHash, mainUtxo.txOutputIndex)
1693
+ )
1694
+ )
1695
+ ] = true;
1403
1696
  }
1404
1697
 
1405
1698
  /// @notice Processes the Bitcoin redemption transaction input vector. It
@@ -1647,83 +1940,6 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1647
1940
  // 7. Reduce the `pendingRedemptionsValue` (`wallets` mapping) for
1648
1941
  // given wallet by request's redeemable amount computed as
1649
1942
  // `requestedAmount - treasuryFee`.
1650
- // 8. Punish the wallet, probably by slashing its operators.
1651
- // 9. Change wallet's state in `wallets` mapping to `MovingFunds` in
1652
- // order to prevent against new redemption requests hitting
1653
- // that wallet.
1654
- // 10. Expect the wallet to transfer its funds to another healthy
1655
- // wallet (just as in case of failed heartbeat). The wallet is
1656
- // expected to finish the already queued redemption requests
1657
- // before moving funds but we are not going to enforce it on-chain.
1658
-
1659
- // TODO: Function `submitRedemptionFraudProof`
1660
- //
1661
- // Deposit and redemption fraud proofs are challenging to implement
1662
- // and it may happen we will have to rely on the coverage pool
1663
- // (https://github.com/keep-network/coverage-pools) and DAO to
1664
- // reimburse unlucky depositors and bring back the balance to the
1665
- // system in case of a wallet fraud by liquidating a part of the
1666
- // coverage pool manually.
1667
- //
1668
- // The probability of 51-of-100 wallet being fraudulent is negligible:
1669
- // https://github.com/keep-network/tbtc-v2/blob/main/docs/rfc/rfc-2.adoc#111-group-size-and-threshold
1670
- // and the coverage pool would be there to bring the balance back in
1671
- // case we are unlucky and malicious wallet emerges.
1672
- //
1673
- // We do not want to slash for a misbehavior that is not provable
1674
- // on-chain and it is possible to construct such a Bitcoin transaction
1675
- // that is not provable on Ethereum, see
1676
- // https://consensys.net/diligence/blog/2020/05/tbtc-navigating-the-cross-chain-conundrum
1677
- //
1678
- // The algorithm described below assumes we will be able to prove the
1679
- // TX on Ethereum which may not always be the case. Consider the steps
1680
- // below as an idea, and not necessarily how this function will be
1681
- // implemented because it may happen this function will never be
1682
- // implemented, given the Bitcoin transaction size problems.
1683
- //
1684
- // The algorithm:
1685
- // 1. Take a `BitcoinTx.Info` and `BitcoinTx.Proof` of the
1686
- // fraudulent transaction. It should also accept `walletPubKeyHash`
1687
- // and index of fraudulent output. Probably index of fraudulent
1688
- // input will be also required if the transaction is supposed
1689
- // to have a bad input vector.
1690
- // 2. Perform SPV proof to make sure it occurred on Bitcoin chain.
1691
- // If not - revert.
1692
- // 3. Check if wallet state is Live or MovingFunds. If not, revert.
1693
- // 4. Validate the number of inputs. If there is one input and it
1694
- // points to the wallet's main UTXO - move to point 5. If there
1695
- // are multiple inputs and there is wallet's main UTXO in the set,
1696
- // check if this is a sweep transaction. If it's not a sweep,
1697
- // consider it as fraudulent and move to point 6.
1698
- // In all other cases revert the call.
1699
- // 5. Extract the output and analyze its type. The output is not
1700
- // a fraud and the call should be reverted ONLY IF one of the
1701
- // following conditions is true:
1702
- // - Output is a requested redemption held by `pendingRedemptions`
1703
- // and output value fulfills the request range. There is an
1704
- // open question if a misfunded request should be removed
1705
- // from `pendingRedemptions` (probably yes) and whether the
1706
- // redeemer should be reimbursed in case of an underfund.
1707
- // - Output is a timed out redemption held by `timedOutRedemptions`
1708
- // and output value fulfills the request range.
1709
- // - Output is a proper change i.e. a single output targeting
1710
- // the wallet PKH back and having a non-zero value.
1711
- // - Wallet is in MovingFunds state, the output points to the
1712
- // expected target wallet, have non-zero value, and is a single
1713
- // output in the vector.
1714
- // In all other cases consider the transaction as fraud and
1715
- // proceed to point 6.
1716
- // 6. Punish the wallet, probably by severely slashing its operators.
1717
- // 7. Change wallet's state in `wallets` mapping to `Terminated` in
1718
- // order to prevent against new redemption requests hitting
1719
- // that wallet. This also prevents against reporting a fraud
1720
- // multiple times for one transaction (see point 3) and blocks
1721
- // submission of sweep and redemption proofs. `Terminated` wallet
1722
- // is blocked in the Bridge forever. If the fraud was a mistake
1723
- // done by the wallet and the wallet is still honest deep in its
1724
- // heart, the wallet can coordinate off-chain to recover the BTC
1725
- // and donate it to another wallet. If they recover all of the
1726
- // remaining BTC, DAO might decide to reward them with tokens so
1727
- // that they can have at least some portion of their slashed
1728
- // tokens back.
1943
+ // 8. Call `wallets.notifyRedemptionTimedOut` to propagate timeout
1944
+ // consequences to the wallet.
1729
1945
  }