@keep-network/tbtc-v2 0.1.1-dev.6 → 0.1.1-dev.60

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