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