@keep-network/tbtc-v2 0.1.1-dev.61 → 0.1.1-dev.64

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 (71) hide show
  1. package/artifacts/Bank.json +6 -6
  2. package/artifacts/Bridge.json +151 -117
  3. package/artifacts/Deposit.json +7 -7
  4. package/artifacts/DepositSweep.json +14 -13
  5. package/artifacts/EcdsaDkgValidator.json +1 -1
  6. package/artifacts/EcdsaInactivity.json +1 -1
  7. package/artifacts/Fraud.json +20 -10
  8. package/artifacts/KeepRegistry.json +1 -1
  9. package/artifacts/KeepStake.json +2 -2
  10. package/artifacts/KeepToken.json +2 -2
  11. package/artifacts/KeepTokenStaking.json +1 -1
  12. package/artifacts/MovingFunds.json +7 -7
  13. package/artifacts/NuCypherStakingEscrow.json +1 -1
  14. package/artifacts/NuCypherToken.json +2 -2
  15. package/artifacts/RandomBeaconStub.json +1 -1
  16. package/artifacts/Redemption.json +9 -9
  17. package/artifacts/ReimbursementPool.json +2 -2
  18. package/artifacts/Relay.json +11 -11
  19. package/artifacts/SortitionPool.json +2 -2
  20. package/artifacts/T.json +2 -2
  21. package/artifacts/TBTC.json +6 -6
  22. package/artifacts/TBTCToken.json +6 -6
  23. package/artifacts/TokenStaking.json +1 -1
  24. package/artifacts/TokenholderGovernor.json +9 -9
  25. package/artifacts/TokenholderTimelock.json +8 -8
  26. package/artifacts/VendingMachine.json +13 -13
  27. package/artifacts/VendingMachineKeep.json +1 -1
  28. package/artifacts/VendingMachineNuCypher.json +1 -1
  29. package/artifacts/WalletRegistry.json +2 -2
  30. package/artifacts/WalletRegistryGovernance.json +2 -2
  31. package/artifacts/Wallets.json +7 -7
  32. package/artifacts/solcInputs/{6ff443beb223cf0c26c6b81361aa1799.json → 2190be89eb3998f4cbf0924da2c5641c.json} +12 -9
  33. package/build/contracts/GovernanceUtils.sol/GovernanceUtils.dbg.json +1 -1
  34. package/build/contracts/bank/Bank.sol/Bank.dbg.json +1 -1
  35. package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.dbg.json +1 -1
  36. package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.json +2 -2
  37. package/build/contracts/bridge/Bridge.sol/Bridge.dbg.json +1 -1
  38. package/build/contracts/bridge/Bridge.sol/Bridge.json +69 -38
  39. package/build/contracts/bridge/BridgeState.sol/BridgeState.dbg.json +1 -1
  40. package/build/contracts/bridge/BridgeState.sol/BridgeState.json +2 -2
  41. package/build/contracts/bridge/Deposit.sol/Deposit.dbg.json +1 -1
  42. package/build/contracts/bridge/Deposit.sol/Deposit.json +2 -2
  43. package/build/contracts/bridge/DepositSweep.sol/DepositSweep.dbg.json +1 -1
  44. package/build/contracts/bridge/DepositSweep.sol/DepositSweep.json +2 -2
  45. package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.dbg.json +1 -1
  46. package/build/contracts/bridge/Fraud.sol/Fraud.dbg.json +1 -1
  47. package/build/contracts/bridge/Fraud.sol/Fraud.json +2 -2
  48. package/build/contracts/bridge/Heartbeat.sol/Heartbeat.dbg.json +1 -1
  49. package/build/contracts/bridge/Heartbeat.sol/Heartbeat.json +2 -2
  50. package/build/contracts/bridge/IRelay.sol/IRelay.dbg.json +1 -1
  51. package/build/contracts/bridge/MovingFunds.sol/MovingFunds.dbg.json +1 -1
  52. package/build/contracts/bridge/MovingFunds.sol/MovingFunds.json +2 -2
  53. package/build/contracts/bridge/Redemption.sol/OutboundTx.dbg.json +1 -1
  54. package/build/contracts/bridge/Redemption.sol/OutboundTx.json +2 -2
  55. package/build/contracts/bridge/Redemption.sol/Redemption.dbg.json +1 -1
  56. package/build/contracts/bridge/Redemption.sol/Redemption.json +2 -2
  57. package/build/contracts/bridge/VendingMachine.sol/VendingMachine.dbg.json +1 -1
  58. package/build/contracts/bridge/Wallets.sol/Wallets.dbg.json +1 -1
  59. package/build/contracts/bridge/Wallets.sol/Wallets.json +2 -2
  60. package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
  61. package/build/contracts/vault/DonationVault.sol/DonationVault.dbg.json +4 -0
  62. package/build/contracts/vault/DonationVault.sol/DonationVault.json +103 -0
  63. package/build/contracts/vault/IVault.sol/IVault.dbg.json +1 -1
  64. package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +1 -1
  65. package/contracts/bridge/Bridge.sol +49 -4
  66. package/contracts/bridge/DepositSweep.sol +93 -40
  67. package/contracts/bridge/Fraud.sol +81 -4
  68. package/contracts/bridge/Heartbeat.sol +5 -0
  69. package/contracts/vault/DonationVault.sol +125 -0
  70. package/export.json +23 -0
  71. package/package.json +1 -1
@@ -327,6 +327,15 @@ contract Bridge is Governable, EcdsaWalletOwner {
327
327
  /// @param mainUtxo Data of the wallet's main UTXO, as currently known on
328
328
  /// the Ethereum chain. If no main UTXO exists for the given wallet,
329
329
  /// this parameter is ignored
330
+ /// @param vault Optional address of the vault where all swept deposits
331
+ /// should be routed to. All deposits swept as part of the transaction
332
+ /// must have their `vault` parameters set to the same address.
333
+ /// If this parameter is set to an address of a trusted vault, swept
334
+ /// deposits are routed to that vault.
335
+ /// If this parameter is set to the zero address or to an address
336
+ /// of a non-trusted vault, swept deposits are not routed to a
337
+ /// vault but depositors' balances are increased in the Bank
338
+ /// individually.
330
339
  /// @dev Requirements:
331
340
  /// - `sweepTx` components must match the expected structure. See
332
341
  /// `BitcoinTx.Info` docs for reference. Their values must exactly
@@ -340,6 +349,9 @@ contract Bridge is Governable, EcdsaWalletOwner {
340
349
  /// revealed deposits UTXOs. That transaction must have only
341
350
  /// one P2(W)PKH output locking funds on the 20-byte wallet public
342
351
  /// key hash.
352
+ /// - All revealed deposits that are swept by `sweepTx` must have
353
+ /// their `vault` parameters set to the same address as the address
354
+ /// passed in the `vault` function parameter.
343
355
  /// - `sweepProof` components must match the expected structure. See
344
356
  /// `BitcoinTx.Proof` docs for reference. The `bitcoinHeaders`
345
357
  /// field must contain a valid number of block headers, not less
@@ -350,9 +362,10 @@ contract Bridge is Governable, EcdsaWalletOwner {
350
362
  function submitDepositSweepProof(
351
363
  BitcoinTx.Info calldata sweepTx,
352
364
  BitcoinTx.Proof calldata sweepProof,
353
- BitcoinTx.UTXO calldata mainUtxo
365
+ BitcoinTx.UTXO calldata mainUtxo,
366
+ address vault
354
367
  ) external {
355
- self.submitDepositSweepProof(sweepTx, sweepProof, mainUtxo);
368
+ self.submitDepositSweepProof(sweepTx, sweepProof, mainUtxo, vault);
356
369
  }
357
370
 
358
371
  /// @notice Requests redemption of the given amount from the specified
@@ -844,8 +857,8 @@ contract Bridge is Governable, EcdsaWalletOwner {
844
857
  /// to protocol rules. To prevent spurious allegations, the caller
845
858
  /// must deposit ETH that is returned back upon justified fraud
846
859
  /// challenge or confiscated otherwise.
847
- ///@param walletPublicKey The public key of the wallet in the uncompressed
848
- /// and unprefixed format (64 bytes)
860
+ /// @param walletPublicKey The public key of the wallet in the uncompressed
861
+ /// and unprefixed format (64 bytes)
849
862
  /// @param sighash The hash that was used to produce the ECDSA signature
850
863
  /// that is the subject of the fraud claim. This hash is constructed
851
864
  /// by applying double SHA-256 over a serialized subset of the
@@ -905,6 +918,38 @@ contract Bridge is Governable, EcdsaWalletOwner {
905
918
  self.defeatFraudChallenge(walletPublicKey, preimage, witness);
906
919
  }
907
920
 
921
+ /// @notice Allows to defeat a pending fraud challenge against a wallet by
922
+ /// proving the sighash and signature were produced for an off-chain
923
+ /// wallet heartbeat message following a strict format.
924
+ /// In order to defeat the challenge the same `walletPublicKey` and
925
+ /// signature (represented by `r`, `s` and `v`) must be provided as
926
+ /// were used to calculate the sighash during heartbeat message
927
+ /// signing. The fraud challenge defeat attempt will only succeed if
928
+ /// the signed message follows a strict format required for
929
+ /// heartbeat messages. If successfully defeated, the fraud
930
+ /// challenge is marked as resolved and the amount of ether
931
+ /// deposited by the challenger is sent to the treasury.
932
+ /// @param walletPublicKey The public key of the wallet in the uncompressed
933
+ /// and unprefixed format (64 bytes)
934
+ /// @param heartbeatMessage Off-chain heartbeat message meeting the heartbeat
935
+ /// message format requirements which produces sighash used to
936
+ /// generate the ECDSA signature that is the subject of the fraud
937
+ /// claim
938
+ /// @dev Requirements:
939
+ /// - `walletPublicKey` and `sighash` calculated as
940
+ /// `hash256(heartbeatMessage)` must identify an open fraud challenge
941
+ /// - `heartbeatMessage` must follow a strict format of heartbeat
942
+ /// messages
943
+ function defeatFraudChallengeWithHeartbeat(
944
+ bytes calldata walletPublicKey,
945
+ bytes calldata heartbeatMessage
946
+ ) external {
947
+ self.defeatFraudChallengeWithHeartbeat(
948
+ walletPublicKey,
949
+ heartbeatMessage
950
+ );
951
+ }
952
+
908
953
  /// @notice Notifies about defeat timeout for the given fraud challenge.
909
954
  /// Can be called only if there was a fraud challenge identified by
910
955
  /// the provided `walletPublicKey` and `sighash` and it was not
@@ -39,6 +39,29 @@ library DepositSweep {
39
39
 
40
40
  using BTCUtils for bytes;
41
41
 
42
+ /// @notice Represents temporary information needed during the processing
43
+ /// of the deposit sweep Bitcoin transaction inputs. This structure
44
+ /// is an internal one and should not be exported outside of the
45
+ /// deposit sweep transaction processing code.
46
+ /// @dev Allows to mitigate "stack too deep" errors on EVM.
47
+ struct DepositSweepTxInputsProcessingInfo {
48
+ // Input vector of the deposit sweep Bitcoin transaction. It is
49
+ // assumed the vector's structure is valid so it must be validated
50
+ // using e.g. `BTCUtils.validateVin` function before being used
51
+ // during the processing. The validation is usually done as part
52
+ // of the `BitcoinTx.validateProof` call that checks the SPV proof.
53
+ bytes sweepTxInputVector;
54
+ // Data of the wallet's main UTXO. If no main UTXO exists for the given
55
+ // sweeping wallet, this parameter's fields should be zeroed to bypass
56
+ // the main UTXO validation
57
+ BitcoinTx.UTXO mainUtxo;
58
+ // Address of the vault where all swept deposits should be routed to.
59
+ // It is used to validate whether all swept deposits have been revealed
60
+ // with the same `vault` parameter. It is an optional parameter.
61
+ // Set to zero address if deposits are not routed to a vault.
62
+ address vault;
63
+ }
64
+
42
65
  /// @notice Represents an outcome of the sweep Bitcoin transaction
43
66
  /// inputs processing.
44
67
  struct DepositSweepTxInputsInfo {
@@ -83,6 +106,15 @@ library DepositSweep {
83
106
  /// @param mainUtxo Data of the wallet's main UTXO, as currently known on
84
107
  /// the Ethereum chain. If no main UTXO exists for the given wallet,
85
108
  /// this parameter is ignored
109
+ /// @param vault Optional address of the vault where all swept deposits
110
+ /// should be routed to. All deposits swept as part of the transaction
111
+ /// must have their `vault` parameters set to the same address.
112
+ /// If this parameter is set to an address of a trusted vault, swept
113
+ /// deposits are routed to that vault.
114
+ /// If this parameter is set to the zero address or to an address
115
+ /// of a non-trusted vault, swept deposits are not routed to a
116
+ /// vault but depositors' balances are increased in the Bank
117
+ /// individually.
86
118
  /// @dev Requirements:
87
119
  /// - `sweepTx` components must match the expected structure. See
88
120
  /// `BitcoinTx.Info` docs for reference. Their values must exactly
@@ -96,6 +128,9 @@ library DepositSweep {
96
128
  /// revealed deposits UTXOs. That transaction must have only
97
129
  /// one P2(W)PKH output locking funds on the 20-byte wallet public
98
130
  /// key hash.
131
+ /// - All revealed deposits that are swept by `sweepTx` must have
132
+ /// their `vault` parameters set to the same address as the address
133
+ /// passed in the `vault` function parameter.
99
134
  /// - `sweepProof` components must match the expected structure. See
100
135
  /// `BitcoinTx.Proof` docs for reference. The `bitcoinHeaders`
101
136
  /// field must contain a valid number of block headers, not less
@@ -107,7 +142,8 @@ library DepositSweep {
107
142
  BridgeState.Storage storage self,
108
143
  BitcoinTx.Info calldata sweepTx,
109
144
  BitcoinTx.Proof calldata sweepProof,
110
- BitcoinTx.UTXO calldata mainUtxo
145
+ BitcoinTx.UTXO calldata mainUtxo,
146
+ address vault
111
147
  ) external {
112
148
  // The actual transaction proof is performed here. After that point, we
113
149
  // can assume the transaction happened on Bitcoin chain and has
@@ -132,8 +168,11 @@ library DepositSweep {
132
168
  DepositSweepTxInputsInfo
133
169
  memory inputsInfo = processDepositSweepTxInputs(
134
170
  self,
135
- sweepTx.inputVector,
136
- resolvedMainUtxo
171
+ DepositSweepTxInputsProcessingInfo(
172
+ sweepTx.inputVector,
173
+ resolvedMainUtxo,
174
+ vault
175
+ )
137
176
  );
138
177
 
139
178
  // Helper variable that will hold the sum of treasury fees paid by
@@ -189,15 +228,26 @@ library DepositSweep {
189
228
  // slither-disable-next-line reentrancy-events
190
229
  emit DepositsSwept(walletPubKeyHash, sweepTxHash);
191
230
 
192
- // Update depositors balances in the Bank.
193
- self.bank.increaseBalances(
194
- inputsInfo.depositors,
195
- inputsInfo.depositedAmounts
196
- );
231
+ if (vault != address(0) && self.isVaultTrusted[vault]) {
232
+ // If the `vault` address is not zero and belongs to a trusted
233
+ // vault, route the deposits to that vault.
234
+ self.bank.increaseBalanceAndCall(
235
+ vault,
236
+ inputsInfo.depositors,
237
+ inputsInfo.depositedAmounts
238
+ );
239
+ } else {
240
+ // If the `vault` address is zero or belongs to a non-trusted
241
+ // vault, increase balances in the Bank individually for each
242
+ // depositor.
243
+ self.bank.increaseBalances(
244
+ inputsInfo.depositors,
245
+ inputsInfo.depositedAmounts
246
+ );
247
+ }
248
+
197
249
  // Pass the treasury fee to the treasury address.
198
250
  self.bank.increaseBalance(self.treasury, totalTreasuryFee);
199
-
200
- // TODO: Handle deposits having `vault` set.
201
251
  }
202
252
 
203
253
  /// @notice Resolves sweeping wallet based on the provided wallet public key
@@ -299,32 +349,23 @@ library DepositSweep {
299
349
  /// if one of the inputs cannot be recognized as a pointer to a
300
350
  /// revealed deposit or expected main UTXO.
301
351
  /// This function also marks each processed deposit as swept.
302
- /// @param sweepTxInputVector Bitcoin sweep transaction input vector.
303
- /// This function assumes vector's structure is valid so it must be
304
- /// validated using e.g. `BTCUtils.validateVin` function before
305
- /// it is passed here
306
- /// @param mainUtxo Data of the wallet's main UTXO. If no main UTXO
307
- /// exists for the given the wallet, this parameter's fields should
308
- /// be zeroed to bypass the main UTXO validation
309
- /// @return info Outcomes of the processing.
352
+ /// @return resultInfo Outcomes of the processing.
310
353
  function processDepositSweepTxInputs(
311
354
  BridgeState.Storage storage self,
312
- bytes memory sweepTxInputVector,
313
- BitcoinTx.UTXO memory mainUtxo
314
- ) internal returns (DepositSweepTxInputsInfo memory info) {
355
+ DepositSweepTxInputsProcessingInfo memory processInfo
356
+ ) internal returns (DepositSweepTxInputsInfo memory resultInfo) {
315
357
  // If the passed `mainUtxo` parameter's values are zeroed, the main UTXO
316
358
  // for the given wallet doesn't exist and it is not expected to be
317
359
  // included in the sweep transaction input vector.
318
- bool mainUtxoExpected = mainUtxo.txHash != bytes32(0);
360
+ bool mainUtxoExpected = processInfo.mainUtxo.txHash != bytes32(0);
319
361
  bool mainUtxoFound = false;
320
362
 
321
363
  // Determining the total number of sweep transaction inputs in the same
322
364
  // way as for number of outputs. See `BitcoinTx.inputVector` docs for
323
365
  // more details.
324
- (
325
- uint256 inputsCompactSizeUintLength,
326
- uint256 inputsCount
327
- ) = sweepTxInputVector.parseVarInt();
366
+ (uint256 inputsCompactSizeUintLength, uint256 inputsCount) = processInfo
367
+ .sweepTxInputVector
368
+ .parseVarInt();
328
369
 
329
370
  // To determine the first input starting index, we must jump over
330
371
  // the compactSize uint which prepends the input vector. One byte
@@ -346,11 +387,13 @@ library DepositSweep {
346
387
  // Determine the swept deposits count. If main UTXO is NOT expected,
347
388
  // all inputs should be deposits. If main UTXO is expected, one input
348
389
  // should point to that main UTXO.
349
- info.depositors = new address[](
390
+ resultInfo.depositors = new address[](
350
391
  !mainUtxoExpected ? inputsCount : inputsCount - 1
351
392
  );
352
- info.depositedAmounts = new uint256[](info.depositors.length);
353
- info.treasuryFees = new uint256[](info.depositors.length);
393
+ resultInfo.depositedAmounts = new uint256[](
394
+ resultInfo.depositors.length
395
+ );
396
+ resultInfo.treasuryFees = new uint256[](resultInfo.depositors.length);
354
397
 
355
398
  // Initialize helper variables.
356
399
  uint256 processedDepositsCount = 0;
@@ -362,7 +405,7 @@ library DepositSweep {
362
405
  uint32 outpointIndex,
363
406
  uint256 inputLength
364
407
  ) = parseDepositSweepTxInputAt(
365
- sweepTxInputVector,
408
+ processInfo.sweepTxInputVector,
366
409
  inputStartingIndex
367
410
  );
368
411
 
@@ -377,7 +420,12 @@ library DepositSweep {
377
420
  // a revealed deposit.
378
421
  require(deposit.sweptAt == 0, "Deposit already swept");
379
422
 
380
- if (processedDepositsCount == info.depositors.length) {
423
+ require(
424
+ deposit.vault == processInfo.vault,
425
+ "Deposit should be routed to another vault"
426
+ );
427
+
428
+ if (processedDepositsCount == resultInfo.depositors.length) {
381
429
  // If this condition is true, that means a deposit input
382
430
  // took place of an expected main UTXO input.
383
431
  // In other words, there is no expected main UTXO
@@ -390,22 +438,27 @@ library DepositSweep {
390
438
  /* solhint-disable-next-line not-rely-on-time */
391
439
  deposit.sweptAt = uint32(block.timestamp);
392
440
 
393
- info.depositors[processedDepositsCount] = deposit.depositor;
394
- info.depositedAmounts[processedDepositsCount] = deposit.amount;
395
- info.inputsTotalValue += info.depositedAmounts[
441
+ resultInfo.depositors[processedDepositsCount] = deposit
442
+ .depositor;
443
+ resultInfo.depositedAmounts[processedDepositsCount] = deposit
444
+ .amount;
445
+ resultInfo.inputsTotalValue += resultInfo.depositedAmounts[
396
446
  processedDepositsCount
397
447
  ];
398
- info.treasuryFees[processedDepositsCount] = deposit.treasuryFee;
448
+ resultInfo.treasuryFees[processedDepositsCount] = deposit
449
+ .treasuryFee;
399
450
 
400
451
  processedDepositsCount++;
401
452
  } else if (
402
453
  mainUtxoExpected != mainUtxoFound &&
403
- mainUtxo.txHash == outpointTxHash &&
404
- mainUtxo.txOutputIndex == outpointIndex
454
+ processInfo.mainUtxo.txHash == outpointTxHash &&
455
+ processInfo.mainUtxo.txOutputIndex == outpointIndex
405
456
  ) {
406
457
  // If we entered here, that means the input was identified as
407
458
  // the expected main UTXO.
408
- info.inputsTotalValue += mainUtxo.txOutputValue;
459
+ resultInfo.inputsTotalValue += processInfo
460
+ .mainUtxo
461
+ .txOutputValue;
409
462
  mainUtxoFound = true;
410
463
 
411
464
  // Main UTXO used as an input, mark it as spent.
@@ -426,7 +479,7 @@ library DepositSweep {
426
479
  }
427
480
 
428
481
  // Construction of the input processing loop guarantees that:
429
- // `processedDepositsCount == info.depositors.length == info.depositedAmounts.length`
482
+ // `processedDepositsCount == resultInfo.depositors.length == resultInfo.depositedAmounts.length`
430
483
  // is always true at this point. We just use the first variable
431
484
  // to assert the total count of swept deposit is bigger than zero.
432
485
  require(
@@ -441,7 +494,7 @@ library DepositSweep {
441
494
  "Expected main UTXO not present in sweep transaction inputs"
442
495
  );
443
496
 
444
- return info;
497
+ return resultInfo;
445
498
  }
446
499
 
447
500
  /// @notice Parses a Bitcoin transaction input starting at the given index.
@@ -22,6 +22,7 @@ import {CheckBitcoinSigs} from "@keep-network/bitcoin-spv-sol/contracts/CheckBit
22
22
  import "./BitcoinTx.sol";
23
23
  import "./EcdsaLib.sol";
24
24
  import "./BridgeState.sol";
25
+ import "./Heartbeat.sol";
25
26
  import "./MovingFunds.sol";
26
27
  import "./Wallets.sol";
27
28
 
@@ -38,10 +39,18 @@ import "./Wallets.sol";
38
39
  /// signature must be provided as were used to calculate the sighash during
39
40
  /// the challenge. The wallet provides the preimage which produces sighash
40
41
  /// used to generate the ECDSA signature that is the subject of the fraud
41
- /// claim. The fraud challenge defeat attempt will only succeed if the
42
- /// inputs in the preimage are considered honestly spent by the wallet.
43
- /// Therefore the transaction spending the UTXO must be proven in the
44
- /// Bridge before a challenge defeat is called.
42
+ /// claim.
43
+ ///
44
+ /// The fraud challenge defeat attempt will succeed if the inputs in the
45
+ /// preimage are considered honestly spent by the wallet. Therefore the
46
+ /// transaction spending the UTXO must be proven in the Bridge before
47
+ /// a challenge defeat is called.
48
+ ///
49
+ /// Another option is when a malicious wallet member used a signed heartbeat
50
+ /// message periodically produced by the wallet off-chain to challenge the
51
+ /// wallet for a fraud. Anyone from the wallet can defeat the challenge by
52
+ /// proving the sighash and signature were produced for a heartbeat message
53
+ /// following a strict format.
45
54
  library Fraud {
46
55
  using Wallets for BridgeState.Storage;
47
56
 
@@ -73,6 +82,10 @@ library Fraud {
73
82
 
74
83
  event FraudChallengeDefeatTimedOut(
75
84
  bytes20 walletPubKeyHash,
85
+ // Sighash calculated as a Bitcoin's hash256 (double sha2) of:
86
+ // - a preimage of a transaction spending UTXO according to the protocol
87
+ // rules OR
88
+ // - a valid heartbeat message produced by the wallet off-chain.
76
89
  bytes32 sighash
77
90
  );
78
91
 
@@ -234,6 +247,69 @@ library Fraud {
234
247
  "Spent UTXO not found among correctly spent UTXOs"
235
248
  );
236
249
 
250
+ resolveFraudChallenge(self, walletPublicKey, challenge, sighash);
251
+ }
252
+
253
+ /// @notice Allows to defeat a pending fraud challenge against a wallet by
254
+ /// proving the sighash and signature were produced for an off-chain
255
+ /// wallet heartbeat message following a strict format.
256
+ /// In order to defeat the challenge the same `walletPublicKey` and
257
+ /// signature (represented by `r`, `s` and `v`) must be provided as
258
+ /// were used to calculate the sighash during heartbeat message
259
+ /// signing. The fraud challenge defeat attempt will only succeed if
260
+ /// the signed message follows a strict format required for
261
+ /// heartbeat messages. If successfully defeated, the fraud
262
+ /// challenge is marked as resolved and the amount of ether
263
+ /// deposited by the challenger is sent to the treasury.
264
+ /// @param walletPublicKey The public key of the wallet in the uncompressed
265
+ /// and unprefixed format (64 bytes)
266
+ /// @param heartbeatMessage Off-chain heartbeat message meeting the heartbeat
267
+ /// message format requirements which produces sighash used to
268
+ /// generate the ECDSA signature that is the subject of the fraud
269
+ /// claim
270
+ /// @dev Requirements:
271
+ /// - `walletPublicKey` and `sighash` calculated as
272
+ /// `hash256(heartbeatMessage)` must identify an open fraud challenge
273
+ /// - `heartbeatMessage` must follow a strict format of heartbeat
274
+ /// messages
275
+ function defeatFraudChallengeWithHeartbeat(
276
+ BridgeState.Storage storage self,
277
+ bytes calldata walletPublicKey,
278
+ bytes calldata heartbeatMessage
279
+ ) external {
280
+ bytes32 sighash = heartbeatMessage.hash256();
281
+
282
+ uint256 challengeKey = uint256(
283
+ keccak256(abi.encodePacked(walletPublicKey, sighash))
284
+ );
285
+
286
+ FraudChallenge storage challenge = self.fraudChallenges[challengeKey];
287
+
288
+ require(challenge.reportedAt > 0, "Fraud challenge does not exist");
289
+ require(
290
+ !challenge.resolved,
291
+ "Fraud challenge has already been resolved"
292
+ );
293
+
294
+ require(
295
+ Heartbeat.isValidHeartbeatMessage(heartbeatMessage),
296
+ "Not a valid heartbeat message"
297
+ );
298
+
299
+ resolveFraudChallenge(self, walletPublicKey, challenge, sighash);
300
+ }
301
+
302
+ /// @notice Called only for successfully defeated fraud challenges.
303
+ /// The fraud challenge is marked as resolved and the amount of
304
+ /// ether deposited by the challenger is sent to the treasury.
305
+ /// @dev Requirements:
306
+ /// - Must be called only for successfully defeated fraud challenges.
307
+ function resolveFraudChallenge(
308
+ BridgeState.Storage storage self,
309
+ bytes calldata walletPublicKey,
310
+ FraudChallenge storage challenge,
311
+ bytes32 sighash
312
+ ) internal {
237
313
  // Mark the challenge as resolved as it was successfully defeated
238
314
  challenge.resolved = true;
239
315
 
@@ -248,6 +324,7 @@ library Fraud {
248
324
  walletPublicKey.slice32(32)
249
325
  );
250
326
  bytes20 walletPubKeyHash = compressedWalletPublicKey.hash160View();
327
+
251
328
  // slither-disable-next-line reentrancy-events
252
329
  emit FraudChallengeDefeated(walletPubKeyHash, sighash);
253
330
  }
@@ -78,6 +78,11 @@ import {BytesLib} from "@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol";
78
78
  /// message. The first 8 bytes are 0xffffffffffffffff. The last 8 bytes
79
79
  /// are for an arbitrary uint64, being a signed heartbeat nonce (for
80
80
  /// example, the last Ethereum block hash).
81
+ ///
82
+ /// The message being signed by the wallet when executing the heartbeat
83
+ /// protocol should be Bitcoin's hash256 (double SHA-256) of the heartbeat
84
+ /// message:
85
+ /// heartbeat_sighash = hash256(heartbeat_message)
81
86
  library Heartbeat {
82
87
  using BytesLib for bytes;
83
88
 
@@ -0,0 +1,125 @@
1
+ // SPDX-License-Identifier: MIT
2
+
3
+ // ██████████████ ▐████▌ ██████████████
4
+ // ██████████████ ▐████▌ ██████████████
5
+ // ▐████▌ ▐████▌
6
+ // ▐████▌ ▐████▌
7
+ // ██████████████ ▐████▌ ██████████████
8
+ // ██████████████ ▐████▌ ██████████████
9
+ // ▐████▌ ▐████▌
10
+ // ▐████▌ ▐████▌
11
+ // ▐████▌ ▐████▌
12
+ // ▐████▌ ▐████▌
13
+ // ▐████▌ ▐████▌
14
+ // ▐████▌ ▐████▌
15
+
16
+ pragma solidity ^0.8.9;
17
+
18
+ import "./IVault.sol";
19
+ import "../bank/Bank.sol";
20
+
21
+ /// @title BTC donation vault
22
+ /// @notice Vault that allows making BTC donations to the system. Upon deposit,
23
+ /// this vault does not increase depositors' balances and always
24
+ /// decreases its own balance in the same transaction. The vault also
25
+ /// allows making donations using existing Bank balances.
26
+ ///
27
+ /// BEWARE: ALL BTC DEPOSITS TARGETING THIS VAULT ARE NOT REDEEMABLE
28
+ /// AND THERE IS NO WAY TO RESTORE THE DONATED BALANCE.
29
+ /// USE THIS VAULT ONLY WHEN YOU REALLY KNOW WHAT YOU ARE DOING!
30
+ contract DonationVault is IVault {
31
+ Bank public bank;
32
+
33
+ event DonationReceived(address donor, uint256 donatedAmount);
34
+
35
+ modifier onlyBank() {
36
+ require(msg.sender == address(bank), "Caller is not the Bank");
37
+ _;
38
+ }
39
+
40
+ constructor(Bank _bank) {
41
+ require(
42
+ address(_bank) != address(0),
43
+ "Bank can not be the zero address"
44
+ );
45
+
46
+ bank = _bank;
47
+ }
48
+
49
+ /// @notice Transfers the given `amount` of the Bank balance from the
50
+ /// caller to the Donation Vault and immediately decreases the
51
+ /// vault's balance in the Bank by the transferred `amount`.
52
+ /// @param amount Amount of the Bank balance to donate
53
+ /// @dev Requirements:
54
+ /// - The caller's balance in the Bank must be greater than or equal
55
+ /// to the `amount`.
56
+ /// - Donation Vault must have an allowance for caller's balance in
57
+ /// the Bank for at least `amount`.
58
+ function donate(uint256 amount) external {
59
+ address donor = msg.sender;
60
+
61
+ require(
62
+ bank.balanceOf(donor) >= amount,
63
+ "Amount exceeds balance in the bank"
64
+ );
65
+
66
+ emit DonationReceived(donor, amount);
67
+
68
+ bank.transferBalanceFrom(donor, address(this), amount);
69
+ bank.decreaseBalance(amount);
70
+ }
71
+
72
+ /// @notice Transfers the given `amount` of the Bank balance from the
73
+ /// `owner` to the Donation Vault and immediately decreases the
74
+ /// vault's balance in the Bank by the transferred `amount`.
75
+ /// @param owner Address of the Bank balance owner who approved their
76
+ /// balance to be used by the vault
77
+ /// @param amount The amount of the Bank balance approved by the owner
78
+ /// to be used by the vault
79
+ /// @dev Requirements:
80
+ /// - Can only be called by the Bank via `approveBalanceAndCall`.
81
+ /// - The `owner` balance in the Bank must be greater than or equal
82
+ /// to the `amount`.
83
+ function receiveBalanceApproval(address owner, uint256 amount)
84
+ external
85
+ override
86
+ onlyBank
87
+ {
88
+ require(
89
+ bank.balanceOf(owner) >= amount,
90
+ "Amount exceeds balance in the bank"
91
+ );
92
+
93
+ emit DonationReceived(owner, amount);
94
+
95
+ bank.transferBalanceFrom(owner, address(this), amount);
96
+ bank.decreaseBalance(amount);
97
+ }
98
+
99
+ /// @notice Ignores the deposited amounts and does not increase depositors'
100
+ /// individual balances. The vault decreases its own tBTC balance
101
+ /// in the Bank by the total deposited amount.
102
+ /// @param depositors Addresses of depositors whose deposits have been swept
103
+ /// @param depositedAmounts Amounts deposited by individual depositors and
104
+ /// swept
105
+ /// @dev Requirements:
106
+ /// - Can only be called by the Bank after the Bridge swept deposits
107
+ /// and Bank increased balance for the vault.
108
+ /// - The `depositors` array must not be empty.
109
+ /// - The `depositors` array length must be equal to the
110
+ /// `depositedAmounts` array length.
111
+ function receiveBalanceIncrease(
112
+ address[] calldata depositors,
113
+ uint256[] calldata depositedAmounts
114
+ ) external override onlyBank {
115
+ require(depositors.length != 0, "No depositors specified");
116
+
117
+ uint256 totalAmount = 0;
118
+ for (uint256 i = 0; i < depositors.length; i++) {
119
+ totalAmount += depositedAmounts[i];
120
+ emit DonationReceived(depositors[i], depositedAmounts[i]);
121
+ }
122
+
123
+ bank.decreaseBalance(totalAmount);
124
+ }
125
+ }
package/export.json CHANGED
@@ -14871,6 +14871,24 @@
14871
14871
  "stateMutability": "nonpayable",
14872
14872
  "type": "function"
14873
14873
  },
14874
+ {
14875
+ "inputs": [
14876
+ {
14877
+ "internalType": "bytes",
14878
+ "name": "walletPublicKey",
14879
+ "type": "bytes"
14880
+ },
14881
+ {
14882
+ "internalType": "bytes",
14883
+ "name": "heartbeatMessage",
14884
+ "type": "bytes"
14885
+ }
14886
+ ],
14887
+ "name": "defeatFraudChallengeWithHeartbeat",
14888
+ "outputs": [],
14889
+ "stateMutability": "nonpayable",
14890
+ "type": "function"
14891
+ },
14874
14892
  {
14875
14893
  "inputs": [],
14876
14894
  "name": "depositParameters",
@@ -16019,6 +16037,11 @@
16019
16037
  "internalType": "struct BitcoinTx.UTXO",
16020
16038
  "name": "mainUtxo",
16021
16039
  "type": "tuple"
16040
+ },
16041
+ {
16042
+ "internalType": "address",
16043
+ "name": "vault",
16044
+ "type": "address"
16022
16045
  }
16023
16046
  ],
16024
16047
  "name": "submitDepositSweepProof",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keep-network/tbtc-v2",
3
- "version": "0.1.1-dev.61+main.7a1cd6ba871d67f537fa5b44cb36dbd2f29fb161",
3
+ "version": "0.1.1-dev.64+main.d47a7754c858959c03630155ce64c823f5cfc6cf",
4
4
  "license": "MIT",
5
5
  "files": [
6
6
  "artifacts/",