@keep-network/tbtc-v2 0.1.1-dev.10 → 0.1.1-dev.100

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 (115) hide show
  1. package/README.adoc +12 -0
  2. package/artifacts/Bank.json +807 -0
  3. package/artifacts/Bridge.json +2300 -0
  4. package/artifacts/Deposit.json +117 -0
  5. package/artifacts/DepositSweep.json +77 -0
  6. package/artifacts/EcdsaDkgValidator.json +532 -0
  7. package/artifacts/EcdsaInactivity.json +156 -0
  8. package/artifacts/EcdsaSortitionPool.json +1004 -0
  9. package/artifacts/Fraud.json +164 -0
  10. package/artifacts/KeepRegistry.json +99 -0
  11. package/artifacts/KeepStake.json +286 -0
  12. package/artifacts/KeepToken.json +711 -0
  13. package/artifacts/KeepTokenStaking.json +483 -0
  14. package/artifacts/MovingFunds.json +249 -0
  15. package/artifacts/NuCypherStakingEscrow.json +256 -0
  16. package/artifacts/NuCypherToken.json +711 -0
  17. package/artifacts/RandomBeaconStub.json +141 -0
  18. package/artifacts/Redemption.json +174 -0
  19. package/artifacts/ReimbursementPool.json +509 -0
  20. package/artifacts/Relay.json +123 -0
  21. package/artifacts/T.json +1148 -0
  22. package/artifacts/TBTC.json +27 -26
  23. package/artifacts/TBTCToken.json +27 -26
  24. package/artifacts/TBTCVault.json +691 -0
  25. package/artifacts/TokenStaking.json +2288 -0
  26. package/artifacts/TokenholderGovernor.json +1795 -0
  27. package/artifacts/TokenholderTimelock.json +1058 -0
  28. package/artifacts/VendingMachine.json +31 -30
  29. package/artifacts/VendingMachineKeep.json +400 -0
  30. package/artifacts/VendingMachineNuCypher.json +400 -0
  31. package/artifacts/WalletRegistry.json +1843 -0
  32. package/artifacts/WalletRegistryGovernance.json +2754 -0
  33. package/artifacts/Wallets.json +186 -0
  34. package/artifacts/solcInputs/8b65103759482b36742c6820fa66b63b.json +314 -0
  35. package/build/contracts/GovernanceUtils.sol/GovernanceUtils.dbg.json +1 -1
  36. package/build/contracts/GovernanceUtils.sol/GovernanceUtils.json +2 -2
  37. package/build/contracts/bank/Bank.sol/Bank.dbg.json +1 -1
  38. package/build/contracts/bank/Bank.sol/Bank.json +10 -5
  39. package/build/contracts/bank/IReceiveBalanceApproval.sol/IReceiveBalanceApproval.dbg.json +4 -0
  40. package/build/contracts/bank/IReceiveBalanceApproval.sol/IReceiveBalanceApproval.json +34 -0
  41. package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.dbg.json +1 -1
  42. package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.json +2 -2
  43. package/build/contracts/bridge/Bridge.sol/Bridge.dbg.json +1 -1
  44. package/build/contracts/bridge/Bridge.sol/Bridge.json +2549 -198
  45. package/build/contracts/bridge/BridgeState.sol/BridgeState.dbg.json +4 -0
  46. package/build/contracts/bridge/BridgeState.sol/BridgeState.json +226 -0
  47. package/build/contracts/bridge/Deposit.sol/Deposit.dbg.json +4 -0
  48. package/build/contracts/bridge/Deposit.sol/Deposit.json +72 -0
  49. package/build/contracts/bridge/DepositSweep.sol/DepositSweep.dbg.json +4 -0
  50. package/build/contracts/bridge/DepositSweep.sol/DepositSweep.json +30 -0
  51. package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.dbg.json +4 -0
  52. package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.json +10 -0
  53. package/build/contracts/bridge/Fraud.sol/Fraud.dbg.json +4 -0
  54. package/build/contracts/bridge/Fraud.sol/Fraud.json +86 -0
  55. package/build/contracts/bridge/Heartbeat.sol/Heartbeat.dbg.json +4 -0
  56. package/build/contracts/bridge/Heartbeat.sol/Heartbeat.json +10 -0
  57. package/build/contracts/bridge/IRelay.sol/IRelay.dbg.json +4 -0
  58. package/build/contracts/bridge/IRelay.sol/IRelay.json +37 -0
  59. package/build/contracts/bridge/MovingFunds.sol/MovingFunds.dbg.json +4 -0
  60. package/build/contracts/bridge/MovingFunds.sol/MovingFunds.json +138 -0
  61. package/build/contracts/bridge/Redemption.sol/OutboundTx.dbg.json +4 -0
  62. package/build/contracts/bridge/Redemption.sol/OutboundTx.json +10 -0
  63. package/build/contracts/bridge/Redemption.sol/Redemption.dbg.json +4 -0
  64. package/build/contracts/bridge/Redemption.sol/Redemption.json +92 -0
  65. package/build/contracts/bridge/VendingMachine.sol/VendingMachine.dbg.json +1 -1
  66. package/build/contracts/bridge/VendingMachine.sol/VendingMachine.json +2 -2
  67. package/build/contracts/bridge/Wallets.sol/Wallets.dbg.json +4 -0
  68. package/build/contracts/bridge/Wallets.sol/Wallets.json +112 -0
  69. package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
  70. package/build/contracts/token/TBTC.sol/TBTC.json +2 -2
  71. package/build/contracts/vault/DonationVault.sol/DonationVault.dbg.json +4 -0
  72. package/build/contracts/vault/DonationVault.sol/DonationVault.json +108 -0
  73. package/build/contracts/vault/IVault.sol/IVault.dbg.json +1 -1
  74. package/build/contracts/vault/IVault.sol/IVault.json +5 -0
  75. package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +1 -1
  76. package/build/contracts/vault/TBTCVault.sol/TBTCVault.json +273 -5
  77. package/contracts/GovernanceUtils.sol +4 -4
  78. package/contracts/bank/Bank.sol +113 -66
  79. package/contracts/bank/IReceiveBalanceApproval.sol +45 -0
  80. package/contracts/bridge/BitcoinTx.sol +267 -10
  81. package/contracts/bridge/Bridge.sol +1698 -245
  82. package/contracts/bridge/BridgeState.sol +768 -0
  83. package/contracts/bridge/Deposit.sol +269 -0
  84. package/contracts/bridge/DepositSweep.sol +574 -0
  85. package/contracts/bridge/EcdsaLib.sol +45 -0
  86. package/contracts/bridge/Fraud.sol +579 -0
  87. package/contracts/bridge/Heartbeat.sol +112 -0
  88. package/contracts/bridge/IRelay.sol +28 -0
  89. package/contracts/bridge/MovingFunds.sol +1077 -0
  90. package/contracts/bridge/Redemption.sol +1058 -0
  91. package/contracts/bridge/VendingMachine.sol +2 -2
  92. package/contracts/bridge/Wallets.sol +719 -0
  93. package/contracts/hardhat-dependency-compiler/.hardhat-dependency-compiler +1 -0
  94. package/contracts/hardhat-dependency-compiler/@keep-network/ecdsa/contracts/WalletRegistry.sol +3 -0
  95. package/contracts/hardhat-dependency-compiler/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol +3 -0
  96. package/contracts/hardhat-dependency-compiler/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +3 -0
  97. package/contracts/token/TBTC.sol +1 -1
  98. package/contracts/vault/DonationVault.sol +125 -0
  99. package/contracts/vault/IVault.sol +6 -22
  100. package/contracts/vault/TBTCVault.sol +188 -29
  101. package/deploy/00_resolve_relay.ts +28 -0
  102. package/deploy/{03_transfer_roles.ts → 03_transfer_vending_machine_roles.ts} +1 -1
  103. package/deploy/04_deploy_bank.ts +27 -0
  104. package/deploy/05_deploy_bridge.ts +80 -0
  105. package/deploy/06_deploy_tbtc_vault.ts +30 -0
  106. package/deploy/07_bank_update_bridge.ts +19 -0
  107. package/deploy/08_transfer_bank_ownership.ts +15 -0
  108. package/deploy/09_transfer_tbtc_vault_ownership.ts +15 -0
  109. package/deploy/10_transfer_bridge_governance.ts +20 -0
  110. package/deploy/11_initialize_wallet_owner.ts +18 -0
  111. package/deploy/11_transfer_proxy_admin_ownership.ts +30 -0
  112. package/deploy/12_deploy_proxy_admin_with_deputy.ts +33 -0
  113. package/export.json +15771 -443
  114. package/package.json +34 -26
  115. package/artifacts/solcInputs/524094faac10a04084fcc411e06dab84.json +0 -128
@@ -0,0 +1,719 @@
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 {BTCUtils} from "@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol";
19
+ import {EcdsaDkg} from "@keep-network/ecdsa/contracts/libraries/EcdsaDkg.sol";
20
+ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
21
+
22
+ import "./BitcoinTx.sol";
23
+ import "./EcdsaLib.sol";
24
+ import "./BridgeState.sol";
25
+
26
+ /// @title Wallet library
27
+ /// @notice Library responsible for handling integration between Bridge
28
+ /// contract and ECDSA wallets.
29
+ library Wallets {
30
+ using BTCUtils for bytes;
31
+
32
+ /// @notice Represents wallet state:
33
+ enum WalletState {
34
+ /// @dev The wallet is unknown to the Bridge.
35
+ Unknown,
36
+ /// @dev The wallet can sweep deposits and accept redemption requests.
37
+ Live,
38
+ /// @dev The wallet was deemed unhealthy and is expected to move their
39
+ /// outstanding funds to another wallet. The wallet can still
40
+ /// fulfill their pending redemption requests although new
41
+ /// redemption requests and new deposit reveals are not accepted.
42
+ MovingFunds,
43
+ /// @dev The wallet moved or redeemed all their funds and is in the
44
+ /// closing period where it is still a 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
+ /// can no longer perform any action in the Bridge.
52
+ Closed,
53
+ /// @dev The wallet committed a fraud that was reported, did not move
54
+ /// funds to another wallet before a timeout, or did not sweep
55
+ /// funds moved to if from another wallet before a timeout. The
56
+ /// wallet is blocked and can not perform any actions in the Bridge.
57
+ /// Off-chain coordination with the wallet operators is needed to
58
+ /// recover funds.
59
+ Terminated
60
+ }
61
+
62
+ /// @notice Holds information about a wallet.
63
+ struct Wallet {
64
+ // Identifier of a ECDSA Wallet registered in the ECDSA Wallet Registry.
65
+ bytes32 ecdsaWalletID;
66
+ // Latest wallet's main UTXO hash computed as
67
+ // keccak256(txHash | txOutputIndex | txOutputValue). The `tx` prefix
68
+ // refers to the transaction which created that main UTXO. The `txHash`
69
+ // is `bytes32` (ordered as in Bitcoin internally), `txOutputIndex`
70
+ // an `uint32`, and `txOutputValue` an `uint64` value.
71
+ bytes32 mainUtxoHash;
72
+ // The total redeemable value of pending redemption requests targeting
73
+ // that wallet.
74
+ uint64 pendingRedemptionsValue;
75
+ // UNIX timestamp the wallet was created at.
76
+ uint32 createdAt;
77
+ // UNIX timestamp indicating the moment the wallet was requested to
78
+ // move their funds.
79
+ uint32 movingFundsRequestedAt;
80
+ // UNIX timestamp indicating the moment the wallet's closing period
81
+ // started.
82
+ uint32 closingStartedAt;
83
+ // Total count of pending moved funds sweep requests targeting this wallet.
84
+ uint32 pendingMovedFundsSweepRequestsCount;
85
+ // Current state of the wallet.
86
+ WalletState state;
87
+ // Moving funds target wallet commitment submitted by the wallet. It
88
+ // is built by applying the keccak256 hash over the list of 20-byte
89
+ // public key hashes of the target wallets.
90
+ bytes32 movingFundsTargetWalletsCommitmentHash;
91
+ // This struct doesn't contain `__gap` property as the structure is stored
92
+ // in a mapping, mappings store values in different slots and they are
93
+ // not contiguous with other values.
94
+ }
95
+
96
+ event NewWalletRequested();
97
+
98
+ event NewWalletRegistered(
99
+ bytes32 indexed ecdsaWalletID,
100
+ bytes20 indexed walletPubKeyHash
101
+ );
102
+
103
+ event WalletMovingFunds(
104
+ bytes32 indexed ecdsaWalletID,
105
+ bytes20 indexed walletPubKeyHash
106
+ );
107
+
108
+ event WalletClosing(
109
+ bytes32 indexed ecdsaWalletID,
110
+ bytes20 indexed walletPubKeyHash
111
+ );
112
+
113
+ event WalletClosed(
114
+ bytes32 indexed ecdsaWalletID,
115
+ bytes20 indexed walletPubKeyHash
116
+ );
117
+
118
+ event WalletTerminated(
119
+ bytes32 indexed ecdsaWalletID,
120
+ bytes20 indexed walletPubKeyHash
121
+ );
122
+
123
+ /// @notice Requests creation of a new wallet. This function just
124
+ /// forms a request and the creation process is performed
125
+ /// asynchronously. Outcome of that process should be delivered
126
+ /// using `registerNewWallet` function.
127
+ /// @param activeWalletMainUtxo Data of the active wallet's main UTXO, as
128
+ /// currently known on the Ethereum chain.
129
+ /// @dev Requirements:
130
+ /// - `activeWalletMainUtxo` components must point to the recent main
131
+ /// UTXO of the given active wallet, as currently known on the
132
+ /// Ethereum chain. If there is no active wallet at the moment, or
133
+ /// the active wallet has no main UTXO, this parameter can be
134
+ /// empty as it is ignored,
135
+ /// - Wallet creation must not be in progress,
136
+ /// - If the active wallet is set, one of the following
137
+ /// conditions must be true:
138
+ /// - The active wallet BTC balance is above the minimum threshold
139
+ /// and the active wallet is old enough, i.e. the creation period
140
+ /// was elapsed since its creation time,
141
+ /// - The active wallet BTC balance is above the maximum threshold.
142
+ function requestNewWallet(
143
+ BridgeState.Storage storage self,
144
+ BitcoinTx.UTXO calldata activeWalletMainUtxo
145
+ ) external {
146
+ require(
147
+ self.ecdsaWalletRegistry.getWalletCreationState() ==
148
+ EcdsaDkg.State.IDLE,
149
+ "Wallet creation already in progress"
150
+ );
151
+
152
+ bytes20 activeWalletPubKeyHash = self.activeWalletPubKeyHash;
153
+
154
+ // If the active wallet is set, fetch this wallet's details from
155
+ // storage to perform conditions check. The `registerNewWallet`
156
+ // function guarantees an active wallet is always one of the
157
+ // registered ones.
158
+ if (activeWalletPubKeyHash != bytes20(0)) {
159
+ uint64 activeWalletBtcBalance = getWalletBtcBalance(
160
+ self,
161
+ activeWalletPubKeyHash,
162
+ activeWalletMainUtxo
163
+ );
164
+ uint32 activeWalletCreatedAt = self
165
+ .registeredWallets[activeWalletPubKeyHash]
166
+ .createdAt;
167
+ /* solhint-disable-next-line not-rely-on-time */
168
+ bool activeWalletOldEnough = block.timestamp >=
169
+ activeWalletCreatedAt + self.walletCreationPeriod;
170
+
171
+ require(
172
+ (activeWalletOldEnough &&
173
+ activeWalletBtcBalance >=
174
+ self.walletCreationMinBtcBalance) ||
175
+ activeWalletBtcBalance >= self.walletCreationMaxBtcBalance,
176
+ "Wallet creation conditions are not met"
177
+ );
178
+ }
179
+
180
+ emit NewWalletRequested();
181
+
182
+ self.ecdsaWalletRegistry.requestNewWallet();
183
+ }
184
+
185
+ /// @notice Registers a new wallet. This function should be called
186
+ /// after the wallet creation process initiated using
187
+ /// `requestNewWallet` completes and brings the outcomes.
188
+ /// @param ecdsaWalletID Wallet's unique identifier.
189
+ /// @param publicKeyX Wallet's public key's X coordinate.
190
+ /// @param publicKeyY Wallet's public key's Y coordinate.
191
+ /// @dev Requirements:
192
+ /// - The only caller authorized to call this function is `registry`,
193
+ /// - Given wallet data must not belong to an already registered wallet.
194
+ function registerNewWallet(
195
+ BridgeState.Storage storage self,
196
+ bytes32 ecdsaWalletID,
197
+ bytes32 publicKeyX,
198
+ bytes32 publicKeyY
199
+ ) external {
200
+ require(
201
+ msg.sender == address(self.ecdsaWalletRegistry),
202
+ "Caller is not the ECDSA Wallet Registry"
203
+ );
204
+
205
+ // Compress wallet's public key and calculate Bitcoin's hash160 of it.
206
+ bytes20 walletPubKeyHash = bytes20(
207
+ EcdsaLib.compressPublicKey(publicKeyX, publicKeyY).hash160View()
208
+ );
209
+
210
+ Wallet storage wallet = self.registeredWallets[walletPubKeyHash];
211
+ require(
212
+ wallet.state == WalletState.Unknown,
213
+ "ECDSA wallet has been already registered"
214
+ );
215
+ wallet.ecdsaWalletID = ecdsaWalletID;
216
+ wallet.state = WalletState.Live;
217
+ /* solhint-disable-next-line not-rely-on-time */
218
+ wallet.createdAt = uint32(block.timestamp);
219
+
220
+ // Set the freshly created wallet as the new active wallet.
221
+ self.activeWalletPubKeyHash = walletPubKeyHash;
222
+
223
+ self.liveWalletsCount++;
224
+
225
+ emit NewWalletRegistered(ecdsaWalletID, walletPubKeyHash);
226
+ }
227
+
228
+ /// @notice Handles a notification about a wallet redemption timeout.
229
+ /// Triggers the wallet moving funds process only if the wallet is
230
+ /// still in the Live state. That means multiple action timeouts can
231
+ /// be reported for the same wallet but only the first report
232
+ /// requests the wallet to move their funds. Executes slashing if
233
+ /// the wallet is in Live or MovingFunds state. Allows to notify
234
+ /// redemption timeout also for a Terminated wallet in case the
235
+ /// redemption was requested before the wallet got terminated.
236
+ /// @param walletPubKeyHash 20-byte public key hash of the wallet.
237
+ /// @param walletMembersIDs Identifiers of the wallet signing group members.
238
+ /// @dev Requirements:
239
+ /// - The wallet must be in the `Live`, `MovingFunds`,
240
+ /// or `Terminated` state.
241
+ function notifyWalletRedemptionTimeout(
242
+ BridgeState.Storage storage self,
243
+ bytes20 walletPubKeyHash,
244
+ uint32[] calldata walletMembersIDs
245
+ ) internal {
246
+ Wallet storage wallet = self.registeredWallets[walletPubKeyHash];
247
+ WalletState walletState = wallet.state;
248
+
249
+ require(
250
+ walletState == WalletState.Live ||
251
+ walletState == WalletState.MovingFunds ||
252
+ walletState == WalletState.Terminated,
253
+ "Wallet must be in Live or MovingFunds or Terminated state"
254
+ );
255
+
256
+ if (
257
+ walletState == Wallets.WalletState.Live ||
258
+ walletState == Wallets.WalletState.MovingFunds
259
+ ) {
260
+ // Slash the wallet operators and reward the notifier
261
+ self.ecdsaWalletRegistry.seize(
262
+ self.redemptionTimeoutSlashingAmount,
263
+ self.redemptionTimeoutNotifierRewardMultiplier,
264
+ msg.sender,
265
+ wallet.ecdsaWalletID,
266
+ walletMembersIDs
267
+ );
268
+ }
269
+
270
+ if (walletState == WalletState.Live) {
271
+ moveFunds(self, walletPubKeyHash);
272
+ }
273
+ }
274
+
275
+ /// @notice Handles a notification about a wallet heartbeat failure and
276
+ /// triggers the wallet moving funds process.
277
+ /// @param publicKeyX Wallet's public key's X coordinate.
278
+ /// @param publicKeyY Wallet's public key's Y coordinate.
279
+ /// @dev Requirements:
280
+ /// - The only caller authorized to call this function is `registry`,
281
+ /// - Wallet must be in Live state.
282
+ function notifyWalletHeartbeatFailed(
283
+ BridgeState.Storage storage self,
284
+ bytes32 publicKeyX,
285
+ bytes32 publicKeyY
286
+ ) external {
287
+ require(
288
+ msg.sender == address(self.ecdsaWalletRegistry),
289
+ "Caller is not the ECDSA Wallet Registry"
290
+ );
291
+
292
+ // Compress wallet's public key and calculate Bitcoin's hash160 of it.
293
+ bytes20 walletPubKeyHash = bytes20(
294
+ EcdsaLib.compressPublicKey(publicKeyX, publicKeyY).hash160View()
295
+ );
296
+
297
+ require(
298
+ self.registeredWallets[walletPubKeyHash].state == WalletState.Live,
299
+ "Wallet must be in Live state"
300
+ );
301
+
302
+ moveFunds(self, walletPubKeyHash);
303
+ }
304
+
305
+ /// @notice Notifies that the wallet is either old enough or has too few
306
+ /// satoshis left and qualifies to be closed.
307
+ /// @param walletPubKeyHash 20-byte public key hash of the wallet.
308
+ /// @param walletMainUtxo Data of the wallet's main UTXO, as currently
309
+ /// known on the Ethereum chain.
310
+ /// @dev Requirements:
311
+ /// - Wallet must not be set as the current active wallet,
312
+ /// - Wallet must exceed the wallet maximum age OR the wallet BTC
313
+ /// balance must be lesser than the minimum threshold. If the latter
314
+ /// case is true, the `walletMainUtxo` components must point to the
315
+ /// recent main UTXO of the given wallet, as currently known on the
316
+ /// Ethereum chain. If the wallet has no main UTXO, this parameter
317
+ /// can be empty as it is ignored since the wallet balance is
318
+ /// assumed to be zero,
319
+ /// - Wallet must be in Live state.
320
+ function notifyWalletCloseable(
321
+ BridgeState.Storage storage self,
322
+ bytes20 walletPubKeyHash,
323
+ BitcoinTx.UTXO calldata walletMainUtxo
324
+ ) external {
325
+ require(
326
+ self.activeWalletPubKeyHash != walletPubKeyHash,
327
+ "Active wallet cannot be considered closeable"
328
+ );
329
+
330
+ Wallet storage wallet = self.registeredWallets[walletPubKeyHash];
331
+ require(
332
+ wallet.state == WalletState.Live,
333
+ "Wallet must be in Live state"
334
+ );
335
+
336
+ /* solhint-disable-next-line not-rely-on-time */
337
+ bool walletOldEnough = block.timestamp >=
338
+ wallet.createdAt + self.walletMaxAge;
339
+
340
+ require(
341
+ walletOldEnough ||
342
+ getWalletBtcBalance(self, walletPubKeyHash, walletMainUtxo) <
343
+ self.walletClosureMinBtcBalance,
344
+ "Wallet needs to be old enough or have too few satoshis"
345
+ );
346
+
347
+ moveFunds(self, walletPubKeyHash);
348
+ }
349
+
350
+ /// @notice Notifies about the end of the closing period for the given wallet.
351
+ /// Closes the wallet ultimately and notifies the ECDSA registry
352
+ /// about this fact.
353
+ /// @param walletPubKeyHash 20-byte public key hash of the wallet.
354
+ /// @dev Requirements:
355
+ /// - The wallet must be in the Closing state,
356
+ /// - The wallet closing period must have elapsed.
357
+ function notifyWalletClosingPeriodElapsed(
358
+ BridgeState.Storage storage self,
359
+ bytes20 walletPubKeyHash
360
+ ) internal {
361
+ Wallet storage wallet = self.registeredWallets[walletPubKeyHash];
362
+
363
+ require(
364
+ wallet.state == WalletState.Closing,
365
+ "Wallet must be in Closing state"
366
+ );
367
+
368
+ require(
369
+ /* solhint-disable-next-line not-rely-on-time */
370
+ block.timestamp >
371
+ wallet.closingStartedAt + self.walletClosingPeriod,
372
+ "Closing period has not elapsed yet"
373
+ );
374
+
375
+ finalizeWalletClosing(self, walletPubKeyHash);
376
+ }
377
+
378
+ /// @notice Notifies that the wallet completed the moving funds process
379
+ /// successfully. Checks if the funds were moved to the expected
380
+ /// target wallets. Closes the source wallet if everything went
381
+ /// good and reverts otherwise.
382
+ /// @param walletPubKeyHash 20-byte public key hash of the wallet.
383
+ /// @param targetWalletsHash 32-byte keccak256 hash over the list of
384
+ /// 20-byte public key hashes of the target wallets actually used
385
+ /// within the moving funds transactions.
386
+ /// @dev Requirements:
387
+ /// - The caller must make sure the moving funds transaction actually
388
+ /// happened on Bitcoin chain and fits the protocol requirements,
389
+ /// - The source wallet must be in the MovingFunds state,
390
+ /// - The target wallets commitment must be submitted by the source
391
+ /// wallet,
392
+ /// - The actual target wallets used in the moving funds transaction
393
+ /// must be exactly the same as the target wallets commitment.
394
+ function notifyWalletFundsMoved(
395
+ BridgeState.Storage storage self,
396
+ bytes20 walletPubKeyHash,
397
+ bytes32 targetWalletsHash
398
+ ) internal {
399
+ Wallet storage wallet = self.registeredWallets[walletPubKeyHash];
400
+ // Check that the wallet is in the MovingFunds state but don't check
401
+ // if the moving funds timeout is exceeded. That should give a
402
+ // possibility to move funds in case when timeout was hit but was
403
+ // not reported yet.
404
+ require(
405
+ wallet.state == WalletState.MovingFunds,
406
+ "Wallet must be in MovingFunds state"
407
+ );
408
+
409
+ bytes32 targetWalletsCommitmentHash = wallet
410
+ .movingFundsTargetWalletsCommitmentHash;
411
+
412
+ require(
413
+ targetWalletsCommitmentHash != bytes32(0),
414
+ "Target wallets commitment not submitted yet"
415
+ );
416
+
417
+ // Make sure that the target wallets where funds were moved to are
418
+ // exactly the same as the ones the source wallet committed to.
419
+ require(
420
+ targetWalletsCommitmentHash == targetWalletsHash,
421
+ "Target wallets don't correspond to the commitment"
422
+ );
423
+
424
+ // If funds were moved, the wallet has no longer a main UTXO.
425
+ delete wallet.mainUtxoHash;
426
+
427
+ beginWalletClosing(self, walletPubKeyHash);
428
+ }
429
+
430
+ /// @notice Called when a MovingFunds wallet has a balance below the dust
431
+ /// threshold. Begins the wallet closing.
432
+ /// @param walletPubKeyHash 20-byte public key hash of the wallet.
433
+ /// @dev Requirements:
434
+ /// - The wallet must be in the MovingFunds state.
435
+ function notifyWalletMovingFundsBelowDust(
436
+ BridgeState.Storage storage self,
437
+ bytes20 walletPubKeyHash
438
+ ) internal {
439
+ WalletState walletState = self
440
+ .registeredWallets[walletPubKeyHash]
441
+ .state;
442
+
443
+ require(
444
+ walletState == Wallets.WalletState.MovingFunds,
445
+ "Wallet must be in MovingFunds state"
446
+ );
447
+
448
+ beginWalletClosing(self, walletPubKeyHash);
449
+ }
450
+
451
+ /// @notice Called when the timeout for MovingFunds for the wallet elapsed.
452
+ /// Slashes wallet members and terminates the wallet.
453
+ /// @param walletPubKeyHash 20-byte public key hash of the wallet.
454
+ /// @param walletMembersIDs Identifiers of the wallet signing group members.
455
+ /// @dev Requirements:
456
+ /// - The wallet must be in the MovingFunds state.
457
+ function notifyWalletMovingFundsTimeout(
458
+ BridgeState.Storage storage self,
459
+ bytes20 walletPubKeyHash,
460
+ uint32[] calldata walletMembersIDs
461
+ ) internal {
462
+ Wallets.Wallet storage wallet = self.registeredWallets[
463
+ walletPubKeyHash
464
+ ];
465
+
466
+ require(
467
+ wallet.state == Wallets.WalletState.MovingFunds,
468
+ "Wallet must be in MovingFunds state"
469
+ );
470
+
471
+ self.ecdsaWalletRegistry.seize(
472
+ self.movingFundsTimeoutSlashingAmount,
473
+ self.movingFundsTimeoutNotifierRewardMultiplier,
474
+ msg.sender,
475
+ wallet.ecdsaWalletID,
476
+ walletMembersIDs
477
+ );
478
+
479
+ terminateWallet(self, walletPubKeyHash);
480
+ }
481
+
482
+ /// @notice Called when a wallet which was asked to sweep funds moved from
483
+ /// another wallet did not provide a sweeping proof before a timeout.
484
+ /// Slashes and terminates the wallet who failed to provide a proof.
485
+ /// @param walletPubKeyHash 20-byte public key hash of the wallet which was
486
+ /// supposed to sweep funds.
487
+ /// @param walletMembersIDs Identifiers of the wallet signing group members.
488
+ /// @dev Requirements:
489
+ /// - The wallet must be in the `Live`, `MovingFunds`,
490
+ /// or `Terminated` state.
491
+ function notifyWalletMovedFundsSweepTimeout(
492
+ BridgeState.Storage storage self,
493
+ bytes20 walletPubKeyHash,
494
+ uint32[] calldata walletMembersIDs
495
+ ) internal {
496
+ Wallet storage wallet = self.registeredWallets[walletPubKeyHash];
497
+ WalletState walletState = wallet.state;
498
+
499
+ require(
500
+ walletState == WalletState.Live ||
501
+ walletState == WalletState.MovingFunds ||
502
+ walletState == WalletState.Terminated,
503
+ "Wallet must be in Live or MovingFunds or Terminated state"
504
+ );
505
+
506
+ if (
507
+ walletState == Wallets.WalletState.Live ||
508
+ walletState == Wallets.WalletState.MovingFunds
509
+ ) {
510
+ self.ecdsaWalletRegistry.seize(
511
+ self.movedFundsSweepTimeoutSlashingAmount,
512
+ self.movedFundsSweepTimeoutNotifierRewardMultiplier,
513
+ msg.sender,
514
+ wallet.ecdsaWalletID,
515
+ walletMembersIDs
516
+ );
517
+
518
+ terminateWallet(self, walletPubKeyHash);
519
+ }
520
+ }
521
+
522
+ /// @notice Called when a wallet which was challenged for a fraud did not
523
+ /// defeat the challenge before the timeout. Slashes and terminates
524
+ /// the wallet who failed to defeat the challenge. If the wallet is
525
+ /// already terminated, it does nothing.
526
+ /// @param walletPubKeyHash 20-byte public key hash of the wallet which was
527
+ /// supposed to sweep funds.
528
+ /// @param walletMembersIDs Identifiers of the wallet signing group members.
529
+ /// @param challenger Address of the party which submitted the fraud
530
+ /// challenge.
531
+ /// @dev Requirements:
532
+ /// - The wallet must be in the `Live`, `MovingFunds`, `Closing`
533
+ /// or `Terminated` state.
534
+ function notifyWalletFraudChallengeDefeatTimeout(
535
+ BridgeState.Storage storage self,
536
+ bytes20 walletPubKeyHash,
537
+ uint32[] calldata walletMembersIDs,
538
+ address challenger
539
+ ) internal {
540
+ Wallet storage wallet = self.registeredWallets[walletPubKeyHash];
541
+ WalletState walletState = wallet.state;
542
+
543
+ if (
544
+ walletState == Wallets.WalletState.Live ||
545
+ walletState == Wallets.WalletState.MovingFunds ||
546
+ walletState == Wallets.WalletState.Closing
547
+ ) {
548
+ self.ecdsaWalletRegistry.seize(
549
+ self.fraudSlashingAmount,
550
+ self.fraudNotifierRewardMultiplier,
551
+ challenger,
552
+ wallet.ecdsaWalletID,
553
+ walletMembersIDs
554
+ );
555
+
556
+ terminateWallet(self, walletPubKeyHash);
557
+ } else if (walletState == Wallets.WalletState.Terminated) {
558
+ // This is a special case when the wallet was already terminated
559
+ // due to a previous deliberate protocol violation. In that
560
+ // case, this function should be still callable for other fraud
561
+ // challenges timeouts in order to let the challenger unlock its
562
+ // ETH deposit back. However, the wallet termination logic is
563
+ // not called and the challenger is not rewarded.
564
+ } else {
565
+ revert(
566
+ "Wallet must be in Live or MovingFunds or Closing or Terminated state"
567
+ );
568
+ }
569
+ }
570
+
571
+ /// @notice Requests a wallet to move their funds. If the wallet balance
572
+ /// is zero, the wallet closing begins immediately. If the move
573
+ /// funds request refers to the current active wallet, such a wallet
574
+ /// is no longer considered active and the active wallet slot
575
+ /// is unset allowing to trigger a new wallet creation immediately.
576
+ /// @param walletPubKeyHash 20-byte public key hash of the wallet.
577
+ /// @dev Requirements:
578
+ /// - The caller must make sure that the wallet is in the Live state.
579
+ function moveFunds(
580
+ BridgeState.Storage storage self,
581
+ bytes20 walletPubKeyHash
582
+ ) internal {
583
+ Wallet storage wallet = self.registeredWallets[walletPubKeyHash];
584
+
585
+ if (wallet.mainUtxoHash == bytes32(0)) {
586
+ // If the wallet has no main UTXO, that means its BTC balance
587
+ // is zero and the wallet closing should begin immediately.
588
+ beginWalletClosing(self, walletPubKeyHash);
589
+ } else {
590
+ // Otherwise, initialize the moving funds process.
591
+ wallet.state = WalletState.MovingFunds;
592
+ /* solhint-disable-next-line not-rely-on-time */
593
+ wallet.movingFundsRequestedAt = uint32(block.timestamp);
594
+
595
+ // slither-disable-next-line reentrancy-events
596
+ emit WalletMovingFunds(wallet.ecdsaWalletID, walletPubKeyHash);
597
+ }
598
+
599
+ if (self.activeWalletPubKeyHash == walletPubKeyHash) {
600
+ // If the move funds request refers to the current active wallet,
601
+ // unset the active wallet and make the wallet creation process
602
+ // possible in order to get a new healthy active wallet.
603
+ delete self.activeWalletPubKeyHash;
604
+ }
605
+
606
+ self.liveWalletsCount--;
607
+ }
608
+
609
+ /// @notice Begins the closing period of the given wallet.
610
+ /// @param walletPubKeyHash 20-byte public key hash of the wallet.
611
+ /// @dev Requirements:
612
+ /// - The caller must make sure that the wallet is in the
613
+ /// MovingFunds state.
614
+ function beginWalletClosing(
615
+ BridgeState.Storage storage self,
616
+ bytes20 walletPubKeyHash
617
+ ) internal {
618
+ Wallet storage wallet = self.registeredWallets[walletPubKeyHash];
619
+ // Initialize the closing period.
620
+ wallet.state = WalletState.Closing;
621
+ /* solhint-disable-next-line not-rely-on-time */
622
+ wallet.closingStartedAt = uint32(block.timestamp);
623
+
624
+ // slither-disable-next-line reentrancy-events
625
+ emit WalletClosing(wallet.ecdsaWalletID, walletPubKeyHash);
626
+ }
627
+
628
+ /// @notice Finalizes the closing period of the given wallet and notifies
629
+ /// the ECDSA registry about this fact.
630
+ /// @param walletPubKeyHash 20-byte public key hash of the wallet.
631
+ /// @dev Requirements:
632
+ /// - The caller must make sure that the wallet is in the Closing state.
633
+ function finalizeWalletClosing(
634
+ BridgeState.Storage storage self,
635
+ bytes20 walletPubKeyHash
636
+ ) internal {
637
+ Wallet storage wallet = self.registeredWallets[walletPubKeyHash];
638
+
639
+ wallet.state = WalletState.Closed;
640
+
641
+ emit WalletClosed(wallet.ecdsaWalletID, walletPubKeyHash);
642
+
643
+ self.ecdsaWalletRegistry.closeWallet(wallet.ecdsaWalletID);
644
+ }
645
+
646
+ /// @notice Terminates the given wallet and notifies the ECDSA registry
647
+ /// about this fact. If the wallet termination refers to the current
648
+ /// active wallet, such a wallet is no longer considered active and
649
+ /// the active wallet slot is unset allowing to trigger a new wallet
650
+ /// creation immediately.
651
+ /// @param walletPubKeyHash 20-byte public key hash of the wallet.
652
+ /// @dev Requirements:
653
+ /// - The caller must make sure that the wallet is in the
654
+ /// Live or MovingFunds or Closing state.
655
+ function terminateWallet(
656
+ BridgeState.Storage storage self,
657
+ bytes20 walletPubKeyHash
658
+ ) internal {
659
+ Wallet storage wallet = self.registeredWallets[walletPubKeyHash];
660
+
661
+ if (wallet.state == WalletState.Live) {
662
+ self.liveWalletsCount--;
663
+ }
664
+
665
+ wallet.state = WalletState.Terminated;
666
+
667
+ // slither-disable-next-line reentrancy-events
668
+ emit WalletTerminated(wallet.ecdsaWalletID, walletPubKeyHash);
669
+
670
+ if (self.activeWalletPubKeyHash == walletPubKeyHash) {
671
+ // If termination refers to the current active wallet,
672
+ // unset the active wallet and make the wallet creation process
673
+ // possible in order to get a new healthy active wallet.
674
+ delete self.activeWalletPubKeyHash;
675
+ }
676
+
677
+ self.ecdsaWalletRegistry.closeWallet(wallet.ecdsaWalletID);
678
+ }
679
+
680
+ /// @notice Gets BTC balance for given the wallet.
681
+ /// @param walletPubKeyHash 20-byte public key hash of the wallet.
682
+ /// @param walletMainUtxo Data of the wallet's main UTXO, as currently
683
+ /// known on the Ethereum chain.
684
+ /// @return walletBtcBalance Current BTC balance for the given wallet.
685
+ /// @dev Requirements:
686
+ /// - `walletMainUtxo` components must point to the recent main UTXO
687
+ /// of the given wallet, as currently known on the Ethereum chain.
688
+ /// If the wallet has no main UTXO, this parameter can be empty as it
689
+ /// is ignored.
690
+ function getWalletBtcBalance(
691
+ BridgeState.Storage storage self,
692
+ bytes20 walletPubKeyHash,
693
+ BitcoinTx.UTXO calldata walletMainUtxo
694
+ ) internal view returns (uint64 walletBtcBalance) {
695
+ bytes32 walletMainUtxoHash = self
696
+ .registeredWallets[walletPubKeyHash]
697
+ .mainUtxoHash;
698
+
699
+ // If the wallet has a main UTXO hash set, cross-check it with the
700
+ // provided plain-text parameter and get the transaction output value
701
+ // as BTC balance. Otherwise, the BTC balance is just zero.
702
+ if (walletMainUtxoHash != bytes32(0)) {
703
+ require(
704
+ keccak256(
705
+ abi.encodePacked(
706
+ walletMainUtxo.txHash,
707
+ walletMainUtxo.txOutputIndex,
708
+ walletMainUtxo.txOutputValue
709
+ )
710
+ ) == walletMainUtxoHash,
711
+ "Invalid wallet main UTXO data"
712
+ );
713
+
714
+ walletBtcBalance = walletMainUtxo.txOutputValue;
715
+ }
716
+
717
+ return walletBtcBalance;
718
+ }
719
+ }