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