@keep-network/tbtc-v2 0.1.1-dev.9 → 0.1.1-dev.90
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/README.adoc +12 -0
- package/artifacts/Bank.json +817 -0
- package/artifacts/Bridge.json +2666 -0
- package/artifacts/Deposit.json +117 -0
- package/artifacts/DepositSweep.json +77 -0
- package/artifacts/EcdsaDkgValidator.json +532 -0
- package/artifacts/EcdsaInactivity.json +156 -0
- package/artifacts/EcdsaSortitionPool.json +1004 -0
- package/artifacts/Fraud.json +164 -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 +249 -0
- package/artifacts/NuCypherStakingEscrow.json +256 -0
- package/artifacts/NuCypherToken.json +711 -0
- package/artifacts/RandomBeaconStub.json +141 -0
- package/artifacts/Redemption.json +174 -0
- package/artifacts/ReimbursementPool.json +509 -0
- package/artifacts/Relay.json +123 -0
- package/artifacts/T.json +1148 -0
- package/artifacts/TBTC.json +27 -26
- package/artifacts/TBTCToken.json +27 -26
- package/artifacts/TBTCVault.json +691 -0
- package/artifacts/TokenStaking.json +2288 -0
- package/artifacts/TokenholderGovernor.json +1795 -0
- package/artifacts/TokenholderTimelock.json +1058 -0
- package/artifacts/VendingMachine.json +30 -29
- package/artifacts/VendingMachineKeep.json +400 -0
- package/artifacts/VendingMachineNuCypher.json +400 -0
- package/artifacts/WalletRegistry.json +1843 -0
- package/artifacts/WalletRegistryGovernance.json +2754 -0
- package/artifacts/Wallets.json +186 -0
- package/artifacts/solcInputs/6e28b66ed60b6dd7b2b25b3a1c4fe89b.json +314 -0
- package/build/contracts/GovernanceUtils.sol/GovernanceUtils.dbg.json +1 -1
- package/build/contracts/GovernanceUtils.sol/GovernanceUtils.json +2 -2
- package/build/contracts/bank/Bank.sol/Bank.dbg.json +1 -1
- package/build/contracts/bank/Bank.sol/Bank.json +27 -4
- package/build/contracts/bank/IReceiveBalanceApproval.sol/IReceiveBalanceApproval.dbg.json +4 -0
- package/build/contracts/bank/IReceiveBalanceApproval.sol/IReceiveBalanceApproval.json +34 -0
- 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 +2547 -196
- package/build/contracts/bridge/BridgeState.sol/BridgeState.dbg.json +4 -0
- package/build/contracts/bridge/BridgeState.sol/BridgeState.json +226 -0
- package/build/contracts/bridge/Deposit.sol/Deposit.dbg.json +4 -0
- package/build/contracts/bridge/Deposit.sol/Deposit.json +72 -0
- package/build/contracts/bridge/DepositSweep.sol/DepositSweep.dbg.json +4 -0
- package/build/contracts/bridge/DepositSweep.sol/DepositSweep.json +30 -0
- package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.dbg.json +4 -0
- package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.json +10 -0
- package/build/contracts/bridge/Fraud.sol/Fraud.dbg.json +4 -0
- package/build/contracts/bridge/Fraud.sol/Fraud.json +86 -0
- package/build/contracts/bridge/Heartbeat.sol/Heartbeat.dbg.json +4 -0
- package/build/contracts/bridge/Heartbeat.sol/Heartbeat.json +10 -0
- package/build/contracts/bridge/IRelay.sol/IRelay.dbg.json +4 -0
- package/build/contracts/bridge/IRelay.sol/IRelay.json +37 -0
- package/build/contracts/bridge/MovingFunds.sol/MovingFunds.dbg.json +4 -0
- package/build/contracts/bridge/MovingFunds.sol/MovingFunds.json +138 -0
- package/build/contracts/bridge/Redemption.sol/OutboundTx.dbg.json +4 -0
- package/build/contracts/bridge/Redemption.sol/OutboundTx.json +10 -0
- 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/VendingMachine.sol/VendingMachine.dbg.json +1 -1
- package/build/contracts/bridge/VendingMachine.sol/VendingMachine.json +2 -2
- package/build/contracts/bridge/Wallets.sol/Wallets.dbg.json +4 -0
- package/build/contracts/bridge/Wallets.sol/Wallets.json +112 -0
- package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
- package/build/contracts/token/TBTC.sol/TBTC.json +2 -2
- package/build/contracts/vault/DonationVault.sol/DonationVault.dbg.json +4 -0
- package/build/contracts/vault/DonationVault.sol/DonationVault.json +108 -0
- package/build/contracts/vault/IVault.sol/IVault.dbg.json +1 -1
- package/build/contracts/vault/IVault.sol/IVault.json +24 -1
- package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +1 -1
- package/build/contracts/vault/TBTCVault.sol/TBTCVault.json +295 -9
- package/contracts/GovernanceUtils.sol +4 -4
- package/contracts/bank/Bank.sol +119 -57
- package/contracts/bank/IReceiveBalanceApproval.sol +45 -0
- package/contracts/bridge/BitcoinTx.sol +232 -10
- package/contracts/bridge/Bridge.sol +1692 -244
- package/contracts/bridge/BridgeState.sol +739 -0
- package/contracts/bridge/Deposit.sol +269 -0
- package/contracts/bridge/DepositSweep.sol +571 -0
- package/contracts/bridge/EcdsaLib.sol +45 -0
- package/contracts/bridge/Fraud.sol +604 -0
- package/contracts/bridge/Heartbeat.sol +112 -0
- package/contracts/bridge/IRelay.sol +28 -0
- package/contracts/bridge/MovingFunds.sol +1089 -0
- package/contracts/bridge/Redemption.sol +1021 -0
- package/contracts/bridge/VendingMachine.sol +1 -1
- package/contracts/bridge/Wallets.sol +553 -0
- package/contracts/hardhat-dependency-compiler/.hardhat-dependency-compiler +1 -0
- package/contracts/hardhat-dependency-compiler/@keep-network/ecdsa/contracts/WalletRegistry.sol +3 -0
- package/contracts/hardhat-dependency-compiler/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol +3 -0
- package/contracts/hardhat-dependency-compiler/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +3 -0
- package/contracts/token/TBTC.sol +1 -1
- package/contracts/vault/DonationVault.sol +125 -0
- package/contracts/vault/IVault.sol +19 -13
- package/contracts/vault/TBTCVault.sol +200 -23
- package/deploy/00_resolve_relay.ts +28 -0
- package/deploy/{03_transfer_roles.ts → 03_transfer_vending_machine_roles.ts} +1 -1
- package/deploy/04_deploy_bank.ts +27 -0
- package/deploy/05_deploy_bridge.ts +80 -0
- package/deploy/06_deploy_tbtc_vault.ts +30 -0
- package/deploy/07_bank_update_bridge.ts +19 -0
- package/deploy/08_transfer_bank_ownership.ts +15 -0
- package/deploy/09_transfer_tbtc_vault_ownership.ts +15 -0
- package/deploy/10_transfer_bridge_governance.ts +20 -0
- package/deploy/11_initialize_wallet_owner.ts +18 -0
- package/deploy/11_transfer_proxy_admin_ownership.ts +30 -0
- package/deploy/12_deploy_proxy_admin_with_deputy.ts +33 -0
- package/export.json +16150 -443
- package/package.json +32 -25
- package/artifacts/solcInputs/58d5b3ee7688835879381470de985d6b.json +0 -128
|
@@ -0,0 +1,1089 @@
|
|
|
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 {BytesLib} from "@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol";
|
|
20
|
+
|
|
21
|
+
import "./BitcoinTx.sol";
|
|
22
|
+
import "./BridgeState.sol";
|
|
23
|
+
import "./Redemption.sol";
|
|
24
|
+
import "./Wallets.sol";
|
|
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.
|
|
34
|
+
/// Once the proof is submitted, all target wallets are supposed to
|
|
35
|
+
/// sweep the received UTXOs with their own main UTXOs in order to
|
|
36
|
+
/// update their BTC balances.
|
|
37
|
+
library MovingFunds {
|
|
38
|
+
using BridgeState for BridgeState.Storage;
|
|
39
|
+
using Wallets for BridgeState.Storage;
|
|
40
|
+
using BitcoinTx for BridgeState.Storage;
|
|
41
|
+
|
|
42
|
+
using BTCUtils for bytes;
|
|
43
|
+
using BytesLib for bytes;
|
|
44
|
+
|
|
45
|
+
/// @notice Represents temporary information needed during the processing
|
|
46
|
+
/// of the moving funds Bitcoin transaction outputs. This structure
|
|
47
|
+
/// is an internal one and should not be exported outside of the
|
|
48
|
+
/// moving funds transaction processing code.
|
|
49
|
+
/// @dev Allows to mitigate "stack too deep" errors on EVM.
|
|
50
|
+
struct MovingFundsTxOutputsProcessingInfo {
|
|
51
|
+
// 32-byte hash of the moving funds Bitcoin transaction.
|
|
52
|
+
bytes32 movingFundsTxHash;
|
|
53
|
+
// Output vector of the moving funds Bitcoin transaction. It is
|
|
54
|
+
// assumed the vector's structure is valid so it must be validated
|
|
55
|
+
// using e.g. `BTCUtils.validateVout` function before being used
|
|
56
|
+
// during the processing. The validation is usually done as part
|
|
57
|
+
// of the `BitcoinTx.validateProof` call that checks the SPV proof.
|
|
58
|
+
bytes movingFundsTxOutputVector;
|
|
59
|
+
// This struct doesn't contain `__gap` property as the structure is not
|
|
60
|
+
// stored, it is used as a function's memory argument.
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// @notice Represents moved funds sweep request state.
|
|
64
|
+
enum MovedFundsSweepRequestState {
|
|
65
|
+
/// @dev The request is unknown to the Bridge.
|
|
66
|
+
Unknown,
|
|
67
|
+
/// @dev Request is pending and can become either processed or timed out.
|
|
68
|
+
Pending,
|
|
69
|
+
/// @dev Request was processed by the target wallet.
|
|
70
|
+
Processed,
|
|
71
|
+
/// @dev Request was not processed in the given time window and
|
|
72
|
+
/// the timeout was reported.
|
|
73
|
+
TimedOut
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// @notice Represents a moved funds sweep request. The request is
|
|
77
|
+
/// registered in `submitMovingFundsProof` where we know funds
|
|
78
|
+
/// have been moved to the target wallet and the only step left is
|
|
79
|
+
/// to have the target wallet sweep them.
|
|
80
|
+
struct MovedFundsSweepRequest {
|
|
81
|
+
// 20-byte public key hash of the wallet supposed to sweep the UTXO
|
|
82
|
+
// representing the received funds with their own main UTXO
|
|
83
|
+
bytes20 walletPubKeyHash;
|
|
84
|
+
// Value of the received funds.
|
|
85
|
+
uint64 value;
|
|
86
|
+
// UNIX timestamp the request was created at.
|
|
87
|
+
uint32 createdAt;
|
|
88
|
+
// The current state of the request.
|
|
89
|
+
MovedFundsSweepRequestState state;
|
|
90
|
+
// This struct doesn't contain `__gap` property as the structure is stored
|
|
91
|
+
// in a mapping, mappings store values in different slots and they are
|
|
92
|
+
// not contiguous with other values.
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
event MovingFundsCommitmentSubmitted(
|
|
96
|
+
bytes20 indexed walletPubKeyHash,
|
|
97
|
+
bytes20[] targetWallets,
|
|
98
|
+
address submitter
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
event MovingFundsTimeoutReset(bytes20 indexed walletPubKeyHash);
|
|
102
|
+
|
|
103
|
+
event MovingFundsCompleted(
|
|
104
|
+
bytes20 indexed walletPubKeyHash,
|
|
105
|
+
bytes32 movingFundsTxHash
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
event MovingFundsTimedOut(bytes20 indexed walletPubKeyHash);
|
|
109
|
+
|
|
110
|
+
event MovingFundsBelowDustReported(bytes20 indexed walletPubKeyHash);
|
|
111
|
+
|
|
112
|
+
event MovedFundsSwept(
|
|
113
|
+
bytes20 indexed walletPubKeyHash,
|
|
114
|
+
bytes32 sweepTxHash
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
event MovedFundsSweepTimedOut(
|
|
118
|
+
bytes20 indexed walletPubKeyHash,
|
|
119
|
+
bytes32 movingFundsTxHash,
|
|
120
|
+
uint32 movingFundsTxOutputIndex
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
/// @notice Submits the moving funds target wallets commitment.
|
|
124
|
+
/// Once all requirements are met, that function registers the
|
|
125
|
+
/// target wallets commitment and opens the way for moving funds
|
|
126
|
+
/// proof submission.
|
|
127
|
+
/// @param walletPubKeyHash 20-byte public key hash of the source wallet.
|
|
128
|
+
/// @param walletMainUtxo Data of the source wallet's main UTXO, as
|
|
129
|
+
/// currently known on the Ethereum chain.
|
|
130
|
+
/// @param walletMembersIDs Identifiers of the source wallet signing group
|
|
131
|
+
/// members.
|
|
132
|
+
/// @param walletMemberIndex Position of the caller in the source wallet
|
|
133
|
+
/// signing group members list.
|
|
134
|
+
/// @param targetWallets List of 20-byte public key hashes of the target
|
|
135
|
+
/// wallets that the source wallet commits to move the funds to.
|
|
136
|
+
/// @dev Requirements:
|
|
137
|
+
/// - The source wallet must be in the MovingFunds state,
|
|
138
|
+
/// - The source wallet must not have pending redemption requests,
|
|
139
|
+
/// - The source wallet must not have pending moved funds sweep requests,
|
|
140
|
+
/// - The source wallet must not have submitted its commitment already,
|
|
141
|
+
/// - The expression `keccak256(abi.encode(walletMembersIDs))` must
|
|
142
|
+
/// be exactly the same as the hash stored under `membersIdsHash`
|
|
143
|
+
/// for the given source wallet in the ECDSA registry. Those IDs are
|
|
144
|
+
/// not directly stored in the contract for gas efficiency purposes
|
|
145
|
+
/// but they can be read from appropriate `DkgResultSubmitted`
|
|
146
|
+
/// and `DkgResultApproved` events,
|
|
147
|
+
/// - The `walletMemberIndex` must be in range [1, walletMembersIDs.length],
|
|
148
|
+
/// - The caller must be the member of the source wallet signing group
|
|
149
|
+
/// at the position indicated by `walletMemberIndex` parameter,
|
|
150
|
+
/// - The `walletMainUtxo` components must point to the recent main
|
|
151
|
+
/// UTXO of the source wallet, as currently known on the Ethereum
|
|
152
|
+
/// chain,
|
|
153
|
+
/// - Source wallet BTC balance must be greater than zero,
|
|
154
|
+
/// - At least one Live wallet must exist in the system,
|
|
155
|
+
/// - Submitted target wallets count must match the expected count
|
|
156
|
+
/// `N = min(liveWalletsCount, ceil(walletBtcBalance / walletMaxBtcTransfer))`
|
|
157
|
+
/// where `N > 0`,
|
|
158
|
+
/// - Each target wallet must be not equal to the source wallet,
|
|
159
|
+
/// - Each target wallet must follow the expected order i.e. all
|
|
160
|
+
/// target wallets 20-byte public key hashes represented as numbers
|
|
161
|
+
/// must form a strictly increasing sequence without duplicates,
|
|
162
|
+
/// - Each target wallet must be in Live state.
|
|
163
|
+
function submitMovingFundsCommitment(
|
|
164
|
+
BridgeState.Storage storage self,
|
|
165
|
+
bytes20 walletPubKeyHash,
|
|
166
|
+
BitcoinTx.UTXO calldata walletMainUtxo,
|
|
167
|
+
uint32[] calldata walletMembersIDs,
|
|
168
|
+
uint256 walletMemberIndex,
|
|
169
|
+
bytes20[] calldata targetWallets
|
|
170
|
+
) external {
|
|
171
|
+
Wallets.Wallet storage wallet = self.registeredWallets[
|
|
172
|
+
walletPubKeyHash
|
|
173
|
+
];
|
|
174
|
+
|
|
175
|
+
require(
|
|
176
|
+
wallet.state == Wallets.WalletState.MovingFunds,
|
|
177
|
+
"Source wallet must be in MovingFunds state"
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
require(
|
|
181
|
+
wallet.pendingRedemptionsValue == 0,
|
|
182
|
+
"Source wallet must handle all pending redemptions first"
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
require(
|
|
186
|
+
wallet.pendingMovedFundsSweepRequestsCount == 0,
|
|
187
|
+
"Source wallet must handle all pending moved funds sweep requests first"
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
require(
|
|
191
|
+
wallet.movingFundsTargetWalletsCommitmentHash == bytes32(0),
|
|
192
|
+
"Target wallets commitment already submitted"
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
require(
|
|
196
|
+
self.ecdsaWalletRegistry.isWalletMember(
|
|
197
|
+
wallet.ecdsaWalletID,
|
|
198
|
+
walletMembersIDs,
|
|
199
|
+
msg.sender,
|
|
200
|
+
walletMemberIndex
|
|
201
|
+
),
|
|
202
|
+
"Caller is not a member of the source wallet"
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
uint64 walletBtcBalance = self.getWalletBtcBalance(
|
|
206
|
+
walletPubKeyHash,
|
|
207
|
+
walletMainUtxo
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
require(walletBtcBalance > 0, "Wallet BTC balance is zero");
|
|
211
|
+
|
|
212
|
+
uint256 expectedTargetWalletsCount = Math.min(
|
|
213
|
+
self.liveWalletsCount,
|
|
214
|
+
Math.ceilDiv(walletBtcBalance, self.walletMaxBtcTransfer)
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
// This requirement fails only when `liveWalletsCount` is zero. In
|
|
218
|
+
// that case, the system cannot accept the commitment and must provide
|
|
219
|
+
// new wallets first. However, the wallet supposed to submit the
|
|
220
|
+
// commitment can keep resetting the moving funds timeout until then.
|
|
221
|
+
require(expectedTargetWalletsCount > 0, "No target wallets available");
|
|
222
|
+
|
|
223
|
+
require(
|
|
224
|
+
targetWallets.length == expectedTargetWalletsCount,
|
|
225
|
+
"Submitted target wallets count is other than expected"
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
uint160 lastProcessedTargetWallet = 0;
|
|
229
|
+
|
|
230
|
+
for (uint256 i = 0; i < targetWallets.length; i++) {
|
|
231
|
+
bytes20 targetWallet = targetWallets[i];
|
|
232
|
+
|
|
233
|
+
require(
|
|
234
|
+
targetWallet != walletPubKeyHash,
|
|
235
|
+
"Submitted target wallet cannot be equal to the source wallet"
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
require(
|
|
239
|
+
uint160(targetWallet) > lastProcessedTargetWallet,
|
|
240
|
+
"Submitted target wallet breaks the expected order"
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
require(
|
|
244
|
+
self.registeredWallets[targetWallet].state ==
|
|
245
|
+
Wallets.WalletState.Live,
|
|
246
|
+
"Submitted target wallet must be in Live state"
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
lastProcessedTargetWallet = uint160(targetWallet);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
wallet.movingFundsTargetWalletsCommitmentHash = keccak256(
|
|
253
|
+
abi.encodePacked(targetWallets)
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
emit MovingFundsCommitmentSubmitted(
|
|
257
|
+
walletPubKeyHash,
|
|
258
|
+
targetWallets,
|
|
259
|
+
msg.sender
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/// @notice Resets the moving funds timeout for the given wallet if the
|
|
264
|
+
/// target wallet commitment cannot be submitted due to a lack
|
|
265
|
+
/// of live wallets in the system.
|
|
266
|
+
/// @param walletPubKeyHash 20-byte public key hash of the moving funds wallet
|
|
267
|
+
/// @dev Requirements:
|
|
268
|
+
/// - The wallet must be in the MovingFunds state,
|
|
269
|
+
/// - The target wallets commitment must not be already submitted for
|
|
270
|
+
/// the given moving funds wallet,
|
|
271
|
+
/// - Live wallets count must be zero,
|
|
272
|
+
/// - The moving funds timeout reset delay must be elapsed.
|
|
273
|
+
function resetMovingFundsTimeout(
|
|
274
|
+
BridgeState.Storage storage self,
|
|
275
|
+
bytes20 walletPubKeyHash
|
|
276
|
+
) external {
|
|
277
|
+
Wallets.Wallet storage wallet = self.registeredWallets[
|
|
278
|
+
walletPubKeyHash
|
|
279
|
+
];
|
|
280
|
+
|
|
281
|
+
require(
|
|
282
|
+
wallet.state == Wallets.WalletState.MovingFunds,
|
|
283
|
+
"ECDSA wallet must be in MovingFunds state"
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
// If the moving funds wallet already submitted their target wallets
|
|
287
|
+
// commitment, there is no point to reset the timeout since the
|
|
288
|
+
// wallet can make the BTC transaction and submit the proof.
|
|
289
|
+
require(
|
|
290
|
+
wallet.movingFundsTargetWalletsCommitmentHash == bytes32(0),
|
|
291
|
+
"Target wallets commitment already submitted"
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
require(self.liveWalletsCount == 0, "Live wallets count must be zero");
|
|
295
|
+
|
|
296
|
+
require(
|
|
297
|
+
/* solhint-disable-next-line not-rely-on-time */
|
|
298
|
+
block.timestamp >
|
|
299
|
+
wallet.movingFundsRequestedAt +
|
|
300
|
+
self.movingFundsTimeoutResetDelay,
|
|
301
|
+
"Moving funds timeout cannot be reset yet"
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
/* solhint-disable-next-line not-rely-on-time */
|
|
305
|
+
wallet.movingFundsRequestedAt = uint32(block.timestamp);
|
|
306
|
+
|
|
307
|
+
emit MovingFundsTimeoutReset(walletPubKeyHash);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/// @notice Used by the wallet to prove the BTC moving funds transaction
|
|
311
|
+
/// and to make the necessary state changes. Moving funds is only
|
|
312
|
+
/// accepted if it satisfies SPV proof.
|
|
313
|
+
///
|
|
314
|
+
/// The function validates the moving funds transaction structure
|
|
315
|
+
/// by checking if it actually spends the main UTXO of the declared
|
|
316
|
+
/// wallet and locks the value on the pre-committed target wallets
|
|
317
|
+
/// using a reasonable transaction fee. If all preconditions are
|
|
318
|
+
/// met, this functions closes the source wallet.
|
|
319
|
+
///
|
|
320
|
+
/// It is possible to prove the given moving funds transaction only
|
|
321
|
+
/// one time.
|
|
322
|
+
/// @param movingFundsTx Bitcoin moving funds transaction data.
|
|
323
|
+
/// @param movingFundsProof Bitcoin moving funds proof data.
|
|
324
|
+
/// @param mainUtxo Data of the wallet's main UTXO, as currently known on
|
|
325
|
+
/// the Ethereum chain.
|
|
326
|
+
/// @param walletPubKeyHash 20-byte public key hash (computed using Bitcoin
|
|
327
|
+
/// HASH160 over the compressed ECDSA public key) of the wallet
|
|
328
|
+
/// which performed the moving funds transaction.
|
|
329
|
+
/// @dev Requirements:
|
|
330
|
+
/// - `movingFundsTx` components must match the expected structure. See
|
|
331
|
+
/// `BitcoinTx.Info` docs for reference. Their values must exactly
|
|
332
|
+
/// correspond to appropriate Bitcoin transaction fields to produce
|
|
333
|
+
/// a provable transaction hash,
|
|
334
|
+
/// - The `movingFundsTx` should represent a Bitcoin transaction with
|
|
335
|
+
/// exactly 1 input that refers to the wallet's main UTXO. That
|
|
336
|
+
/// transaction should have 1..n outputs corresponding to the
|
|
337
|
+
/// pre-committed target wallets. Outputs must be ordered in the
|
|
338
|
+
/// same way as their corresponding target wallets are ordered
|
|
339
|
+
/// within the target wallets commitment,
|
|
340
|
+
/// - `movingFundsProof` components must match the expected structure.
|
|
341
|
+
/// See `BitcoinTx.Proof` docs for reference. The `bitcoinHeaders`
|
|
342
|
+
/// field must contain a valid number of block headers, not less
|
|
343
|
+
/// than the `txProofDifficultyFactor` contract constant,
|
|
344
|
+
/// - `mainUtxo` components must point to the recent main UTXO
|
|
345
|
+
/// of the given wallet, as currently known on the Ethereum chain.
|
|
346
|
+
/// Additionally, the recent main UTXO on Ethereum must be set,
|
|
347
|
+
/// - `walletPubKeyHash` must be connected with the main UTXO used
|
|
348
|
+
/// as transaction single input,
|
|
349
|
+
/// - The wallet that `walletPubKeyHash` points to must be in the
|
|
350
|
+
/// MovingFunds state,
|
|
351
|
+
/// - The target wallets commitment must be submitted by the wallet
|
|
352
|
+
/// that `walletPubKeyHash` points to,
|
|
353
|
+
/// - The total Bitcoin transaction fee must be lesser or equal
|
|
354
|
+
/// to `movingFundsTxMaxTotalFee` governable parameter.
|
|
355
|
+
function submitMovingFundsProof(
|
|
356
|
+
BridgeState.Storage storage self,
|
|
357
|
+
BitcoinTx.Info calldata movingFundsTx,
|
|
358
|
+
BitcoinTx.Proof calldata movingFundsProof,
|
|
359
|
+
BitcoinTx.UTXO calldata mainUtxo,
|
|
360
|
+
bytes20 walletPubKeyHash
|
|
361
|
+
) external {
|
|
362
|
+
// The actual transaction proof is performed here. After that point, we
|
|
363
|
+
// can assume the transaction happened on Bitcoin chain and has
|
|
364
|
+
// a sufficient number of confirmations as determined by
|
|
365
|
+
// `txProofDifficultyFactor` constant.
|
|
366
|
+
bytes32 movingFundsTxHash = self.validateProof(
|
|
367
|
+
movingFundsTx,
|
|
368
|
+
movingFundsProof
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
// Process the moving funds transaction input. Specifically, check if
|
|
372
|
+
// it refers to the expected wallet's main UTXO.
|
|
373
|
+
OutboundTx.processWalletOutboundTxInput(
|
|
374
|
+
self,
|
|
375
|
+
movingFundsTx.inputVector,
|
|
376
|
+
mainUtxo,
|
|
377
|
+
walletPubKeyHash
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
(
|
|
381
|
+
bytes32 targetWalletsHash,
|
|
382
|
+
uint256 outputsTotalValue
|
|
383
|
+
) = processMovingFundsTxOutputs(
|
|
384
|
+
self,
|
|
385
|
+
MovingFundsTxOutputsProcessingInfo(
|
|
386
|
+
movingFundsTxHash,
|
|
387
|
+
movingFundsTx.outputVector
|
|
388
|
+
)
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
require(
|
|
392
|
+
mainUtxo.txOutputValue - outputsTotalValue <=
|
|
393
|
+
self.movingFundsTxMaxTotalFee,
|
|
394
|
+
"Transaction fee is too high"
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
self.notifyWalletFundsMoved(walletPubKeyHash, targetWalletsHash);
|
|
398
|
+
// slither-disable-next-line reentrancy-events
|
|
399
|
+
emit MovingFundsCompleted(walletPubKeyHash, movingFundsTxHash);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/// @notice Processes the moving funds Bitcoin transaction output vector
|
|
403
|
+
/// and extracts information required for further processing.
|
|
404
|
+
/// @param processInfo Processing info containing the moving funds tx
|
|
405
|
+
/// hash and output vector.
|
|
406
|
+
/// @return targetWalletsHash keccak256 hash over the list of actual
|
|
407
|
+
/// target wallets used in the transaction.
|
|
408
|
+
/// @return outputsTotalValue Sum of all outputs values.
|
|
409
|
+
/// @dev Requirements:
|
|
410
|
+
/// - The `movingFundsTxOutputVector` must be parseable, i.e. must
|
|
411
|
+
/// be validated by the caller as stated in their parameter doc,
|
|
412
|
+
/// - Each output must refer to a 20-byte public key hash,
|
|
413
|
+
/// - The total outputs value must be evenly divided over all outputs.
|
|
414
|
+
function processMovingFundsTxOutputs(
|
|
415
|
+
BridgeState.Storage storage self,
|
|
416
|
+
MovingFundsTxOutputsProcessingInfo memory processInfo
|
|
417
|
+
) internal returns (bytes32 targetWalletsHash, uint256 outputsTotalValue) {
|
|
418
|
+
// Determining the total number of Bitcoin transaction outputs in
|
|
419
|
+
// the same way as for number of inputs. See `BitcoinTx.outputVector`
|
|
420
|
+
// docs for more details.
|
|
421
|
+
(
|
|
422
|
+
uint256 outputsCompactSizeUintLength,
|
|
423
|
+
uint256 outputsCount
|
|
424
|
+
) = processInfo.movingFundsTxOutputVector.parseVarInt();
|
|
425
|
+
|
|
426
|
+
// To determine the first output starting index, we must jump over
|
|
427
|
+
// the compactSize uint which prepends the output vector. One byte
|
|
428
|
+
// must be added because `BtcUtils.parseVarInt` does not include
|
|
429
|
+
// compactSize uint tag in the returned length.
|
|
430
|
+
//
|
|
431
|
+
// For >= 0 && <= 252, `BTCUtils.determineVarIntDataLengthAt`
|
|
432
|
+
// returns `0`, so we jump over one byte of compactSize uint.
|
|
433
|
+
//
|
|
434
|
+
// For >= 253 && <= 0xffff there is `0xfd` tag,
|
|
435
|
+
// `BTCUtils.determineVarIntDataLengthAt` returns `2` (no
|
|
436
|
+
// tag byte included) so we need to jump over 1+2 bytes of
|
|
437
|
+
// compactSize uint.
|
|
438
|
+
//
|
|
439
|
+
// Please refer `BTCUtils` library and compactSize uint
|
|
440
|
+
// docs in `BitcoinTx` library for more details.
|
|
441
|
+
uint256 outputStartingIndex = 1 + outputsCompactSizeUintLength;
|
|
442
|
+
|
|
443
|
+
bytes20[] memory targetWallets = new bytes20[](outputsCount);
|
|
444
|
+
uint64[] memory outputsValues = new uint64[](outputsCount);
|
|
445
|
+
|
|
446
|
+
// Outputs processing loop. Note that the `outputIndex` must be
|
|
447
|
+
// `uint32` to build proper `movedFundsSweepRequests` keys.
|
|
448
|
+
for (
|
|
449
|
+
uint32 outputIndex = 0;
|
|
450
|
+
outputIndex < outputsCount;
|
|
451
|
+
outputIndex++
|
|
452
|
+
) {
|
|
453
|
+
uint256 outputLength = processInfo
|
|
454
|
+
.movingFundsTxOutputVector
|
|
455
|
+
.determineOutputLengthAt(outputStartingIndex);
|
|
456
|
+
|
|
457
|
+
bytes memory output = processInfo.movingFundsTxOutputVector.slice(
|
|
458
|
+
outputStartingIndex,
|
|
459
|
+
outputLength
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
bytes20 targetWalletPubKeyHash = self.extractPubKeyHash(output);
|
|
463
|
+
|
|
464
|
+
// Add the wallet public key hash to the list that will be used
|
|
465
|
+
// to build the result list hash. There is no need to check if
|
|
466
|
+
// given output is a change here because the actual target wallet
|
|
467
|
+
// list must be exactly the same as the pre-committed target wallet
|
|
468
|
+
// list which is guaranteed to be valid.
|
|
469
|
+
targetWallets[outputIndex] = targetWalletPubKeyHash;
|
|
470
|
+
|
|
471
|
+
// Extract the value from given output.
|
|
472
|
+
outputsValues[outputIndex] = output.extractValue();
|
|
473
|
+
outputsTotalValue += outputsValues[outputIndex];
|
|
474
|
+
|
|
475
|
+
// Register a moved funds sweep request that must be handled
|
|
476
|
+
// by the target wallet. The target wallet must sweep the
|
|
477
|
+
// received funds with their own main UTXO in order to update
|
|
478
|
+
// their BTC balance. Worth noting there is no need to check
|
|
479
|
+
// if the sweep request already exists in the system because
|
|
480
|
+
// the moving funds wallet is moved to the Closing state after
|
|
481
|
+
// submitting the moving funds proof so there is no possibility
|
|
482
|
+
// to submit the proof again and register the sweep request twice.
|
|
483
|
+
self.movedFundsSweepRequests[
|
|
484
|
+
uint256(
|
|
485
|
+
keccak256(
|
|
486
|
+
abi.encodePacked(
|
|
487
|
+
processInfo.movingFundsTxHash,
|
|
488
|
+
outputIndex
|
|
489
|
+
)
|
|
490
|
+
)
|
|
491
|
+
)
|
|
492
|
+
] = MovedFundsSweepRequest(
|
|
493
|
+
targetWalletPubKeyHash,
|
|
494
|
+
outputsValues[outputIndex],
|
|
495
|
+
/* solhint-disable-next-line not-rely-on-time */
|
|
496
|
+
uint32(block.timestamp),
|
|
497
|
+
MovedFundsSweepRequestState.Pending
|
|
498
|
+
);
|
|
499
|
+
// We added a new moved funds sweep request for the target wallet
|
|
500
|
+
// so we must increment their request counter.
|
|
501
|
+
self
|
|
502
|
+
.registeredWallets[targetWalletPubKeyHash]
|
|
503
|
+
.pendingMovedFundsSweepRequestsCount++;
|
|
504
|
+
|
|
505
|
+
// Make the `outputStartingIndex` pointing to the next output by
|
|
506
|
+
// increasing it by current output's length.
|
|
507
|
+
outputStartingIndex += outputLength;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Compute the indivisible remainder that remains after dividing the
|
|
511
|
+
// outputs total value over all outputs evenly.
|
|
512
|
+
uint256 outputsTotalValueRemainder = outputsTotalValue % outputsCount;
|
|
513
|
+
// Compute the minimum allowed output value by dividing the outputs
|
|
514
|
+
// total value (reduced by the remainder) by the number of outputs.
|
|
515
|
+
uint256 minOutputValue = (outputsTotalValue -
|
|
516
|
+
outputsTotalValueRemainder) / outputsCount;
|
|
517
|
+
// Maximum possible value is the minimum value with the remainder included.
|
|
518
|
+
uint256 maxOutputValue = minOutputValue + outputsTotalValueRemainder;
|
|
519
|
+
|
|
520
|
+
for (uint256 i = 0; i < outputsCount; i++) {
|
|
521
|
+
require(
|
|
522
|
+
minOutputValue <= outputsValues[i] &&
|
|
523
|
+
outputsValues[i] <= maxOutputValue,
|
|
524
|
+
"Transaction amount is not distributed evenly"
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
targetWalletsHash = keccak256(abi.encodePacked(targetWallets));
|
|
529
|
+
|
|
530
|
+
return (targetWalletsHash, outputsTotalValue);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/// @notice Notifies about a timed out moving funds process. Terminates
|
|
534
|
+
/// the wallet and slashes signing group members as a result.
|
|
535
|
+
/// @param walletPubKeyHash 20-byte public key hash of the wallet.
|
|
536
|
+
/// @param walletMembersIDs Identifiers of the wallet signing group members.
|
|
537
|
+
/// @dev Requirements:
|
|
538
|
+
/// - The wallet must be in the MovingFunds state,
|
|
539
|
+
/// - The moving funds timeout must be actually exceeded,
|
|
540
|
+
/// - The expression `keccak256(abi.encode(walletMembersIDs))` must
|
|
541
|
+
/// be exactly the same as the hash stored under `membersIdsHash`
|
|
542
|
+
/// for the given `walletID`. Those IDs are not directly stored
|
|
543
|
+
/// in the contract for gas efficiency purposes but they can be
|
|
544
|
+
/// read from appropriate `DkgResultSubmitted` and `DkgResultApproved`
|
|
545
|
+
/// events of the `WalletRegistry` contract.
|
|
546
|
+
function notifyMovingFundsTimeout(
|
|
547
|
+
BridgeState.Storage storage self,
|
|
548
|
+
bytes20 walletPubKeyHash,
|
|
549
|
+
uint32[] calldata walletMembersIDs
|
|
550
|
+
) external {
|
|
551
|
+
Wallets.Wallet storage wallet = self.registeredWallets[
|
|
552
|
+
walletPubKeyHash
|
|
553
|
+
];
|
|
554
|
+
|
|
555
|
+
require(
|
|
556
|
+
wallet.state == Wallets.WalletState.MovingFunds,
|
|
557
|
+
"ECDSA wallet must be in MovingFunds state"
|
|
558
|
+
);
|
|
559
|
+
|
|
560
|
+
require(
|
|
561
|
+
/* solhint-disable-next-line not-rely-on-time */
|
|
562
|
+
block.timestamp >
|
|
563
|
+
wallet.movingFundsRequestedAt + self.movingFundsTimeout,
|
|
564
|
+
"Moving funds has not timed out yet"
|
|
565
|
+
);
|
|
566
|
+
|
|
567
|
+
self.terminateWallet(walletPubKeyHash);
|
|
568
|
+
|
|
569
|
+
self.ecdsaWalletRegistry.seize(
|
|
570
|
+
self.movingFundsTimeoutSlashingAmount,
|
|
571
|
+
self.movingFundsTimeoutNotifierRewardMultiplier,
|
|
572
|
+
msg.sender,
|
|
573
|
+
wallet.ecdsaWalletID,
|
|
574
|
+
walletMembersIDs
|
|
575
|
+
);
|
|
576
|
+
|
|
577
|
+
// slither-disable-next-line reentrancy-events
|
|
578
|
+
emit MovingFundsTimedOut(walletPubKeyHash);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/// @notice Notifies about a moving funds wallet whose BTC balance is
|
|
582
|
+
/// below the moving funds dust threshold. Ends the moving funds
|
|
583
|
+
/// process and begins wallet closing immediately.
|
|
584
|
+
/// @param walletPubKeyHash 20-byte public key hash of the wallet.
|
|
585
|
+
/// @param mainUtxo Data of the wallet's main UTXO, as currently known
|
|
586
|
+
/// on the Ethereum chain.
|
|
587
|
+
/// @dev Requirements:
|
|
588
|
+
/// - The wallet must be in the MovingFunds state,
|
|
589
|
+
/// - The `mainUtxo` components must point to the recent main UTXO
|
|
590
|
+
/// of the given wallet, as currently known on the Ethereum chain.
|
|
591
|
+
/// If the wallet has no main UTXO, this parameter can be empty as it
|
|
592
|
+
/// is ignored,
|
|
593
|
+
/// - The wallet BTC balance must be below the moving funds threshold.
|
|
594
|
+
function notifyMovingFundsBelowDust(
|
|
595
|
+
BridgeState.Storage storage self,
|
|
596
|
+
bytes20 walletPubKeyHash,
|
|
597
|
+
BitcoinTx.UTXO calldata mainUtxo
|
|
598
|
+
) external {
|
|
599
|
+
Wallets.Wallet storage wallet = self.registeredWallets[
|
|
600
|
+
walletPubKeyHash
|
|
601
|
+
];
|
|
602
|
+
|
|
603
|
+
require(
|
|
604
|
+
wallet.state == Wallets.WalletState.MovingFunds,
|
|
605
|
+
"ECDSA wallet must be in MovingFunds state"
|
|
606
|
+
);
|
|
607
|
+
|
|
608
|
+
uint64 walletBtcBalance = self.getWalletBtcBalance(
|
|
609
|
+
walletPubKeyHash,
|
|
610
|
+
mainUtxo
|
|
611
|
+
);
|
|
612
|
+
|
|
613
|
+
require(
|
|
614
|
+
walletBtcBalance < self.movingFundsDustThreshold,
|
|
615
|
+
"Wallet BTC balance must be below the moving funds dust threshold"
|
|
616
|
+
);
|
|
617
|
+
|
|
618
|
+
self.beginWalletClosing(walletPubKeyHash);
|
|
619
|
+
|
|
620
|
+
// slither-disable-next-line reentrancy-events
|
|
621
|
+
emit MovingFundsBelowDustReported(walletPubKeyHash);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/// @notice Used by the wallet to prove the BTC moved funds sweep
|
|
625
|
+
/// transaction and to make the necessary state changes. Moved
|
|
626
|
+
/// funds sweep is only accepted if it satisfies SPV proof.
|
|
627
|
+
///
|
|
628
|
+
/// The function validates the sweep transaction structure by
|
|
629
|
+
/// checking if it actually spends the moved funds UTXO and the
|
|
630
|
+
/// sweeping wallet's main UTXO (optionally), and if it locks the
|
|
631
|
+
/// value on the sweeping wallet's 20-byte public key hash using a
|
|
632
|
+
/// reasonable transaction fee. If all preconditions are
|
|
633
|
+
/// met, this function updates the sweeping wallet main UTXO, thus
|
|
634
|
+
/// their BTC balance.
|
|
635
|
+
///
|
|
636
|
+
/// It is possible to prove the given sweep transaction only
|
|
637
|
+
/// one time.
|
|
638
|
+
/// @param sweepTx Bitcoin sweep funds transaction data.
|
|
639
|
+
/// @param sweepProof Bitcoin sweep funds proof data.
|
|
640
|
+
/// @param mainUtxo Data of the sweeping wallet's main UTXO, as currently
|
|
641
|
+
/// known on the Ethereum chain.
|
|
642
|
+
/// @dev Requirements:
|
|
643
|
+
/// - `sweepTx` components must match the expected structure. See
|
|
644
|
+
/// `BitcoinTx.Info` docs for reference. Their values must exactly
|
|
645
|
+
/// correspond to appropriate Bitcoin transaction fields to produce
|
|
646
|
+
/// a provable transaction hash,
|
|
647
|
+
/// - The `sweepTx` should represent a Bitcoin transaction with
|
|
648
|
+
/// the first input pointing to a wallet's sweep Pending request and,
|
|
649
|
+
/// optionally, the second input pointing to the wallet's main UTXO,
|
|
650
|
+
/// if the sweeping wallet has a main UTXO set. There should be only
|
|
651
|
+
/// one output locking funds on the sweeping wallet 20-byte public
|
|
652
|
+
/// key hash,
|
|
653
|
+
/// - `sweepProof` components must match the expected structure.
|
|
654
|
+
/// See `BitcoinTx.Proof` docs for reference. The `bitcoinHeaders`
|
|
655
|
+
/// field must contain a valid number of block headers, not less
|
|
656
|
+
/// than the `txProofDifficultyFactor` contract constant,
|
|
657
|
+
/// - `mainUtxo` components must point to the recent main UTXO
|
|
658
|
+
/// of the sweeping wallet, as currently known on the Ethereum chain.
|
|
659
|
+
/// If there is no main UTXO, this parameter is ignored,
|
|
660
|
+
/// - The sweeping wallet must be in the Live or MovingFunds state,
|
|
661
|
+
/// - The total Bitcoin transaction fee must be lesser or equal
|
|
662
|
+
/// to `movedFundsSweepTxMaxTotalFee` governable parameter.
|
|
663
|
+
function submitMovedFundsSweepProof(
|
|
664
|
+
BridgeState.Storage storage self,
|
|
665
|
+
BitcoinTx.Info calldata sweepTx,
|
|
666
|
+
BitcoinTx.Proof calldata sweepProof,
|
|
667
|
+
BitcoinTx.UTXO calldata mainUtxo
|
|
668
|
+
) external {
|
|
669
|
+
// The actual transaction proof is performed here. After that point, we
|
|
670
|
+
// can assume the transaction happened on Bitcoin chain and has
|
|
671
|
+
// a sufficient number of confirmations as determined by
|
|
672
|
+
// `txProofDifficultyFactor` constant.
|
|
673
|
+
bytes32 sweepTxHash = self.validateProof(sweepTx, sweepProof);
|
|
674
|
+
|
|
675
|
+
(
|
|
676
|
+
bytes20 walletPubKeyHash,
|
|
677
|
+
uint64 sweepTxOutputValue
|
|
678
|
+
) = processMovedFundsSweepTxOutput(self, sweepTx.outputVector);
|
|
679
|
+
|
|
680
|
+
(
|
|
681
|
+
Wallets.Wallet storage wallet,
|
|
682
|
+
BitcoinTx.UTXO memory resolvedMainUtxo
|
|
683
|
+
) = resolveMovedFundsSweepingWallet(self, walletPubKeyHash, mainUtxo);
|
|
684
|
+
|
|
685
|
+
uint256 sweepTxInputsTotalValue = processMovedFundsSweepTxInputs(
|
|
686
|
+
self,
|
|
687
|
+
sweepTx.inputVector,
|
|
688
|
+
resolvedMainUtxo,
|
|
689
|
+
walletPubKeyHash
|
|
690
|
+
);
|
|
691
|
+
|
|
692
|
+
require(
|
|
693
|
+
sweepTxInputsTotalValue - sweepTxOutputValue <=
|
|
694
|
+
self.movedFundsSweepTxMaxTotalFee,
|
|
695
|
+
"Transaction fee is too high"
|
|
696
|
+
);
|
|
697
|
+
|
|
698
|
+
// Use the sweep transaction output as the new sweeping wallet's main UTXO.
|
|
699
|
+
// Transaction output index is always 0 as sweep transaction always
|
|
700
|
+
// contains only one output.
|
|
701
|
+
wallet.mainUtxoHash = keccak256(
|
|
702
|
+
abi.encodePacked(sweepTxHash, uint32(0), sweepTxOutputValue)
|
|
703
|
+
);
|
|
704
|
+
|
|
705
|
+
// slither-disable-next-line reentrancy-events
|
|
706
|
+
emit MovedFundsSwept(walletPubKeyHash, sweepTxHash);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/// @notice Processes the Bitcoin moved funds sweep transaction output vector
|
|
710
|
+
/// by extracting the single output and using it to gain additional
|
|
711
|
+
/// information required for further processing (e.g. value and
|
|
712
|
+
/// wallet public key hash).
|
|
713
|
+
/// @param sweepTxOutputVector Bitcoin moved funds sweep transaction output
|
|
714
|
+
/// vector.
|
|
715
|
+
/// This function assumes vector's structure is valid so it must be
|
|
716
|
+
/// validated using e.g. `BTCUtils.validateVout` function before
|
|
717
|
+
/// it is passed here.
|
|
718
|
+
/// @return walletPubKeyHash 20-byte wallet public key hash.
|
|
719
|
+
/// @return value 8-byte moved funds sweep transaction output value.
|
|
720
|
+
/// @dev Requirements:
|
|
721
|
+
/// - Output vector must contain only one output,
|
|
722
|
+
/// - The single output must be of P2PKH or P2WPKH type and lock the
|
|
723
|
+
/// funds on a 20-byte public key hash.
|
|
724
|
+
function processMovedFundsSweepTxOutput(
|
|
725
|
+
BridgeState.Storage storage self,
|
|
726
|
+
bytes memory sweepTxOutputVector
|
|
727
|
+
) internal view returns (bytes20 walletPubKeyHash, uint64 value) {
|
|
728
|
+
// To determine the total number of sweep transaction outputs, we need to
|
|
729
|
+
// parse the compactSize uint (VarInt) the output vector is prepended by.
|
|
730
|
+
// That compactSize uint encodes the number of vector elements using the
|
|
731
|
+
// format presented in:
|
|
732
|
+
// https://developer.bitcoin.org/reference/transactions.html#compactsize-unsigned-integers
|
|
733
|
+
// We don't need asserting the compactSize uint is parseable since it
|
|
734
|
+
// was already checked during `validateVout` validation performed as
|
|
735
|
+
// part of the `BitcoinTx.validateProof` call.
|
|
736
|
+
// See `BitcoinTx.outputVector` docs for more details.
|
|
737
|
+
(, uint256 outputsCount) = sweepTxOutputVector.parseVarInt();
|
|
738
|
+
require(
|
|
739
|
+
outputsCount == 1,
|
|
740
|
+
"Moved funds sweep transaction must have a single output"
|
|
741
|
+
);
|
|
742
|
+
|
|
743
|
+
bytes memory output = sweepTxOutputVector.extractOutputAtIndex(0);
|
|
744
|
+
walletPubKeyHash = self.extractPubKeyHash(output);
|
|
745
|
+
value = output.extractValue();
|
|
746
|
+
|
|
747
|
+
return (walletPubKeyHash, value);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/// @notice Resolves sweeping wallet based on the provided wallet public key
|
|
751
|
+
/// hash. Validates the wallet state and current main UTXO, as
|
|
752
|
+
/// currently known on the Ethereum chain.
|
|
753
|
+
/// @param walletPubKeyHash public key hash of the wallet proving the sweep
|
|
754
|
+
/// Bitcoin transaction.
|
|
755
|
+
/// @param mainUtxo Data of the wallet's main UTXO, as currently known on
|
|
756
|
+
/// the Ethereum chain. If no main UTXO exists for the given wallet,
|
|
757
|
+
/// this parameter is ignored.
|
|
758
|
+
/// @return wallet Data of the sweeping wallet.
|
|
759
|
+
/// @return resolvedMainUtxo The actual main UTXO of the sweeping wallet
|
|
760
|
+
/// resolved by cross-checking the `mainUtxo` parameter with
|
|
761
|
+
/// the chain state. If the validation went well, this is the
|
|
762
|
+
/// plain-text main UTXO corresponding to the `wallet.mainUtxoHash`.
|
|
763
|
+
/// @dev Requirements:
|
|
764
|
+
/// - Sweeping wallet must be either in Live or MovingFunds state,
|
|
765
|
+
/// - If the main UTXO of the sweeping wallet exists in the storage,
|
|
766
|
+
/// the passed `mainUTXO` parameter must be equal to the stored one.
|
|
767
|
+
function resolveMovedFundsSweepingWallet(
|
|
768
|
+
BridgeState.Storage storage self,
|
|
769
|
+
bytes20 walletPubKeyHash,
|
|
770
|
+
BitcoinTx.UTXO calldata mainUtxo
|
|
771
|
+
)
|
|
772
|
+
internal
|
|
773
|
+
view
|
|
774
|
+
returns (
|
|
775
|
+
Wallets.Wallet storage wallet,
|
|
776
|
+
BitcoinTx.UTXO memory resolvedMainUtxo
|
|
777
|
+
)
|
|
778
|
+
{
|
|
779
|
+
wallet = self.registeredWallets[walletPubKeyHash];
|
|
780
|
+
|
|
781
|
+
Wallets.WalletState walletState = wallet.state;
|
|
782
|
+
require(
|
|
783
|
+
walletState == Wallets.WalletState.Live ||
|
|
784
|
+
walletState == Wallets.WalletState.MovingFunds,
|
|
785
|
+
"Wallet must be in Live or MovingFunds state"
|
|
786
|
+
);
|
|
787
|
+
|
|
788
|
+
// Check if the main UTXO for given wallet exists. If so, validate
|
|
789
|
+
// passed main UTXO data against the stored hash and use them for
|
|
790
|
+
// further processing. If no main UTXO exists, use empty data.
|
|
791
|
+
resolvedMainUtxo = BitcoinTx.UTXO(bytes32(0), 0, 0);
|
|
792
|
+
bytes32 mainUtxoHash = wallet.mainUtxoHash;
|
|
793
|
+
if (mainUtxoHash != bytes32(0)) {
|
|
794
|
+
require(
|
|
795
|
+
keccak256(
|
|
796
|
+
abi.encodePacked(
|
|
797
|
+
mainUtxo.txHash,
|
|
798
|
+
mainUtxo.txOutputIndex,
|
|
799
|
+
mainUtxo.txOutputValue
|
|
800
|
+
)
|
|
801
|
+
) == mainUtxoHash,
|
|
802
|
+
"Invalid main UTXO data"
|
|
803
|
+
);
|
|
804
|
+
resolvedMainUtxo = mainUtxo;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
/// @notice Processes the Bitcoin moved funds sweep transaction input vector.
|
|
809
|
+
/// It extracts the first input and tries to match it with one of
|
|
810
|
+
/// the moved funds sweep requests targeting the sweeping wallet.
|
|
811
|
+
/// If the sweep request is an existing Pending request, this
|
|
812
|
+
/// function marks it as Processed. If the sweeping wallet has a
|
|
813
|
+
/// main UTXO, this function extracts the second input, makes sure
|
|
814
|
+
/// it refers to the wallet main UTXO, and marks that main UTXO as
|
|
815
|
+
/// correctly spent.
|
|
816
|
+
/// @param sweepTxInputVector Bitcoin moved funds sweep transaction input vector.
|
|
817
|
+
/// This function assumes vector's structure is valid so it must be
|
|
818
|
+
/// validated using e.g. `BTCUtils.validateVin` function before
|
|
819
|
+
/// it is passed here.
|
|
820
|
+
/// @param mainUtxo Data of the sweeping wallet's main UTXO. If no main UTXO
|
|
821
|
+
/// exists for the given the wallet, this parameter's fields should
|
|
822
|
+
/// be zeroed to bypass the main UTXO validation.
|
|
823
|
+
/// @param walletPubKeyHash 20-byte public key hash of the sweeping wallet.
|
|
824
|
+
/// @return inputsTotalValue Total inputs value sum.
|
|
825
|
+
/// @dev Requirements:
|
|
826
|
+
/// - The input vector must consist of one mandatory and one optional
|
|
827
|
+
/// input,
|
|
828
|
+
/// - The mandatory input must be the first input in the vector,
|
|
829
|
+
/// - The mandatory input must point to a Pending moved funds sweep
|
|
830
|
+
/// request that is targeted to the sweeping wallet,
|
|
831
|
+
/// - The optional output must be the second input in the vector,
|
|
832
|
+
/// - The optional input is required if the sweeping wallet has a
|
|
833
|
+
/// main UTXO (i.e. the `mainUtxo` is not zeroed). In that case,
|
|
834
|
+
/// that input must point the the sweeping wallet main UTXO.
|
|
835
|
+
function processMovedFundsSweepTxInputs(
|
|
836
|
+
BridgeState.Storage storage self,
|
|
837
|
+
bytes memory sweepTxInputVector,
|
|
838
|
+
BitcoinTx.UTXO memory mainUtxo,
|
|
839
|
+
bytes20 walletPubKeyHash
|
|
840
|
+
) internal returns (uint256 inputsTotalValue) {
|
|
841
|
+
// To determine the total number of Bitcoin transaction inputs,
|
|
842
|
+
// we need to parse the compactSize uint (VarInt) the input vector is
|
|
843
|
+
// prepended by. That compactSize uint encodes the number of vector
|
|
844
|
+
// elements using the format presented in:
|
|
845
|
+
// https://developer.bitcoin.org/reference/transactions.html#compactsize-unsigned-integers
|
|
846
|
+
// We don't need asserting the compactSize uint is parseable since it
|
|
847
|
+
// was already checked during `validateVin` validation performed as
|
|
848
|
+
// part of the `BitcoinTx.validateProof` call.
|
|
849
|
+
// See `BitcoinTx.inputVector` docs for more details.
|
|
850
|
+
(
|
|
851
|
+
uint256 inputsCompactSizeUintLength,
|
|
852
|
+
uint256 inputsCount
|
|
853
|
+
) = sweepTxInputVector.parseVarInt();
|
|
854
|
+
|
|
855
|
+
// To determine the first input starting index, we must jump over
|
|
856
|
+
// the compactSize uint which prepends the input vector. One byte
|
|
857
|
+
// must be added because `BtcUtils.parseVarInt` does not include
|
|
858
|
+
// compactSize uint tag in the returned length.
|
|
859
|
+
//
|
|
860
|
+
// For >= 0 && <= 252, `BTCUtils.determineVarIntDataLengthAt`
|
|
861
|
+
// returns `0`, so we jump over one byte of compactSize uint.
|
|
862
|
+
//
|
|
863
|
+
// For >= 253 && <= 0xffff there is `0xfd` tag,
|
|
864
|
+
// `BTCUtils.determineVarIntDataLengthAt` returns `2` (no
|
|
865
|
+
// tag byte included) so we need to jump over 1+2 bytes of
|
|
866
|
+
// compactSize uint.
|
|
867
|
+
//
|
|
868
|
+
// Please refer `BTCUtils` library and compactSize uint
|
|
869
|
+
// docs in `BitcoinTx` library for more details.
|
|
870
|
+
uint256 inputStartingIndex = 1 + inputsCompactSizeUintLength;
|
|
871
|
+
|
|
872
|
+
// We always expect the first input to be the swept UTXO. Additionally,
|
|
873
|
+
// if the sweeping wallet has a main UTXO, that main UTXO should be
|
|
874
|
+
// pointed by the second input.
|
|
875
|
+
require(
|
|
876
|
+
inputsCount == (mainUtxo.txHash != bytes32(0) ? 2 : 1),
|
|
877
|
+
"Moved funds sweep transaction must have a proper inputs count"
|
|
878
|
+
);
|
|
879
|
+
|
|
880
|
+
// Parse the first input and extract its outpoint tx hash and index.
|
|
881
|
+
(
|
|
882
|
+
bytes32 firstInputOutpointTxHash,
|
|
883
|
+
uint32 firstInputOutpointIndex,
|
|
884
|
+
uint256 firstInputLength
|
|
885
|
+
) = parseMovedFundsSweepTxInputAt(
|
|
886
|
+
sweepTxInputVector,
|
|
887
|
+
inputStartingIndex
|
|
888
|
+
);
|
|
889
|
+
|
|
890
|
+
// Build the request key and fetch the corresponding moved funds sweep
|
|
891
|
+
// request from contract storage.
|
|
892
|
+
MovedFundsSweepRequest storage sweepRequest = self
|
|
893
|
+
.movedFundsSweepRequests[
|
|
894
|
+
uint256(
|
|
895
|
+
keccak256(
|
|
896
|
+
abi.encodePacked(
|
|
897
|
+
firstInputOutpointTxHash,
|
|
898
|
+
firstInputOutpointIndex
|
|
899
|
+
)
|
|
900
|
+
)
|
|
901
|
+
)
|
|
902
|
+
];
|
|
903
|
+
|
|
904
|
+
require(
|
|
905
|
+
sweepRequest.state == MovedFundsSweepRequestState.Pending,
|
|
906
|
+
"Sweep request must be in Pending state"
|
|
907
|
+
);
|
|
908
|
+
// We must check if the wallet extracted from the moved funds sweep
|
|
909
|
+
// transaction output is truly the owner of the sweep request connected
|
|
910
|
+
// with the swept UTXO. This is needed to prevent a case when a wallet
|
|
911
|
+
// handles its own sweep request but locks the funds on another
|
|
912
|
+
// wallet public key hash.
|
|
913
|
+
require(
|
|
914
|
+
sweepRequest.walletPubKeyHash == walletPubKeyHash,
|
|
915
|
+
"Sweep request belongs to another wallet"
|
|
916
|
+
);
|
|
917
|
+
// If the validation passed, the sweep request must be marked as
|
|
918
|
+
// processed and its value should be counted into the total inputs
|
|
919
|
+
// value sum.
|
|
920
|
+
sweepRequest.state = MovedFundsSweepRequestState.Processed;
|
|
921
|
+
inputsTotalValue += sweepRequest.value;
|
|
922
|
+
|
|
923
|
+
self
|
|
924
|
+
.registeredWallets[walletPubKeyHash]
|
|
925
|
+
.pendingMovedFundsSweepRequestsCount--;
|
|
926
|
+
|
|
927
|
+
// If the main UTXO for the sweeping wallet exists, it must be processed.
|
|
928
|
+
if (mainUtxo.txHash != bytes32(0)) {
|
|
929
|
+
// The second input is supposed to point to that sweeping wallet
|
|
930
|
+
// main UTXO. We need to parse that input.
|
|
931
|
+
(
|
|
932
|
+
bytes32 secondInputOutpointTxHash,
|
|
933
|
+
uint32 secondInputOutpointIndex,
|
|
934
|
+
|
|
935
|
+
) = parseMovedFundsSweepTxInputAt(
|
|
936
|
+
sweepTxInputVector,
|
|
937
|
+
inputStartingIndex + firstInputLength
|
|
938
|
+
);
|
|
939
|
+
// Make sure the second input refers to the sweeping wallet main UTXO.
|
|
940
|
+
require(
|
|
941
|
+
mainUtxo.txHash == secondInputOutpointTxHash &&
|
|
942
|
+
mainUtxo.txOutputIndex == secondInputOutpointIndex,
|
|
943
|
+
"Second input must point to the wallet's main UTXO"
|
|
944
|
+
);
|
|
945
|
+
|
|
946
|
+
// If the validation passed, count the main UTXO value into the
|
|
947
|
+
// total inputs value sum.
|
|
948
|
+
inputsTotalValue += mainUtxo.txOutputValue;
|
|
949
|
+
|
|
950
|
+
// Main UTXO used as an input, mark it as spent. This is needed
|
|
951
|
+
// to defend against fraud challenges referring to this main UTXO.
|
|
952
|
+
self.spentMainUTXOs[
|
|
953
|
+
uint256(
|
|
954
|
+
keccak256(
|
|
955
|
+
abi.encodePacked(
|
|
956
|
+
secondInputOutpointTxHash,
|
|
957
|
+
secondInputOutpointIndex
|
|
958
|
+
)
|
|
959
|
+
)
|
|
960
|
+
)
|
|
961
|
+
] = true;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
return inputsTotalValue;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
/// @notice Parses a Bitcoin transaction input starting at the given index.
|
|
968
|
+
/// @param inputVector Bitcoin transaction input vector.
|
|
969
|
+
/// @param inputStartingIndex Index the given input starts at.
|
|
970
|
+
/// @return outpointTxHash 32-byte hash of the Bitcoin transaction which is
|
|
971
|
+
/// pointed in the given input's outpoint.
|
|
972
|
+
/// @return outpointIndex 4-byte index of the Bitcoin transaction output
|
|
973
|
+
/// which is pointed in the given input's outpoint.
|
|
974
|
+
/// @return inputLength Byte length of the given input.
|
|
975
|
+
/// @dev This function assumes vector's structure is valid so it must be
|
|
976
|
+
/// validated using e.g. `BTCUtils.validateVin` function before it
|
|
977
|
+
/// is passed here.
|
|
978
|
+
function parseMovedFundsSweepTxInputAt(
|
|
979
|
+
bytes memory inputVector,
|
|
980
|
+
uint256 inputStartingIndex
|
|
981
|
+
)
|
|
982
|
+
internal
|
|
983
|
+
pure
|
|
984
|
+
returns (
|
|
985
|
+
bytes32 outpointTxHash,
|
|
986
|
+
uint32 outpointIndex,
|
|
987
|
+
uint256 inputLength
|
|
988
|
+
)
|
|
989
|
+
{
|
|
990
|
+
outpointTxHash = inputVector.extractInputTxIdLeAt(inputStartingIndex);
|
|
991
|
+
|
|
992
|
+
outpointIndex = BTCUtils.reverseUint32(
|
|
993
|
+
uint32(inputVector.extractTxIndexLeAt(inputStartingIndex))
|
|
994
|
+
);
|
|
995
|
+
|
|
996
|
+
inputLength = inputVector.determineInputLengthAt(inputStartingIndex);
|
|
997
|
+
|
|
998
|
+
return (outpointTxHash, outpointIndex, inputLength);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
/// @notice Notifies about a timed out moved funds sweep process. If the
|
|
1002
|
+
/// wallet is not terminated yet, that function terminates
|
|
1003
|
+
/// the wallet and slashes signing group members as a result.
|
|
1004
|
+
/// Marks the given sweep request as TimedOut.
|
|
1005
|
+
/// @param movingFundsTxHash 32-byte hash of the moving funds transaction
|
|
1006
|
+
/// that caused the sweep request to be created.
|
|
1007
|
+
/// @param movingFundsTxOutputIndex Index of the moving funds transaction
|
|
1008
|
+
/// output that is subject of the sweep request.
|
|
1009
|
+
/// @param walletMembersIDs Identifiers of the wallet signing group members.
|
|
1010
|
+
/// @dev Requirements:
|
|
1011
|
+
/// - The moved funds sweep request must be in the Pending state,
|
|
1012
|
+
/// - The moved funds sweep timeout must be actually exceeded,
|
|
1013
|
+
/// - The wallet must be either in the Live or MovingFunds or
|
|
1014
|
+
/// Terminated state,,
|
|
1015
|
+
/// - The expression `keccak256(abi.encode(walletMembersIDs))` must
|
|
1016
|
+
/// be exactly the same as the hash stored under `membersIdsHash`
|
|
1017
|
+
/// for the given `walletID`. Those IDs are not directly stored
|
|
1018
|
+
/// in the contract for gas efficiency purposes but they can be
|
|
1019
|
+
/// read from appropriate `DkgResultSubmitted` and `DkgResultApproved`
|
|
1020
|
+
/// events of the `WalletRegistry` contract.
|
|
1021
|
+
function notifyMovedFundsSweepTimeout(
|
|
1022
|
+
BridgeState.Storage storage self,
|
|
1023
|
+
bytes32 movingFundsTxHash,
|
|
1024
|
+
uint32 movingFundsTxOutputIndex,
|
|
1025
|
+
uint32[] calldata walletMembersIDs
|
|
1026
|
+
) external {
|
|
1027
|
+
MovedFundsSweepRequest storage sweepRequest = self
|
|
1028
|
+
.movedFundsSweepRequests[
|
|
1029
|
+
uint256(
|
|
1030
|
+
keccak256(
|
|
1031
|
+
abi.encodePacked(
|
|
1032
|
+
movingFundsTxHash,
|
|
1033
|
+
movingFundsTxOutputIndex
|
|
1034
|
+
)
|
|
1035
|
+
)
|
|
1036
|
+
)
|
|
1037
|
+
];
|
|
1038
|
+
|
|
1039
|
+
require(
|
|
1040
|
+
sweepRequest.state == MovedFundsSweepRequestState.Pending,
|
|
1041
|
+
"Sweep request must be in Pending state"
|
|
1042
|
+
);
|
|
1043
|
+
|
|
1044
|
+
require(
|
|
1045
|
+
/* solhint-disable-next-line not-rely-on-time */
|
|
1046
|
+
block.timestamp >
|
|
1047
|
+
sweepRequest.createdAt + self.movedFundsSweepTimeout,
|
|
1048
|
+
"Sweep request has not timed out yet"
|
|
1049
|
+
);
|
|
1050
|
+
|
|
1051
|
+
bytes20 walletPubKeyHash = sweepRequest.walletPubKeyHash;
|
|
1052
|
+
Wallets.Wallet storage wallet = self.registeredWallets[
|
|
1053
|
+
walletPubKeyHash
|
|
1054
|
+
];
|
|
1055
|
+
Wallets.WalletState walletState = wallet.state;
|
|
1056
|
+
|
|
1057
|
+
require(
|
|
1058
|
+
walletState == Wallets.WalletState.Live ||
|
|
1059
|
+
walletState == Wallets.WalletState.MovingFunds ||
|
|
1060
|
+
walletState == Wallets.WalletState.Terminated,
|
|
1061
|
+
"ECDSA wallet must be in Live or MovingFunds or Terminated state"
|
|
1062
|
+
);
|
|
1063
|
+
|
|
1064
|
+
sweepRequest.state = MovedFundsSweepRequestState.TimedOut;
|
|
1065
|
+
wallet.pendingMovedFundsSweepRequestsCount--;
|
|
1066
|
+
|
|
1067
|
+
if (
|
|
1068
|
+
walletState == Wallets.WalletState.Live ||
|
|
1069
|
+
walletState == Wallets.WalletState.MovingFunds
|
|
1070
|
+
) {
|
|
1071
|
+
self.terminateWallet(walletPubKeyHash);
|
|
1072
|
+
|
|
1073
|
+
self.ecdsaWalletRegistry.seize(
|
|
1074
|
+
self.movedFundsSweepTimeoutSlashingAmount,
|
|
1075
|
+
self.movedFundsSweepTimeoutNotifierRewardMultiplier,
|
|
1076
|
+
msg.sender,
|
|
1077
|
+
wallet.ecdsaWalletID,
|
|
1078
|
+
walletMembersIDs
|
|
1079
|
+
);
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
// slither-disable-next-line reentrancy-events
|
|
1083
|
+
emit MovedFundsSweepTimedOut(
|
|
1084
|
+
walletPubKeyHash,
|
|
1085
|
+
movingFundsTxHash,
|
|
1086
|
+
movingFundsTxOutputIndex
|
|
1087
|
+
);
|
|
1088
|
+
}
|
|
1089
|
+
}
|