@keep-network/tbtc-v2 0.1.1-dev.42 → 0.1.1-dev.45

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 (81) hide show
  1. package/artifacts/Bank.json +742 -0
  2. package/artifacts/Bridge.json +2914 -0
  3. package/artifacts/Deposit.json +117 -0
  4. package/artifacts/EcdsaDkgValidator.json +532 -0
  5. package/artifacts/EcdsaInactivity.json +156 -0
  6. package/artifacts/Fraud.json +153 -0
  7. package/artifacts/KeepRegistry.json +99 -0
  8. package/artifacts/KeepStake.json +286 -0
  9. package/artifacts/KeepToken.json +711 -0
  10. package/artifacts/KeepTokenStaking.json +483 -0
  11. package/artifacts/MovingFunds.json +137 -0
  12. package/artifacts/NuCypherStakingEscrow.json +256 -0
  13. package/artifacts/NuCypherToken.json +711 -0
  14. package/artifacts/RandomBeaconStub.json +141 -0
  15. package/artifacts/Redemption.json +161 -0
  16. package/artifacts/ReimbursementPool.json +509 -0
  17. package/artifacts/Relay.json +123 -0
  18. package/artifacts/SortitionPool.json +944 -0
  19. package/artifacts/Sweep.json +76 -0
  20. package/artifacts/T.json +1148 -0
  21. package/artifacts/TBTC.json +21 -21
  22. package/artifacts/TBTCToken.json +21 -21
  23. package/artifacts/TokenStaking.json +2288 -0
  24. package/artifacts/TokenholderGovernor.json +1795 -0
  25. package/artifacts/TokenholderTimelock.json +1058 -0
  26. package/artifacts/VendingMachine.json +24 -24
  27. package/artifacts/VendingMachineKeep.json +400 -0
  28. package/artifacts/VendingMachineNuCypher.json +400 -0
  29. package/artifacts/WalletRegistry.json +2709 -0
  30. package/artifacts/WalletRegistryGovernance.json +2364 -0
  31. package/artifacts/Wallets.json +186 -0
  32. package/artifacts/solcInputs/{002940e9cc8128f6629e90620c66cba5.json → 58ca6018672440f1c9c800806e096f9c.json} +22 -22
  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 +489 -173
  39. package/build/contracts/bridge/BridgeState.sol/BridgeState.dbg.json +1 -1
  40. package/build/contracts/bridge/BridgeState.sol/BridgeState.json +114 -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/EcdsaLib.sol/EcdsaLib.dbg.json +1 -1
  44. package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.json +2 -2
  45. package/build/contracts/bridge/Fraud.sol/Fraud.dbg.json +1 -1
  46. package/build/contracts/bridge/Fraud.sol/Fraud.json +5 -57
  47. package/build/contracts/bridge/IRelay.sol/IRelay.dbg.json +1 -1
  48. package/build/contracts/bridge/MovingFunds.sol/MovingFunds.dbg.json +1 -1
  49. package/build/contracts/bridge/MovingFunds.sol/MovingFunds.json +40 -2
  50. package/build/contracts/bridge/Redemption.sol/OutboundTx.dbg.json +4 -0
  51. package/build/contracts/bridge/{Redeem.sol → Redemption.sol}/OutboundTx.json +3 -3
  52. package/build/contracts/bridge/Redemption.sol/Redemption.dbg.json +4 -0
  53. package/build/contracts/bridge/Redemption.sol/Redemption.json +92 -0
  54. package/build/contracts/bridge/Sweep.sol/Sweep.dbg.json +1 -1
  55. package/build/contracts/bridge/Sweep.sol/Sweep.json +2 -2
  56. package/build/contracts/bridge/VendingMachine.sol/VendingMachine.dbg.json +1 -1
  57. package/build/contracts/bridge/Wallets.sol/Wallets.dbg.json +1 -1
  58. package/build/contracts/bridge/Wallets.sol/Wallets.json +21 -2
  59. package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
  60. package/build/contracts/vault/IVault.sol/IVault.dbg.json +1 -1
  61. package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +1 -1
  62. package/contracts/bridge/BitcoinTx.sol +19 -26
  63. package/contracts/bridge/Bridge.sol +736 -489
  64. package/contracts/bridge/BridgeState.sol +271 -24
  65. package/contracts/bridge/Deposit.sol +24 -2
  66. package/contracts/bridge/EcdsaLib.sol +15 -0
  67. package/contracts/bridge/Fraud.sol +64 -31
  68. package/contracts/bridge/MovingFunds.sol +194 -6
  69. package/contracts/bridge/{Redeem.sol → Redemption.sol} +20 -18
  70. package/contracts/bridge/Sweep.sol +13 -8
  71. package/contracts/bridge/Wallets.sol +53 -35
  72. package/deploy/00_resolve_relay.ts +28 -0
  73. package/deploy/04_deploy_bank.ts +25 -0
  74. package/deploy/05_deploy_bridge.ts +60 -0
  75. package/deploy/06_bank_update_bridge.ts +19 -0
  76. package/deploy/07_transfer_ownership.ts +17 -0
  77. package/export.json +14797 -459
  78. package/package.json +2 -2
  79. package/build/contracts/bridge/Redeem.sol/OutboundTx.dbg.json +0 -4
  80. package/build/contracts/bridge/Redeem.sol/Redeem.dbg.json +0 -4
  81. package/build/contracts/bridge/Redeem.sol/Redeem.json +0 -92
@@ -20,20 +20,174 @@ import {BytesLib} from "@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol";
20
20
 
21
21
  import "./BitcoinTx.sol";
22
22
  import "./BridgeState.sol";
23
- import "./Redeem.sol";
23
+ import "./Redemption.sol";
24
+ import "./Wallets.sol";
24
25
 
26
+ /// @title Moving Bridge wallet funds
27
+ /// @notice The library handles the logic for moving Bitcoin between Bridge
28
+ /// wallets.
29
+ /// @dev A wallet that failed a heartbeat, did not process requested redemption
30
+ /// on time, or qualifies to be closed, begins the procedure of moving
31
+ /// funds to other wallets in the Bridge. The wallet needs to commit to
32
+ /// which other Live wallets it is moving the funds to and then, provide an
33
+ /// SPV proof of moving funds to the previously committed wallets.
25
34
  library MovingFunds {
26
35
  using BridgeState for BridgeState.Storage;
27
36
  using Wallets for BridgeState.Storage;
37
+ using BitcoinTx for BridgeState.Storage;
28
38
 
29
39
  using BTCUtils for bytes;
30
40
  using BytesLib for bytes;
31
41
 
42
+ event MovingFundsCommitmentSubmitted(
43
+ bytes20 walletPubKeyHash,
44
+ bytes20[] targetWallets,
45
+ address submitter
46
+ );
47
+
32
48
  event MovingFundsCompleted(
33
49
  bytes20 walletPubKeyHash,
34
50
  bytes32 movingFundsTxHash
35
51
  );
36
52
 
53
+ event MovingFundsTimedOut(bytes20 walletPubKeyHash);
54
+
55
+ /// @notice Submits the moving funds target wallets commitment.
56
+ /// Once all requirements are met, that function registers the
57
+ /// target wallets commitment and opens the way for moving funds
58
+ /// proof submission.
59
+ /// @param walletPubKeyHash 20-byte public key hash of the source wallet
60
+ /// @param walletMainUtxo Data of the source wallet's main UTXO, as
61
+ /// currently known on the Ethereum chain
62
+ /// @param walletMembersIDs Identifiers of the source wallet signing group
63
+ /// members
64
+ /// @param walletMemberIndex Position of the caller in the source wallet
65
+ /// signing group members list
66
+ /// @param targetWallets List of 20-byte public key hashes of the target
67
+ /// wallets that the source wallet commits to move the funds to
68
+ /// @dev Requirements:
69
+ /// - The source wallet must be in the MovingFunds state
70
+ /// - The source wallet must not have pending redemption requests
71
+ /// - The source wallet must not have submitted its commitment already
72
+ /// - The expression `keccak256(abi.encode(walletMembersIDs))` must
73
+ /// be exactly the same as the hash stored under `membersIdsHash`
74
+ /// for the given source wallet in the ECDSA registry. Those IDs are
75
+ /// not directly stored in the contract for gas efficiency purposes
76
+ /// but they can be read from appropriate `DkgResultSubmitted`
77
+ /// and `DkgResultApproved` events.
78
+ /// - The `walletMemberIndex` must be in range [1, walletMembersIDs.length]
79
+ /// - The caller must be the member of the source wallet signing group
80
+ /// at the position indicated by `walletMemberIndex` parameter
81
+ /// - The `walletMainUtxo` components must point to the recent main
82
+ /// UTXO of the source wallet, as currently known on the Ethereum
83
+ /// chain.
84
+ /// - Source wallet BTC balance must be greater than zero
85
+ /// - At least one Live wallet must exist in the system
86
+ /// - Submitted target wallets count must match the expected count
87
+ /// `N = min(liveWalletsCount, ceil(walletBtcBalance / walletMaxBtcTransfer))`
88
+ /// where `N > 0`
89
+ /// - Each target wallet must be not equal to the source wallet
90
+ /// - Each target wallet must follow the expected order i.e. all
91
+ /// target wallets 20-byte public key hashes represented as numbers
92
+ /// must form a strictly increasing sequence without duplicates.
93
+ /// - Each target wallet must be in Live state
94
+ function submitMovingFundsCommitment(
95
+ BridgeState.Storage storage self,
96
+ bytes20 walletPubKeyHash,
97
+ BitcoinTx.UTXO calldata walletMainUtxo,
98
+ uint32[] calldata walletMembersIDs,
99
+ uint256 walletMemberIndex,
100
+ bytes20[] calldata targetWallets
101
+ ) external {
102
+ Wallets.Wallet storage wallet = self.registeredWallets[
103
+ walletPubKeyHash
104
+ ];
105
+
106
+ require(
107
+ wallet.state == Wallets.WalletState.MovingFunds,
108
+ "Source wallet must be in MovingFunds state"
109
+ );
110
+
111
+ require(
112
+ wallet.pendingRedemptionsValue == 0,
113
+ "Source wallet must handle all pending redemptions first"
114
+ );
115
+
116
+ require(
117
+ wallet.movingFundsTargetWalletsCommitmentHash == bytes32(0),
118
+ "Target wallets commitment already submitted"
119
+ );
120
+
121
+ require(
122
+ self.ecdsaWalletRegistry.isWalletMember(
123
+ wallet.ecdsaWalletID,
124
+ walletMembersIDs,
125
+ msg.sender,
126
+ walletMemberIndex
127
+ ),
128
+ "Caller is not a member of the source wallet"
129
+ );
130
+
131
+ uint64 walletBtcBalance = self.getWalletBtcBalance(
132
+ walletPubKeyHash,
133
+ walletMainUtxo
134
+ );
135
+
136
+ require(walletBtcBalance > 0, "Wallet BTC balance is zero");
137
+
138
+ uint256 expectedTargetWalletsCount = Math.min(
139
+ self.liveWalletsCount,
140
+ Math.ceilDiv(walletBtcBalance, self.walletMaxBtcTransfer)
141
+ );
142
+
143
+ // This requirement fails only when `liveWalletsCount` is zero. In
144
+ // that case, the system cannot accept the commitment and must provide
145
+ // new wallets first.
146
+ //
147
+ // TODO: Expose separate function to reset the moving funds timeout
148
+ // if no Live wallets exist in the system.
149
+ require(expectedTargetWalletsCount > 0, "No target wallets available");
150
+
151
+ require(
152
+ targetWallets.length == expectedTargetWalletsCount,
153
+ "Submitted target wallets count is other than expected"
154
+ );
155
+
156
+ uint160 lastProcessedTargetWallet = 0;
157
+
158
+ for (uint256 i = 0; i < targetWallets.length; i++) {
159
+ bytes20 targetWallet = targetWallets[i];
160
+
161
+ require(
162
+ targetWallet != walletPubKeyHash,
163
+ "Submitted target wallet cannot be equal to the source wallet"
164
+ );
165
+
166
+ require(
167
+ uint160(targetWallet) > lastProcessedTargetWallet,
168
+ "Submitted target wallet breaks the expected order"
169
+ );
170
+
171
+ require(
172
+ self.registeredWallets[targetWallet].state ==
173
+ Wallets.WalletState.Live,
174
+ "Submitted target wallet must be in Live state"
175
+ );
176
+
177
+ lastProcessedTargetWallet = uint160(targetWallet);
178
+ }
179
+
180
+ wallet.movingFundsTargetWalletsCommitmentHash = keccak256(
181
+ abi.encodePacked(targetWallets)
182
+ );
183
+
184
+ emit MovingFundsCommitmentSubmitted(
185
+ walletPubKeyHash,
186
+ targetWallets,
187
+ msg.sender
188
+ );
189
+ }
190
+
37
191
  /// @notice Used by the wallet to prove the BTC moving funds transaction
38
192
  /// and to make the necessary state changes. Moving funds is only
39
193
  /// accepted if it satisfies SPV proof.
@@ -90,10 +244,9 @@ library MovingFunds {
90
244
  // can assume the transaction happened on Bitcoin chain and has
91
245
  // a sufficient number of confirmations as determined by
92
246
  // `txProofDifficultyFactor` constant.
93
- bytes32 movingFundsTxHash = BitcoinTx.validateProof(
247
+ bytes32 movingFundsTxHash = self.validateProof(
94
248
  movingFundsTx,
95
- movingFundsProof,
96
- self.proofDifficultyContext()
249
+ movingFundsProof
97
250
  );
98
251
 
99
252
  // Process the moving funds transaction input. Specifically, check if
@@ -117,7 +270,7 @@ library MovingFunds {
117
270
  );
118
271
 
119
272
  self.notifyWalletFundsMoved(walletPubKeyHash, targetWalletsHash);
120
-
273
+ // slither-disable-next-line reentrancy-events
121
274
  emit MovingFundsCompleted(walletPubKeyHash, movingFundsTxHash);
122
275
  }
123
276
 
@@ -137,7 +290,7 @@ library MovingFunds {
137
290
  /// - The total outputs value must be evenly divided over all outputs.
138
291
  function processMovingFundsTxOutputs(bytes memory movingFundsTxOutputVector)
139
292
  internal
140
- view
293
+ pure
141
294
  returns (bytes32 targetWalletsHash, uint256 outputsTotalValue)
142
295
  {
143
296
  // Determining the total number of Bitcoin transaction outputs in
@@ -275,4 +428,39 @@ library MovingFunds {
275
428
 
276
429
  return (targetWalletsHash, outputsTotalValue);
277
430
  }
431
+
432
+ /// @notice Notifies about a timed out moving funds process. Terminates
433
+ /// the wallet and slashes signing group members as a result.
434
+ /// @param walletPubKeyHash 20-byte public key hash of the wallet
435
+ /// @dev Requirements:
436
+ /// - The wallet must be in the MovingFunds state
437
+ /// - The moving funds timeout must be actually exceeded
438
+ function notifyMovingFundsTimeout(
439
+ BridgeState.Storage storage self,
440
+ bytes20 walletPubKeyHash
441
+ ) external {
442
+ Wallets.Wallet storage wallet = self.registeredWallets[
443
+ walletPubKeyHash
444
+ ];
445
+
446
+ require(
447
+ wallet.state == Wallets.WalletState.MovingFunds,
448
+ "ECDSA wallet must be in MovingFunds state"
449
+ );
450
+
451
+ require(
452
+ /* solhint-disable-next-line not-rely-on-time */
453
+ block.timestamp >
454
+ wallet.movingFundsRequestedAt + self.movingFundsTimeout,
455
+ "Moving funds has not timed out yet"
456
+ );
457
+
458
+ self.terminateWallet(walletPubKeyHash);
459
+
460
+ // TODO: Perform slashing of wallet operators, reward the notifier
461
+ // using seized amount, and add unit tests for that.
462
+
463
+ // slither-disable-next-line reentrancy-events
464
+ emit MovingFundsTimedOut(walletPubKeyHash);
465
+ }
278
466
  }
@@ -140,10 +140,18 @@ library OutboundTx {
140
140
  }
141
141
  }
142
142
 
143
- // TODO: Rename to Redemption. All library names are nouns.
144
- library Redeem {
143
+ /// @title Bridge redemption
144
+ /// @notice The library handles the logic for redeeming Bitcoin balances from
145
+ /// the Bridge.
146
+ /// @dev To initiate a redemption, a user with a Bank balance supplies
147
+ /// a Bitcoin address. Then, the system calculates the redemption fee, and
148
+ /// releases balance to the provided Bitcoin address. Just like in case of
149
+ /// sweeps of revealed deposits, redemption requests are processed in
150
+ /// batches and require SPV proof to be submitted to the Bridge.
151
+ library Redemption {
145
152
  using BridgeState for BridgeState.Storage;
146
153
  using Wallets for BridgeState.Storage;
154
+ using BitcoinTx for BridgeState.Storage;
147
155
 
148
156
  using BTCUtils for bytes;
149
157
  using BytesLib for bytes;
@@ -422,18 +430,13 @@ library Redeem {
422
430
  BitcoinTx.UTXO calldata mainUtxo,
423
431
  bytes20 walletPubKeyHash
424
432
  ) external {
425
- // TODO: Just as for `submitSweepProof`, fail early if the function
426
- // call gets frontrunned. See discussion:
427
- // https://github.com/keep-network/tbtc-v2/pull/106#discussion_r801745204
428
-
429
433
  // The actual transaction proof is performed here. After that point, we
430
434
  // can assume the transaction happened on Bitcoin chain and has
431
435
  // a sufficient number of confirmations as determined by
432
436
  // `txProofDifficultyFactor` constant.
433
- bytes32 redemptionTxHash = BitcoinTx.validateProof(
437
+ bytes32 redemptionTxHash = self.validateProof(
434
438
  redemptionTx,
435
- redemptionProof,
436
- self.proofDifficultyContext()
439
+ redemptionProof
437
440
  );
438
441
 
439
442
  // Process the redemption transaction input. Specifically, check if it
@@ -596,11 +599,10 @@ library Redeem {
596
599
  bytes20 walletPubKeyHash,
597
600
  RedemptionTxOutputsProcessingInfo memory processInfo
598
601
  ) internal returns (RedemptionTxOutputsInfo memory resultInfo) {
599
- // Helper variable that counts the number of processed redemption
600
- // outputs. Redemptions can be either pending or reported as timed out.
601
- // TODO: Revisit the approach with redemptions count according to
602
- // https://github.com/keep-network/tbtc-v2/pull/128#discussion_r808237765
603
- uint256 processedRedemptionsCount = 0;
602
+ // Helper flag indicating whether there was at least one redemption
603
+ // output present (redemption must be either pending or reported as
604
+ // timed out).
605
+ bool redemptionPresent = false;
604
606
 
605
607
  // Outputs processing loop.
606
608
  for (uint256 i = 0; i < processInfo.outputsCount; i++) {
@@ -647,7 +649,7 @@ library Redeem {
647
649
  );
648
650
  resultInfo.totalBurnableValue += burnableValue;
649
651
  resultInfo.totalTreasuryFee += treasuryFee;
650
- processedRedemptionsCount++;
652
+ redemptionPresent = true;
651
653
  }
652
654
 
653
655
  // Make the `outputStartingIndex` pointing to the next output by
@@ -659,7 +661,7 @@ library Redeem {
659
661
  // referring back to the wallet PKH and just burning main UTXO value
660
662
  // for transaction fees.
661
663
  require(
662
- processedRedemptionsCount > 0,
664
+ redemptionPresent,
663
665
  "Redemption transaction must process at least one redemption"
664
666
  );
665
667
  }
@@ -791,7 +793,7 @@ library Redeem {
791
793
  uint256 redemptionKey = uint256(
792
794
  keccak256(abi.encodePacked(walletPubKeyHash, redeemerOutputScript))
793
795
  );
794
- Redeem.RedemptionRequest memory request = self.pendingRedemptions[
796
+ Redemption.RedemptionRequest memory request = self.pendingRedemptions[
795
797
  redemptionKey
796
798
  ];
797
799
 
@@ -835,7 +837,7 @@ library Redeem {
835
837
  // Propagate timeout consequences to the wallet
836
838
  self.notifyWalletTimedOutRedemption(walletPubKeyHash);
837
839
  }
838
-
840
+ // slither-disable-next-line reentrancy-events
839
841
  emit RedemptionTimedOut(walletPubKeyHash, redeemerOutputScript);
840
842
 
841
843
  // Return the requested amount of tokens to the redeemer
@@ -23,8 +23,19 @@ import "./Wallets.sol";
23
23
 
24
24
  import "../bank/Bank.sol";
25
25
 
26
+ /// @title Bridge deposit sweep
27
+ /// @notice The library handles the logic for sweeping transactions revealed to
28
+ /// the Bridge
29
+ /// @dev Bridge active wallet periodically signs a transaction that unlocks all
30
+ /// of the valid, revealed deposits above the dust threshold, combines them
31
+ /// into a single UTXO with the existing main wallet UTXO, and relocks
32
+ /// those transactions without a 30-day refund clause to the same wallet.
33
+ /// This has two main effects: it consolidates the UTXO set and it disables
34
+ /// the refund. Balances of depositors in the Bank are increased when the
35
+ /// SPV sweep proof is submitted to the Bridge.
26
36
  library Sweep {
27
37
  using BridgeState for BridgeState.Storage;
38
+ using BitcoinTx for BridgeState.Storage;
28
39
 
29
40
  using BTCUtils for bytes;
30
41
 
@@ -98,18 +109,11 @@ library Sweep {
98
109
  BitcoinTx.Proof calldata sweepProof,
99
110
  BitcoinTx.UTXO calldata mainUtxo
100
111
  ) external {
101
- // TODO: Fail early if the function call gets frontrunned. See discussion:
102
- // https://github.com/keep-network/tbtc-v2/pull/106#discussion_r801745204
103
-
104
112
  // The actual transaction proof is performed here. After that point, we
105
113
  // can assume the transaction happened on Bitcoin chain and has
106
114
  // a sufficient number of confirmations as determined by
107
115
  // `txProofDifficultyFactor` constant.
108
- bytes32 sweepTxHash = BitcoinTx.validateProof(
109
- sweepTx,
110
- sweepProof,
111
- self.proofDifficultyContext()
112
- );
116
+ bytes32 sweepTxHash = self.validateProof(sweepTx, sweepProof);
113
117
 
114
118
  // Process sweep transaction output and extract its target wallet
115
119
  // public key hash and value.
@@ -212,6 +216,7 @@ library Sweep {
212
216
  BitcoinTx.UTXO calldata mainUtxo
213
217
  )
214
218
  internal
219
+ view
215
220
  returns (
216
221
  Wallets.Wallet storage wallet,
217
222
  BitcoinTx.UTXO memory resolvedMainUtxo
@@ -16,8 +16,8 @@
16
16
  pragma solidity ^0.8.9;
17
17
 
18
18
  import {BTCUtils} from "@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol";
19
- import {IWalletRegistry as EcdsaWalletRegistry} from "@keep-network/ecdsa/contracts/api/IWalletRegistry.sol";
20
19
  import {EcdsaDkg} from "@keep-network/ecdsa/contracts/libraries/EcdsaDkg.sol";
20
+ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
21
21
 
22
22
  import "./BitcoinTx.sol";
23
23
  import "./EcdsaLib.sol";
@@ -40,8 +40,15 @@ library Wallets {
40
40
  /// fulfill their pending redemption requests although new
41
41
  /// redemption requests and new deposit reveals are not accepted.
42
42
  MovingFunds,
43
- /// @dev The wallet moved or redeemed all their funds and cannot
44
- /// perform any action.
43
+ /// @dev The wallet moved or redeemed all their funds and is in the
44
+ /// closing period where they can be subject of fraud challenges
45
+ /// and must defend against them. This state is needed to protect
46
+ /// against deposit frauds on deposits revealed but not swept.
47
+ /// The closing period must be greater that the deposit refund
48
+ /// time plus some time margin.
49
+ Closing,
50
+ /// @dev The wallet finalized the closing period successfully and
51
+ /// cannot perform any action in the Bridge.
45
52
  Closed,
46
53
  /// @dev The wallet committed a fraud that was reported. The wallet is
47
54
  /// blocked and can not perform any actions in the Bridge.
@@ -68,6 +75,9 @@ library Wallets {
68
75
  // UNIX timestamp indicating the moment the wallet was requested to
69
76
  // move their funds.
70
77
  uint32 movingFundsRequestedAt;
78
+ // UNIX timestamp indicating the moment the wallet's closing period
79
+ // started.
80
+ uint32 closingStartedAt;
71
81
  // Current state of the wallet.
72
82
  WalletState state;
73
83
  // Moving funds target wallet commitment submitted by the wallet. It
@@ -88,6 +98,11 @@ library Wallets {
88
98
  bytes20 indexed walletPubKeyHash
89
99
  );
90
100
 
101
+ event WalletClosing(
102
+ bytes32 indexed ecdsaWalletID,
103
+ bytes20 indexed walletPubKeyHash
104
+ );
105
+
91
106
  event WalletClosed(
92
107
  bytes32 indexed ecdsaWalletID,
93
108
  bytes20 indexed walletPubKeyHash
@@ -237,6 +252,8 @@ library Wallets {
237
252
  // Set the freshly created wallet as the new active wallet.
238
253
  self.activeWalletPubKeyHash = walletPubKeyHash;
239
254
 
255
+ self.liveWalletsCount++;
256
+
240
257
  emit NewWalletRegistered(ecdsaWalletID, walletPubKeyHash);
241
258
  }
242
259
 
@@ -347,9 +364,8 @@ library Wallets {
347
364
  }
348
365
 
349
366
  /// @notice Requests a wallet to move their funds. If the wallet balance
350
- /// is zero, the wallet is closed immediately and the ECDSA
351
- /// registry is notified about this fact. If the move funds
352
- /// request refers to the current active wallet, such a wallet
367
+ /// is zero, the wallet closing begins immediately. If the move
368
+ /// funds request refers to the current active wallet, such a wallet
353
369
  /// is no longer considered active and the active wallet slot
354
370
  /// is unset allowing to trigger a new wallet creation immediately.
355
371
  /// @param walletPubKeyHash 20-byte public key hash of the wallet
@@ -363,8 +379,8 @@ library Wallets {
363
379
 
364
380
  if (wallet.mainUtxoHash == bytes32(0)) {
365
381
  // If the wallet has no main UTXO, that means its BTC balance
366
- // is zero and it should be closed immediately.
367
- closeWallet(self, walletPubKeyHash);
382
+ // is zero and the wallet closing should begin immediately.
383
+ beginWalletClosing(self, walletPubKeyHash);
368
384
  } else {
369
385
  // Otherwise, initialize the moving funds process.
370
386
  wallet.state = WalletState.MovingFunds;
@@ -380,51 +396,49 @@ library Wallets {
380
396
  // possible in order to get a new healthy active wallet.
381
397
  delete self.activeWalletPubKeyHash;
382
398
  }
399
+
400
+ self.liveWalletsCount--;
383
401
  }
384
402
 
385
- /// @notice Closes the given wallet and notifies the ECDSA registry
386
- /// about this fact.
403
+ /// @notice Begins the closing period of the given wallet.
387
404
  /// @param walletPubKeyHash 20-byte public key hash of the wallet
388
405
  /// @dev Requirements:
389
406
  /// - The caller must make sure that the wallet is in the
390
- /// Live or MovingFunds state.
391
- function closeWallet(
407
+ /// MovingFunds state
408
+ function beginWalletClosing(
392
409
  BridgeState.Storage storage self,
393
410
  bytes20 walletPubKeyHash
394
411
  ) internal {
395
412
  Wallet storage wallet = self.registeredWallets[walletPubKeyHash];
413
+ // Initialize the closing period.
414
+ wallet.state = WalletState.Closing;
415
+ /* solhint-disable-next-line not-rely-on-time */
416
+ wallet.closingStartedAt = uint32(block.timestamp);
396
417
 
397
- wallet.state = WalletState.Closed;
398
-
399
- emit WalletClosed(wallet.ecdsaWalletID, walletPubKeyHash);
400
-
401
- self.ecdsaWalletRegistry.closeWallet(wallet.ecdsaWalletID);
418
+ emit WalletClosing(wallet.ecdsaWalletID, walletPubKeyHash);
402
419
  }
403
420
 
404
- /// @notice Reports about a fraud committed by the given wallet. This
405
- /// function performs slashing and wallet termination in reaction
406
- /// to a proven fraud and it should only be called when the fraud
407
- /// was confirmed.
421
+ /// @notice Finalizes the closing period of the given wallet and notifies
422
+ /// the ECDSA registry about this fact.
408
423
  /// @param walletPubKeyHash 20-byte public key hash of the wallet
409
424
  /// @dev Requirements:
410
- /// - Wallet must be in Live or MovingFunds state
411
- function notifyWalletFraud(
425
+ /// - The caller must make sure that the wallet is in the Closing state
426
+ ///
427
+ /// TODO: Make this function callable from the Bridge contract if
428
+ /// `block.timestamp > wallet.closingStartedAt + self.walletClosingPeriod`.
429
+ ///
430
+ // slither-disable-next-line dead-code
431
+ function finalizeWalletClosing(
412
432
  BridgeState.Storage storage self,
413
433
  bytes20 walletPubKeyHash
414
434
  ) internal {
415
- WalletState walletState = self
416
- .registeredWallets[walletPubKeyHash]
417
- .state;
435
+ Wallet storage wallet = self.registeredWallets[walletPubKeyHash];
418
436
 
419
- require(
420
- walletState == WalletState.Live ||
421
- walletState == WalletState.MovingFunds,
422
- "ECDSA wallet must be in Live or MovingFunds state"
423
- );
437
+ wallet.state = WalletState.Closed;
424
438
 
425
- terminateWallet(self, walletPubKeyHash);
439
+ emit WalletClosed(wallet.ecdsaWalletID, walletPubKeyHash);
426
440
 
427
- // TODO: Perform slashing of wallet operators and add unit tests for that.
441
+ self.ecdsaWalletRegistry.closeWallet(wallet.ecdsaWalletID);
428
442
  }
429
443
 
430
444
  /// @notice Terminates the given wallet and notifies the ECDSA registry
@@ -435,13 +449,17 @@ library Wallets {
435
449
  /// @param walletPubKeyHash 20-byte public key hash of the wallet
436
450
  /// @dev Requirements:
437
451
  /// - The caller must make sure that the wallet is in the
438
- /// Live or MovingFunds state.
452
+ /// Live or MovingFunds or Closing state.
439
453
  function terminateWallet(
440
454
  BridgeState.Storage storage self,
441
455
  bytes20 walletPubKeyHash
442
456
  ) internal {
443
457
  Wallet storage wallet = self.registeredWallets[walletPubKeyHash];
444
458
 
459
+ if (wallet.state == WalletState.Live) {
460
+ self.liveWalletsCount--;
461
+ }
462
+
445
463
  wallet.state = WalletState.Terminated;
446
464
 
447
465
  emit WalletTerminated(wallet.ecdsaWalletID, walletPubKeyHash);
@@ -505,6 +523,6 @@ library Wallets {
505
523
  // If funds were moved, the wallet has no longer a main UTXO.
506
524
  delete wallet.mainUtxoHash;
507
525
 
508
- closeWallet(self, walletPubKeyHash);
526
+ beginWalletClosing(self, walletPubKeyHash);
509
527
  }
510
528
  }
@@ -0,0 +1,28 @@
1
+ import { HardhatRuntimeEnvironment } from "hardhat/types"
2
+ import { DeployFunction } from "hardhat-deploy/types"
3
+
4
+ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
5
+ const { getNamedAccounts, deployments, helpers } = hre
6
+ const { log } = deployments
7
+ const { deployer } = await getNamedAccounts()
8
+
9
+ const Relay = await deployments.getOrNull("Relay")
10
+
11
+ if (Relay && helpers.address.isValid(Relay.address)) {
12
+ log(`using external Relay at ${Relay.address}`)
13
+ } else if (hre.network.name !== "hardhat") {
14
+ throw new Error("deployed Relay contract not found")
15
+ } else {
16
+ log("deploying Relay stub")
17
+
18
+ await deployments.deploy("Relay", {
19
+ contract: "TestRelay",
20
+ from: deployer,
21
+ log: true,
22
+ })
23
+ }
24
+ }
25
+
26
+ export default func
27
+
28
+ func.tags = ["Relay"]
@@ -0,0 +1,25 @@
1
+ import { HardhatRuntimeEnvironment } from "hardhat/types"
2
+ import { DeployFunction } from "hardhat-deploy/types"
3
+
4
+ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
5
+ const { deployments, getNamedAccounts } = hre
6
+ const { deploy } = deployments
7
+ const { deployer } = await getNamedAccounts()
8
+
9
+ const Bank = await deploy("Bank", {
10
+ from: deployer,
11
+ args: [],
12
+ log: true,
13
+ })
14
+
15
+ if (hre.network.tags.tenderly) {
16
+ await hre.tenderly.verify({
17
+ name: "Bank",
18
+ address: Bank.address,
19
+ })
20
+ }
21
+ }
22
+
23
+ export default func
24
+
25
+ func.tags = ["Bank"]