@keep-network/tbtc-v2 0.1.1-dev.7 → 0.1.1-dev.70

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 (110) hide show
  1. package/README.adoc +12 -0
  2. package/artifacts/Bank.json +752 -0
  3. package/artifacts/Bridge.json +2556 -0
  4. package/artifacts/DefaultProxyAdmin.json +259 -0
  5. package/artifacts/Deposit.json +117 -0
  6. package/artifacts/DepositSweep.json +77 -0
  7. package/artifacts/EcdsaDkgValidator.json +533 -0
  8. package/artifacts/EcdsaInactivity.json +156 -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 +162 -0
  19. package/artifacts/ReimbursementPool.json +509 -0
  20. package/artifacts/Relay.json +123 -0
  21. package/artifacts/SortitionPool.json +1005 -0
  22. package/artifacts/T.json +1148 -0
  23. package/artifacts/TBTC.json +27 -26
  24. package/artifacts/TBTCToken.json +27 -26
  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 +30 -29
  29. package/artifacts/VendingMachineKeep.json +400 -0
  30. package/artifacts/VendingMachineNuCypher.json +400 -0
  31. package/artifacts/WalletRegistry.json +2117 -0
  32. package/artifacts/WalletRegistry_Implementation.json +2824 -0
  33. package/artifacts/WalletRegistry_Proxy.json +259 -0
  34. package/artifacts/Wallets.json +186 -0
  35. package/artifacts/solcInputs/1635d55d57a0a2552952c0d22586ed23.json +56 -0
  36. package/artifacts/solcInputs/922f95457ca7980d018c851fb3308a7e.json +269 -0
  37. package/build/contracts/GovernanceUtils.sol/GovernanceUtils.dbg.json +1 -1
  38. package/build/contracts/GovernanceUtils.sol/GovernanceUtils.json +2 -2
  39. package/build/contracts/bank/Bank.sol/Bank.dbg.json +1 -1
  40. package/build/contracts/bank/Bank.sol/Bank.json +20 -2
  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 +2546 -128
  45. package/build/contracts/bridge/BridgeState.sol/BridgeState.dbg.json +4 -0
  46. package/build/contracts/bridge/BridgeState.sol/BridgeState.json +220 -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 +103 -0
  73. package/build/contracts/vault/IVault.sol/IVault.dbg.json +1 -1
  74. package/build/contracts/vault/IVault.sol/IVault.json +19 -1
  75. package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +1 -1
  76. package/build/contracts/vault/TBTCVault.sol/TBTCVault.json +36 -18
  77. package/contracts/GovernanceUtils.sol +1 -1
  78. package/contracts/bank/Bank.sol +34 -18
  79. package/contracts/bridge/BitcoinTx.sol +231 -9
  80. package/contracts/bridge/Bridge.sol +1592 -211
  81. package/contracts/bridge/BridgeState.sol +714 -0
  82. package/contracts/bridge/Deposit.sol +271 -0
  83. package/contracts/bridge/DepositSweep.sol +571 -0
  84. package/contracts/bridge/EcdsaLib.sol +45 -0
  85. package/contracts/bridge/Fraud.sol +604 -0
  86. package/contracts/bridge/Heartbeat.sol +112 -0
  87. package/contracts/bridge/IRelay.sol +28 -0
  88. package/contracts/bridge/MovingFunds.sol +1080 -0
  89. package/contracts/bridge/Redemption.sol +874 -0
  90. package/contracts/bridge/VendingMachine.sol +1 -1
  91. package/contracts/bridge/Wallets.sol +553 -0
  92. package/contracts/hardhat-dependency-compiler/.hardhat-dependency-compiler +1 -0
  93. package/contracts/hardhat-dependency-compiler/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol +3 -0
  94. package/contracts/hardhat-dependency-compiler/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +3 -0
  95. package/contracts/token/TBTC.sol +1 -1
  96. package/contracts/vault/DonationVault.sol +125 -0
  97. package/contracts/vault/IVault.sol +32 -10
  98. package/contracts/vault/TBTCVault.sol +20 -2
  99. package/deploy/00_resolve_relay.ts +28 -0
  100. package/deploy/00_resolve_wallet_registry.ts +83 -0
  101. package/deploy/04_deploy_bank.ts +27 -0
  102. package/deploy/05_deploy_bridge.ts +76 -0
  103. package/deploy/06_bank_update_bridge.ts +19 -0
  104. package/deploy/07_transfer_ownership.ts +15 -0
  105. package/deploy/08_transfer_governance.ts +20 -0
  106. package/deploy/09_transfer_proxy_admin_ownership.ts +23 -0
  107. package/deploy/10_deploy_proxy_admin_with_deputy.ts +33 -0
  108. package/export.json +16243 -475
  109. package/package.json +31 -24
  110. package/artifacts/solcInputs/25bea07ad744b8c97e466495ad2abf97.json +0 -128
@@ -1,6 +1,6 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
 
3
- pragma solidity 0.8.4;
3
+ pragma solidity ^0.8.9;
4
4
 
5
5
  import "@openzeppelin/contracts/access/Ownable.sol";
6
6
  import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
@@ -0,0 +1,553 @@
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 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.
52
+ Closed,
53
+ /// @dev The wallet committed a fraud that was reported. The wallet is
54
+ /// blocked and can not perform any actions in the Bridge.
55
+ /// Off-chain coordination with the wallet operators is needed to
56
+ /// recover funds.
57
+ Terminated
58
+ }
59
+
60
+ /// @notice Holds information about a wallet.
61
+ struct Wallet {
62
+ // Identifier of a ECDSA Wallet registered in the ECDSA Wallet Registry.
63
+ bytes32 ecdsaWalletID;
64
+ // Latest wallet's main UTXO hash computed as
65
+ // keccak256(txHash | txOutputIndex | txOutputValue). The `tx` prefix
66
+ // refers to the transaction which created that main UTXO. The `txHash`
67
+ // is `bytes32` (ordered as in Bitcoin internally), `txOutputIndex`
68
+ // an `uint32`, and `txOutputValue` an `uint64` value.
69
+ bytes32 mainUtxoHash;
70
+ // The total redeemable value of pending redemption requests targeting
71
+ // that wallet.
72
+ uint64 pendingRedemptionsValue;
73
+ // UNIX timestamp the wallet was created at.
74
+ uint32 createdAt;
75
+ // UNIX timestamp indicating the moment the wallet was requested to
76
+ // move their funds.
77
+ uint32 movingFundsRequestedAt;
78
+ // UNIX timestamp indicating the moment the wallet's closing period
79
+ // started.
80
+ uint32 closingStartedAt;
81
+ // Total count of pending moved funds sweep requests targeting this wallet.
82
+ uint32 pendingMovedFundsSweepRequestsCount;
83
+ // Current state of the wallet.
84
+ WalletState state;
85
+ // Moving funds target wallet commitment submitted by the wallet. It
86
+ // is built by applying the keccak256 hash over the list of 20-byte
87
+ // public key hashes of the target wallets.
88
+ bytes32 movingFundsTargetWalletsCommitmentHash;
89
+ // This struct doesn't contain `__gap` property as the structure is stored
90
+ // in a mapping, mappings store values in different slots and they are
91
+ // not contiguous with other values.
92
+ }
93
+
94
+ event NewWalletRequested();
95
+
96
+ event NewWalletRegistered(
97
+ bytes32 indexed ecdsaWalletID,
98
+ bytes20 indexed walletPubKeyHash
99
+ );
100
+
101
+ event WalletMovingFunds(
102
+ bytes32 indexed ecdsaWalletID,
103
+ bytes20 indexed walletPubKeyHash
104
+ );
105
+
106
+ event WalletClosing(
107
+ bytes32 indexed ecdsaWalletID,
108
+ bytes20 indexed walletPubKeyHash
109
+ );
110
+
111
+ event WalletClosed(
112
+ bytes32 indexed ecdsaWalletID,
113
+ bytes20 indexed walletPubKeyHash
114
+ );
115
+
116
+ event WalletTerminated(
117
+ bytes32 indexed ecdsaWalletID,
118
+ bytes20 indexed walletPubKeyHash
119
+ );
120
+
121
+ /// @notice Requests creation of a new wallet. This function just
122
+ /// forms a request and the creation process is performed
123
+ /// asynchronously. Outcome of that process should be delivered
124
+ /// using `registerNewWallet` function.
125
+ /// @param activeWalletMainUtxo Data of the active wallet's main UTXO, as
126
+ /// currently known on the Ethereum chain.
127
+ /// @dev Requirements:
128
+ /// - `activeWalletMainUtxo` components must point to the recent main
129
+ /// UTXO of the given active wallet, as currently known on the
130
+ /// Ethereum chain. If there is no active wallet at the moment, or
131
+ /// the active wallet has no main UTXO, this parameter can be
132
+ /// empty as it is ignored.
133
+ /// - Wallet creation must not be in progress
134
+ /// - If the active wallet is set, one of the following
135
+ /// conditions must be true:
136
+ /// - The active wallet BTC balance is above the minimum threshold
137
+ /// and the active wallet is old enough, i.e. the creation period
138
+ /// was elapsed since its creation time
139
+ /// - The active wallet BTC balance is above the maximum threshold
140
+ function requestNewWallet(
141
+ BridgeState.Storage storage self,
142
+ BitcoinTx.UTXO calldata activeWalletMainUtxo
143
+ ) external {
144
+ require(
145
+ self.ecdsaWalletRegistry.getWalletCreationState() ==
146
+ EcdsaDkg.State.IDLE,
147
+ "Wallet creation already in progress"
148
+ );
149
+
150
+ bytes20 activeWalletPubKeyHash = self.activeWalletPubKeyHash;
151
+
152
+ // If the active wallet is set, fetch this wallet's details from
153
+ // storage to perform conditions check. The `registerNewWallet`
154
+ // function guarantees an active wallet is always one of the
155
+ // registered ones.
156
+ if (activeWalletPubKeyHash != bytes20(0)) {
157
+ uint64 activeWalletBtcBalance = getWalletBtcBalance(
158
+ self,
159
+ activeWalletPubKeyHash,
160
+ activeWalletMainUtxo
161
+ );
162
+ uint32 activeWalletCreatedAt = self
163
+ .registeredWallets[activeWalletPubKeyHash]
164
+ .createdAt;
165
+ /* solhint-disable-next-line not-rely-on-time */
166
+ bool activeWalletOldEnough = block.timestamp >=
167
+ activeWalletCreatedAt + self.walletCreationPeriod;
168
+
169
+ require(
170
+ (activeWalletOldEnough &&
171
+ activeWalletBtcBalance >=
172
+ self.walletCreationMinBtcBalance) ||
173
+ activeWalletBtcBalance >= self.walletCreationMaxBtcBalance,
174
+ "Wallet creation conditions are not met"
175
+ );
176
+ }
177
+
178
+ emit NewWalletRequested();
179
+
180
+ self.ecdsaWalletRegistry.requestNewWallet();
181
+ }
182
+
183
+ /// @notice Gets BTC balance for given the wallet.
184
+ /// @param walletPubKeyHash 20-byte public key hash of the wallet
185
+ /// @param walletMainUtxo Data of the wallet's main UTXO, as currently
186
+ /// known on the Ethereum chain.
187
+ /// @return walletBtcBalance Current BTC balance for the given wallet.
188
+ /// @dev Requirements:
189
+ /// - `walletMainUtxo` components must point to the recent main UTXO
190
+ /// of the given wallet, as currently known on the Ethereum chain.
191
+ /// If the wallet has no main UTXO, this parameter can be empty as it
192
+ /// is ignored.
193
+ function getWalletBtcBalance(
194
+ BridgeState.Storage storage self,
195
+ bytes20 walletPubKeyHash,
196
+ BitcoinTx.UTXO calldata walletMainUtxo
197
+ ) internal view returns (uint64 walletBtcBalance) {
198
+ bytes32 walletMainUtxoHash = self
199
+ .registeredWallets[walletPubKeyHash]
200
+ .mainUtxoHash;
201
+
202
+ // If the wallet has a main UTXO hash set, cross-check it with the
203
+ // provided plain-text parameter and get the transaction output value
204
+ // as BTC balance. Otherwise, the BTC balance is just zero.
205
+ if (walletMainUtxoHash != bytes32(0)) {
206
+ require(
207
+ keccak256(
208
+ abi.encodePacked(
209
+ walletMainUtxo.txHash,
210
+ walletMainUtxo.txOutputIndex,
211
+ walletMainUtxo.txOutputValue
212
+ )
213
+ ) == walletMainUtxoHash,
214
+ "Invalid wallet main UTXO data"
215
+ );
216
+
217
+ walletBtcBalance = walletMainUtxo.txOutputValue;
218
+ }
219
+
220
+ return walletBtcBalance;
221
+ }
222
+
223
+ /// @notice Registers a new wallet. This function should be called
224
+ /// after the wallet creation process initiated using
225
+ /// `requestNewWallet` completes and brings the outcomes.
226
+ /// @param ecdsaWalletID Wallet's unique identifier.
227
+ /// @param publicKeyX Wallet's public key's X coordinate.
228
+ /// @param publicKeyY Wallet's public key's Y coordinate.
229
+ /// @dev Requirements:
230
+ /// - The only caller authorized to call this function is `registry`
231
+ /// - Given wallet data must not belong to an already registered wallet
232
+ function registerNewWallet(
233
+ BridgeState.Storage storage self,
234
+ bytes32 ecdsaWalletID,
235
+ bytes32 publicKeyX,
236
+ bytes32 publicKeyY
237
+ ) external {
238
+ require(
239
+ msg.sender == address(self.ecdsaWalletRegistry),
240
+ "Caller is not the ECDSA Wallet Registry"
241
+ );
242
+
243
+ // Compress wallet's public key and calculate Bitcoin's hash160 of it.
244
+ bytes20 walletPubKeyHash = bytes20(
245
+ EcdsaLib.compressPublicKey(publicKeyX, publicKeyY).hash160View()
246
+ );
247
+
248
+ Wallet storage wallet = self.registeredWallets[walletPubKeyHash];
249
+ require(
250
+ wallet.state == WalletState.Unknown,
251
+ "ECDSA wallet has been already registered"
252
+ );
253
+ wallet.ecdsaWalletID = ecdsaWalletID;
254
+ wallet.state = WalletState.Live;
255
+ /* solhint-disable-next-line not-rely-on-time */
256
+ wallet.createdAt = uint32(block.timestamp);
257
+
258
+ // Set the freshly created wallet as the new active wallet.
259
+ self.activeWalletPubKeyHash = walletPubKeyHash;
260
+
261
+ self.liveWalletsCount++;
262
+
263
+ emit NewWalletRegistered(ecdsaWalletID, walletPubKeyHash);
264
+ }
265
+
266
+ /// @notice Handles a notification about a wallet heartbeat failure and
267
+ /// triggers the wallet moving funds process.
268
+ /// @param publicKeyX Wallet's public key's X coordinate.
269
+ /// @param publicKeyY Wallet's public key's Y coordinate.
270
+ /// @dev Requirements:
271
+ /// - The only caller authorized to call this function is `registry`
272
+ /// - Wallet must be in Live state
273
+ function notifyWalletHeartbeatFailed(
274
+ BridgeState.Storage storage self,
275
+ bytes32 publicKeyX,
276
+ bytes32 publicKeyY
277
+ ) external {
278
+ require(
279
+ msg.sender == address(self.ecdsaWalletRegistry),
280
+ "Caller is not the ECDSA Wallet Registry"
281
+ );
282
+
283
+ // Compress wallet's public key and calculate Bitcoin's hash160 of it.
284
+ bytes20 walletPubKeyHash = bytes20(
285
+ EcdsaLib.compressPublicKey(publicKeyX, publicKeyY).hash160View()
286
+ );
287
+
288
+ require(
289
+ self.registeredWallets[walletPubKeyHash].state == WalletState.Live,
290
+ "ECDSA wallet must be in Live state"
291
+ );
292
+
293
+ moveFunds(self, walletPubKeyHash);
294
+ }
295
+
296
+ /// @notice Handles a notification about a wallet redemption timeout.
297
+ /// Triggers the wallet moving funds process only if the wallet is
298
+ /// still in the Live state. That means multiple action timeouts can
299
+ /// be reported for the same wallet but only the first report
300
+ /// requests the wallet to move their funds.
301
+ /// @param walletPubKeyHash 20-byte public key hash of the wallet
302
+ /// @dev Requirements:
303
+ /// - The wallet must be in the `Live` or `MovingFunds` state
304
+ function notifyWalletTimedOutRedemption(
305
+ BridgeState.Storage storage self,
306
+ bytes20 walletPubKeyHash
307
+ ) internal {
308
+ WalletState walletState = self
309
+ .registeredWallets[walletPubKeyHash]
310
+ .state;
311
+
312
+ require(
313
+ walletState == WalletState.Live ||
314
+ walletState == WalletState.MovingFunds,
315
+ "ECDSA wallet must be in Live or MovingFunds state"
316
+ );
317
+
318
+ if (walletState == WalletState.Live) {
319
+ moveFunds(self, walletPubKeyHash);
320
+ }
321
+ }
322
+
323
+ /// @notice Notifies that the wallet is either old enough or has too few
324
+ /// satoshis left and qualifies to be closed.
325
+ /// @param walletPubKeyHash 20-byte public key hash of the wallet
326
+ /// @param walletMainUtxo Data of the wallet's main UTXO, as currently
327
+ /// known on the Ethereum chain.
328
+ /// @dev Requirements:
329
+ /// - Wallet must not be set as the current active wallet
330
+ /// - Wallet must exceed the wallet maximum age OR the wallet BTC
331
+ /// balance must be lesser than the minimum threshold. If the latter
332
+ /// case is true, the `walletMainUtxo` components must point to the
333
+ /// recent main UTXO of the given wallet, as currently known on the
334
+ /// Ethereum chain. If the wallet has no main UTXO, this parameter
335
+ /// can be empty as it is ignored since the wallet balance is
336
+ /// assumed to be zero.
337
+ /// - Wallet must be in Live state
338
+ function notifyCloseableWallet(
339
+ BridgeState.Storage storage self,
340
+ bytes20 walletPubKeyHash,
341
+ BitcoinTx.UTXO calldata walletMainUtxo
342
+ ) external {
343
+ require(
344
+ self.activeWalletPubKeyHash != walletPubKeyHash,
345
+ "Active wallet cannot be considered closeable"
346
+ );
347
+
348
+ Wallet storage wallet = self.registeredWallets[walletPubKeyHash];
349
+ require(
350
+ wallet.state == WalletState.Live,
351
+ "ECDSA wallet must be in Live state"
352
+ );
353
+
354
+ /* solhint-disable-next-line not-rely-on-time */
355
+ bool walletOldEnough = block.timestamp >=
356
+ wallet.createdAt + self.walletMaxAge;
357
+
358
+ require(
359
+ walletOldEnough ||
360
+ getWalletBtcBalance(self, walletPubKeyHash, walletMainUtxo) <
361
+ self.walletClosureMinBtcBalance,
362
+ "Wallet needs to be old enough or have too few satoshis"
363
+ );
364
+
365
+ moveFunds(self, walletPubKeyHash);
366
+ }
367
+
368
+ /// @notice Requests a wallet to move their funds. If the wallet balance
369
+ /// is zero, the wallet closing begins immediately. If the move
370
+ /// funds request refers to the current active wallet, such a wallet
371
+ /// is no longer considered active and the active wallet slot
372
+ /// is unset allowing to trigger a new wallet creation immediately.
373
+ /// @param walletPubKeyHash 20-byte public key hash of the wallet
374
+ /// @dev Requirements:
375
+ /// - The caller must make sure that the wallet is in the Live state
376
+ function moveFunds(
377
+ BridgeState.Storage storage self,
378
+ bytes20 walletPubKeyHash
379
+ ) internal {
380
+ Wallet storage wallet = self.registeredWallets[walletPubKeyHash];
381
+
382
+ if (wallet.mainUtxoHash == bytes32(0)) {
383
+ // If the wallet has no main UTXO, that means its BTC balance
384
+ // is zero and the wallet closing should begin immediately.
385
+ beginWalletClosing(self, walletPubKeyHash);
386
+ } else {
387
+ // Otherwise, initialize the moving funds process.
388
+ wallet.state = WalletState.MovingFunds;
389
+ /* solhint-disable-next-line not-rely-on-time */
390
+ wallet.movingFundsRequestedAt = uint32(block.timestamp);
391
+
392
+ emit WalletMovingFunds(wallet.ecdsaWalletID, walletPubKeyHash);
393
+ }
394
+
395
+ if (self.activeWalletPubKeyHash == walletPubKeyHash) {
396
+ // If the move funds request refers to the current active wallet,
397
+ // unset the active wallet and make the wallet creation process
398
+ // possible in order to get a new healthy active wallet.
399
+ delete self.activeWalletPubKeyHash;
400
+ }
401
+
402
+ self.liveWalletsCount--;
403
+ }
404
+
405
+ /// @notice Begins the closing period of the given wallet.
406
+ /// @param walletPubKeyHash 20-byte public key hash of the wallet
407
+ /// @dev Requirements:
408
+ /// - The caller must make sure that the wallet is in the
409
+ /// MovingFunds state
410
+ function beginWalletClosing(
411
+ BridgeState.Storage storage self,
412
+ bytes20 walletPubKeyHash
413
+ ) internal {
414
+ Wallet storage wallet = self.registeredWallets[walletPubKeyHash];
415
+ // Initialize the closing period.
416
+ wallet.state = WalletState.Closing;
417
+ /* solhint-disable-next-line not-rely-on-time */
418
+ wallet.closingStartedAt = uint32(block.timestamp);
419
+
420
+ emit WalletClosing(wallet.ecdsaWalletID, walletPubKeyHash);
421
+ }
422
+
423
+ /// @notice Notifies about the end of the closing period for the given wallet.
424
+ /// Closes the wallet ultimately and notifies the ECDSA registry
425
+ /// about this fact.
426
+ /// @param walletPubKeyHash 20-byte public key hash of the wallet
427
+ /// @dev Requirements:
428
+ /// - The wallet must be in the Closing state
429
+ /// - The wallet closing period must have elapsed
430
+ function notifyWalletClosingPeriodElapsed(
431
+ BridgeState.Storage storage self,
432
+ bytes20 walletPubKeyHash
433
+ ) internal {
434
+ Wallet storage wallet = self.registeredWallets[walletPubKeyHash];
435
+
436
+ require(
437
+ wallet.state == WalletState.Closing,
438
+ "ECDSA wallet must be in Closing state"
439
+ );
440
+
441
+ require(
442
+ /* solhint-disable-next-line not-rely-on-time */
443
+ block.timestamp >
444
+ wallet.closingStartedAt + self.walletClosingPeriod,
445
+ "Closing period has not elapsed yet"
446
+ );
447
+
448
+ finalizeWalletClosing(self, walletPubKeyHash);
449
+ }
450
+
451
+ /// @notice Finalizes the closing period of the given wallet and notifies
452
+ /// the ECDSA registry about this fact.
453
+ /// @param walletPubKeyHash 20-byte public key hash of the wallet
454
+ /// @dev Requirements:
455
+ /// - The caller must make sure that the wallet is in the Closing state
456
+ function finalizeWalletClosing(
457
+ BridgeState.Storage storage self,
458
+ bytes20 walletPubKeyHash
459
+ ) internal {
460
+ Wallet storage wallet = self.registeredWallets[walletPubKeyHash];
461
+
462
+ wallet.state = WalletState.Closed;
463
+
464
+ emit WalletClosed(wallet.ecdsaWalletID, walletPubKeyHash);
465
+
466
+ self.ecdsaWalletRegistry.closeWallet(wallet.ecdsaWalletID);
467
+ }
468
+
469
+ /// @notice Terminates the given wallet and notifies the ECDSA registry
470
+ /// about this fact. If the wallet termination refers to the current
471
+ /// active wallet, such a wallet is no longer considered active and
472
+ /// the active wallet slot is unset allowing to trigger a new wallet
473
+ /// creation immediately.
474
+ /// @param walletPubKeyHash 20-byte public key hash of the wallet
475
+ /// @dev Requirements:
476
+ /// - The caller must make sure that the wallet is in the
477
+ /// Live or MovingFunds or Closing state.
478
+ function terminateWallet(
479
+ BridgeState.Storage storage self,
480
+ bytes20 walletPubKeyHash
481
+ ) internal {
482
+ Wallet storage wallet = self.registeredWallets[walletPubKeyHash];
483
+
484
+ if (wallet.state == WalletState.Live) {
485
+ self.liveWalletsCount--;
486
+ }
487
+
488
+ wallet.state = WalletState.Terminated;
489
+
490
+ emit WalletTerminated(wallet.ecdsaWalletID, walletPubKeyHash);
491
+
492
+ if (self.activeWalletPubKeyHash == walletPubKeyHash) {
493
+ // If termination refers to the current active wallet,
494
+ // unset the active wallet and make the wallet creation process
495
+ // possible in order to get a new healthy active wallet.
496
+ delete self.activeWalletPubKeyHash;
497
+ }
498
+
499
+ self.ecdsaWalletRegistry.closeWallet(wallet.ecdsaWalletID);
500
+ }
501
+
502
+ /// @notice Notifies that the wallet completed the moving funds process
503
+ /// successfully. Checks if the funds were moved to the expected
504
+ /// target wallets. Closes the source wallet if everything went
505
+ /// good and reverts otherwise.
506
+ /// @param walletPubKeyHash 20-byte public key hash of the wallet
507
+ /// @param targetWalletsHash 32-byte keccak256 hash over the list of
508
+ /// 20-byte public key hashes of the target wallets actually used
509
+ /// within the moving funds transactions.
510
+ /// @dev Requirements:
511
+ /// - The caller must make sure the moving funds transaction actually
512
+ /// happened on Bitcoin chain and fits the protocol requirements.
513
+ /// - The source wallet must be in the MovingFunds state
514
+ /// - The target wallets commitment must be submitted by the source
515
+ /// wallet.
516
+ /// - The actual target wallets used in the moving funds transaction
517
+ /// must be exactly the same as the target wallets commitment.
518
+ function notifyWalletFundsMoved(
519
+ BridgeState.Storage storage self,
520
+ bytes20 walletPubKeyHash,
521
+ bytes32 targetWalletsHash
522
+ ) internal {
523
+ Wallet storage wallet = self.registeredWallets[walletPubKeyHash];
524
+ // Check that the wallet is in the MovingFunds state but don't check
525
+ // if the moving funds timeout is exceeded. That should give a
526
+ // possibility to move funds in case when timeout was hit but was
527
+ // not reported yet.
528
+ require(
529
+ wallet.state == WalletState.MovingFunds,
530
+ "ECDSA wallet must be in MovingFunds state"
531
+ );
532
+
533
+ bytes32 targetWalletsCommitmentHash = wallet
534
+ .movingFundsTargetWalletsCommitmentHash;
535
+
536
+ require(
537
+ targetWalletsCommitmentHash != bytes32(0),
538
+ "Target wallets commitment not submitted yet"
539
+ );
540
+
541
+ // Make sure that the target wallets where funds were moved to are
542
+ // exactly the same as the ones the source wallet committed to.
543
+ require(
544
+ targetWalletsCommitmentHash == targetWalletsHash,
545
+ "Target wallets don't correspond to the commitment"
546
+ );
547
+
548
+ // If funds were moved, the wallet has no longer a main UTXO.
549
+ delete wallet.mainUtxoHash;
550
+
551
+ beginWalletClosing(self, walletPubKeyHash);
552
+ }
553
+ }
@@ -0,0 +1 @@
1
+ directory approved for write access by hardhat-dependency-compiler
@@ -0,0 +1,3 @@
1
+ // SPDX-License-Identifier: UNLICENSED
2
+ pragma solidity >0.0.0;
3
+ import '@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol';
@@ -0,0 +1,3 @@
1
+ // SPDX-License-Identifier: UNLICENSED
2
+ pragma solidity >0.0.0;
3
+ import '@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol';
@@ -1,6 +1,6 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
 
3
- pragma solidity 0.8.4;
3
+ pragma solidity ^0.8.9;
4
4
 
5
5
  import "@thesis/solidity-contracts/contracts/token/ERC20WithPermit.sol";
6
6
  import "@thesis/solidity-contracts/contracts/token/MisfundRecovery.sol";