@keep-network/tbtc-v2 0.1.1-dev.23 → 0.1.1-dev.26
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/TBTC.json +11 -10
- package/artifacts/TBTCToken.json +11 -10
- package/artifacts/VendingMachine.json +12 -11
- package/artifacts/solcInputs/37ed423e5330e1e70a9082eb804367e8.json +197 -0
- package/build/contracts/GovernanceUtils.sol/GovernanceUtils.dbg.json +1 -1
- package/build/contracts/bank/Bank.sol/Bank.dbg.json +1 -1
- package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.dbg.json +1 -1
- package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.json +2 -2
- package/build/contracts/bridge/Bridge.sol/Bridge.dbg.json +1 -1
- package/build/contracts/bridge/Bridge.sol/Bridge.json +793 -41
- package/build/contracts/bridge/Bridge.sol/IRelay.dbg.json +1 -1
- 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/Frauds.sol/Frauds.dbg.json +4 -0
- package/build/contracts/bridge/Frauds.sol/Frauds.json +138 -0
- package/build/contracts/bridge/VendingMachine.sol/VendingMachine.dbg.json +1 -1
- package/build/contracts/bridge/Wallets.sol/Wallets.dbg.json +4 -0
- package/build/contracts/bridge/Wallets.sol/Wallets.json +138 -0
- package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
- package/build/contracts/vault/IVault.sol/IVault.dbg.json +1 -1
- package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +1 -1
- package/contracts/bridge/BitcoinTx.sol +10 -0
- package/contracts/bridge/Bridge.sol +470 -156
- package/contracts/bridge/EcdsaLib.sol +30 -0
- package/contracts/bridge/Frauds.sol +531 -0
- package/contracts/bridge/Wallets.sol +520 -0
- package/package.json +20 -17
- package/artifacts/solcInputs/f4cbe9fa0e3015c189ba0e793fa17f51.json +0 -140
|
@@ -19,9 +19,13 @@ import "@openzeppelin/contracts/access/Ownable.sol";
|
|
|
19
19
|
|
|
20
20
|
import {BTCUtils} from "@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol";
|
|
21
21
|
import {BytesLib} from "@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol";
|
|
22
|
+
import {IWalletOwner as EcdsaWalletOwner} from "@keep-network/ecdsa/contracts/api/IWalletOwner.sol";
|
|
22
23
|
|
|
23
24
|
import "../bank/Bank.sol";
|
|
24
25
|
import "./BitcoinTx.sol";
|
|
26
|
+
import "./EcdsaLib.sol";
|
|
27
|
+
import "./Wallets.sol";
|
|
28
|
+
import "./Frauds.sol";
|
|
25
29
|
|
|
26
30
|
/// @title Interface for the Bitcoin relay
|
|
27
31
|
/// @notice Contains only the methods needed by tBTC v2. The Bitcoin relay
|
|
@@ -54,10 +58,12 @@ interface IRelay {
|
|
|
54
58
|
/// wallet informs the Bridge about the sweep increasing appropriate
|
|
55
59
|
/// balances in the Bank.
|
|
56
60
|
/// @dev Bridge is an upgradeable component of the Bank.
|
|
57
|
-
contract Bridge is Ownable {
|
|
61
|
+
contract Bridge is Ownable, EcdsaWalletOwner {
|
|
58
62
|
using BTCUtils for bytes;
|
|
59
63
|
using BTCUtils for uint256;
|
|
60
64
|
using BytesLib for bytes;
|
|
65
|
+
using Frauds for Frauds.Data;
|
|
66
|
+
using Wallets for Wallets.Data;
|
|
61
67
|
|
|
62
68
|
/// @notice Represents data which must be revealed by the depositor during
|
|
63
69
|
/// deposit reveal.
|
|
@@ -165,36 +171,6 @@ contract Bridge is Ownable {
|
|
|
165
171
|
uint64 changeValue;
|
|
166
172
|
}
|
|
167
173
|
|
|
168
|
-
/// @notice Represents wallet state:
|
|
169
|
-
enum WalletState {
|
|
170
|
-
/// @dev The wallet is unknown to the Bridge.
|
|
171
|
-
Unknown,
|
|
172
|
-
/// @dev The wallet can sweep deposits and accept redemption requests.
|
|
173
|
-
Active,
|
|
174
|
-
/// @dev The wallet was deemed unhealthy and is expected to move their
|
|
175
|
-
/// outstanding funds to another wallet. The wallet can still
|
|
176
|
-
/// fulfill their pending redemption requests although new
|
|
177
|
-
/// redemption requests and new deposit reveals are not accepted.
|
|
178
|
-
MovingFunds,
|
|
179
|
-
/// @dev The wallet moved or redeemed all their funds and cannot
|
|
180
|
-
/// perform any action.
|
|
181
|
-
Closed,
|
|
182
|
-
/// @dev The wallet committed a fraud that was reported. The wallet is
|
|
183
|
-
/// blocked and can not perform any actions in the Bridge.
|
|
184
|
-
/// Off-chain coordination with the wallet operators is needed to
|
|
185
|
-
/// recover funds.
|
|
186
|
-
Terminated
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/// @notice Holds information about a wallet.
|
|
190
|
-
struct Wallet {
|
|
191
|
-
// Current state of the wallet.
|
|
192
|
-
WalletState state;
|
|
193
|
-
// The total redeemable value of pending redemption requests targeting
|
|
194
|
-
// that wallet.
|
|
195
|
-
uint64 pendingRedemptionsValue;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
174
|
/// @notice The number of confirmations on the Bitcoin chain required to
|
|
199
175
|
/// successfully evaluate an SPV proof.
|
|
200
176
|
uint256 public immutable txProofDifficultyFactor;
|
|
@@ -287,14 +263,15 @@ contract Bridge is Ownable {
|
|
|
287
263
|
/// validating them before attempting to execute a sweep.
|
|
288
264
|
mapping(uint256 => DepositRequest) public deposits;
|
|
289
265
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
///
|
|
293
|
-
/// keccak256(
|
|
294
|
-
///
|
|
295
|
-
///
|
|
296
|
-
///
|
|
297
|
-
|
|
266
|
+
//TODO: Remember to update this map when implementing transferring funds
|
|
267
|
+
// between wallets (insert the main UTXO that was used as the input).
|
|
268
|
+
/// @notice Collection of main UTXOs that are honestly spent indexed by
|
|
269
|
+
/// keccak256(fundingTxHash | fundingOutputIndex). The fundingTxHash
|
|
270
|
+
/// is bytes32 (ordered as in Bitcoin internally) and
|
|
271
|
+
/// fundingOutputIndex an uint32. A main UTXO is considered honestly
|
|
272
|
+
/// spent if it was used as an input of a transaction that have been
|
|
273
|
+
/// proven in the Bridge.
|
|
274
|
+
mapping(uint256 => bool) public spentMainUTXOs;
|
|
298
275
|
|
|
299
276
|
/// @notice Collection of all pending redemption requests indexed by
|
|
300
277
|
/// redemption key built as
|
|
@@ -310,8 +287,6 @@ contract Bridge is Ownable {
|
|
|
310
287
|
/// successfully
|
|
311
288
|
/// - `notifyRedemptionTimeout` in case the request was reported
|
|
312
289
|
/// to be timed out
|
|
313
|
-
/// - `submitRedemptionFraudProof` in case the request was handled
|
|
314
|
-
/// in an fraudulent way amount-wise.
|
|
315
290
|
mapping(uint256 => RedemptionRequest) public pendingRedemptions;
|
|
316
291
|
|
|
317
292
|
/// @notice Collection of all timed out redemptions requests indexed by
|
|
@@ -332,17 +307,60 @@ contract Bridge is Ownable {
|
|
|
332
307
|
// slither-disable-next-line uninitialized-state
|
|
333
308
|
mapping(uint256 => RedemptionRequest) public timedOutRedemptions;
|
|
334
309
|
|
|
335
|
-
/// @notice
|
|
336
|
-
///
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
///
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
310
|
+
/// @notice Contains parameters related to frauds and the collection of all
|
|
311
|
+
/// submitted fraud challenges.
|
|
312
|
+
Frauds.Data internal frauds;
|
|
313
|
+
|
|
314
|
+
/// @notice State related with wallets.
|
|
315
|
+
Wallets.Data internal wallets;
|
|
316
|
+
|
|
317
|
+
event WalletCreationPeriodUpdated(uint32 newCreationPeriod);
|
|
318
|
+
|
|
319
|
+
event WalletBtcBalanceRangeUpdated(
|
|
320
|
+
uint64 newMinBtcBalance,
|
|
321
|
+
uint64 newMaxBtcBalance
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
event WalletMaxAgeUpdated(uint32 newMaxAge);
|
|
325
|
+
|
|
326
|
+
event NewWalletRequested();
|
|
327
|
+
|
|
328
|
+
event NewWalletRegistered(
|
|
329
|
+
bytes32 indexed ecdsaWalletID,
|
|
330
|
+
bytes20 indexed walletPubKeyHash
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
event WalletMovingFunds(
|
|
334
|
+
bytes32 indexed ecdsaWalletID,
|
|
335
|
+
bytes20 indexed walletPubKeyHash
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
event WalletClosed(
|
|
339
|
+
bytes32 indexed ecdsaWalletID,
|
|
340
|
+
bytes20 indexed walletPubKeyHash
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
event WalletTerminated(
|
|
344
|
+
bytes32 indexed ecdsaWalletID,
|
|
345
|
+
bytes20 indexed walletPubKeyHash
|
|
346
|
+
);
|
|
343
347
|
|
|
344
348
|
event VaultStatusUpdated(address indexed vault, bool isTrusted);
|
|
345
349
|
|
|
350
|
+
event FraudSlashingAmountUpdated(uint256 newFraudSlashingAmount);
|
|
351
|
+
|
|
352
|
+
event FraudNotifierRewardMultiplierUpdated(
|
|
353
|
+
uint256 newFraudNotifierRewardMultiplier
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
event FraudChallengeDefeatTimeoutUpdated(
|
|
357
|
+
uint256 newFraudChallengeDefeatTimeout
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
event FraudChallengeDepositAmountUpdated(
|
|
361
|
+
uint256 newFraudChallengeDepositAmount
|
|
362
|
+
);
|
|
363
|
+
|
|
346
364
|
event DepositRevealed(
|
|
347
365
|
bytes32 fundingTxHash,
|
|
348
366
|
uint32 fundingOutputIndex,
|
|
@@ -371,10 +389,26 @@ contract Bridge is Ownable {
|
|
|
371
389
|
bytes32 redemptionTxHash
|
|
372
390
|
);
|
|
373
391
|
|
|
392
|
+
event FraudChallengeSubmitted(
|
|
393
|
+
bytes20 walletPublicKeyHash,
|
|
394
|
+
bytes32 sighash,
|
|
395
|
+
uint8 v,
|
|
396
|
+
bytes32 r,
|
|
397
|
+
bytes32 s
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
event FraudChallengeDefeated(bytes20 walletPublicKeyHash, bytes32 sighash);
|
|
401
|
+
|
|
402
|
+
event FraudChallengeDefeatTimedOut(
|
|
403
|
+
bytes20 walletPublicKeyHash,
|
|
404
|
+
bytes32 sighash
|
|
405
|
+
);
|
|
406
|
+
|
|
374
407
|
constructor(
|
|
375
408
|
address _bank,
|
|
376
409
|
address _relay,
|
|
377
410
|
address _treasury,
|
|
411
|
+
address _ecdsaWalletRegistry,
|
|
378
412
|
uint256 _txProofDifficultyFactor
|
|
379
413
|
) {
|
|
380
414
|
require(_bank != address(0), "Bank address cannot be zero");
|
|
@@ -396,10 +430,59 @@ contract Bridge is Ownable {
|
|
|
396
430
|
redemptionTreasuryFeeDivisor = 2000; // 1/2000 == 5bps == 0.05% == 0.0005
|
|
397
431
|
redemptionTxMaxFee = 1000; // 1000 satoshi
|
|
398
432
|
redemptionTimeout = 172800; // 48 hours
|
|
433
|
+
frauds.setSlashingAmount(10000 * 1e18); // 10000 T
|
|
434
|
+
frauds.setNotifierRewardMultiplier(100); // 100%
|
|
435
|
+
frauds.setChallengeDefeatTimeout(7 days);
|
|
436
|
+
frauds.setChallengeDepositAmount(2 ether);
|
|
437
|
+
|
|
438
|
+
// TODO: Revisit initial values.
|
|
439
|
+
wallets.init(_ecdsaWalletRegistry);
|
|
440
|
+
wallets.setCreationPeriod(1 weeks);
|
|
441
|
+
wallets.setBtcBalanceRange(1 * 1e8, 10 * 1e8); // [1 BTC, 10 BTC]
|
|
442
|
+
wallets.setMaxAge(26 weeks); // ~6 months
|
|
399
443
|
}
|
|
400
444
|
|
|
401
|
-
|
|
402
|
-
|
|
445
|
+
/// @notice Updates parameters used by the `Wallets` library.
|
|
446
|
+
/// @param creationPeriod New value of the wallet creation period
|
|
447
|
+
/// @param minBtcBalance New value of the minimum BTC balance
|
|
448
|
+
/// @param maxBtcBalance New value of the maximum BTC balance
|
|
449
|
+
/// @param maxAge New value of the wallet maximum age
|
|
450
|
+
/// @dev Requirements:
|
|
451
|
+
/// - Caller must be the contract owner.
|
|
452
|
+
/// - Minimum BTC balance must be greater than zero
|
|
453
|
+
/// - Maximum BTC balance must be greater than minimum BTC balance
|
|
454
|
+
function updateWalletsParameters(
|
|
455
|
+
uint32 creationPeriod,
|
|
456
|
+
uint64 minBtcBalance,
|
|
457
|
+
uint64 maxBtcBalance,
|
|
458
|
+
uint32 maxAge
|
|
459
|
+
) external onlyOwner {
|
|
460
|
+
wallets.setCreationPeriod(creationPeriod);
|
|
461
|
+
wallets.setBtcBalanceRange(minBtcBalance, maxBtcBalance);
|
|
462
|
+
wallets.setMaxAge(maxAge);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/// @return creationPeriod Value of the wallet creation period
|
|
466
|
+
/// @return minBtcBalance Value of the minimum BTC balance
|
|
467
|
+
/// @return maxBtcBalance Value of the maximum BTC balance
|
|
468
|
+
/// @return maxAge Value of the wallet max age
|
|
469
|
+
function getWalletsParameters()
|
|
470
|
+
external
|
|
471
|
+
view
|
|
472
|
+
returns (
|
|
473
|
+
uint32 creationPeriod,
|
|
474
|
+
uint64 minBtcBalance,
|
|
475
|
+
uint64 maxBtcBalance,
|
|
476
|
+
uint32 maxAge
|
|
477
|
+
)
|
|
478
|
+
{
|
|
479
|
+
creationPeriod = wallets.creationPeriod;
|
|
480
|
+
minBtcBalance = wallets.minBtcBalance;
|
|
481
|
+
maxBtcBalance = wallets.maxBtcBalance;
|
|
482
|
+
maxAge = wallets.maxAge;
|
|
483
|
+
|
|
484
|
+
return (creationPeriod, minBtcBalance, maxBtcBalance, maxAge);
|
|
485
|
+
}
|
|
403
486
|
|
|
404
487
|
/// @notice Allows the Governance to mark the given vault address as trusted
|
|
405
488
|
/// or no longer trusted. Vaults are not trusted by default.
|
|
@@ -419,6 +502,105 @@ contract Bridge is Ownable {
|
|
|
419
502
|
emit VaultStatusUpdated(vault, isTrusted);
|
|
420
503
|
}
|
|
421
504
|
|
|
505
|
+
/// @notice Requests creation of a new wallet. This function just
|
|
506
|
+
/// forms a request and the creation process is performed
|
|
507
|
+
/// asynchronously. Once a wallet is created, the ECDSA Wallet
|
|
508
|
+
/// Registry will notify this contract by calling the
|
|
509
|
+
/// `__ecdsaWalletCreatedCallback` function.
|
|
510
|
+
/// @param activeWalletMainUtxo Data of the active wallet's main UTXO, as
|
|
511
|
+
/// currently known on the Ethereum chain.
|
|
512
|
+
/// @dev Requirements:
|
|
513
|
+
/// - `activeWalletMainUtxo` components must point to the recent main
|
|
514
|
+
/// UTXO of the given active wallet, as currently known on the
|
|
515
|
+
/// Ethereum chain. If there is no active wallet at the moment, or
|
|
516
|
+
/// the active wallet has no main UTXO, this parameter can be
|
|
517
|
+
/// empty as it is ignored.
|
|
518
|
+
/// - Wallet creation must not be in progress
|
|
519
|
+
/// - If the active wallet is set, one of the following
|
|
520
|
+
/// conditions must be true:
|
|
521
|
+
/// - The active wallet BTC balance is above the minimum threshold
|
|
522
|
+
/// and the active wallet is old enough, i.e. the creation period
|
|
523
|
+
/// was elapsed since its creation time
|
|
524
|
+
/// - The active wallet BTC balance is above the maximum threshold
|
|
525
|
+
function requestNewWallet(BitcoinTx.UTXO calldata activeWalletMainUtxo)
|
|
526
|
+
external
|
|
527
|
+
{
|
|
528
|
+
wallets.requestNewWallet(activeWalletMainUtxo);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/// @notice A callback function that is called by the ECDSA Wallet Registry
|
|
532
|
+
/// once a new ECDSA wallet is created.
|
|
533
|
+
/// @param ecdsaWalletID Wallet's unique identifier.
|
|
534
|
+
/// @param publicKeyX Wallet's public key's X coordinate.
|
|
535
|
+
/// @param publicKeyY Wallet's public key's Y coordinate.
|
|
536
|
+
/// @dev Requirements:
|
|
537
|
+
/// - The only caller authorized to call this function is `registry`
|
|
538
|
+
/// - Given wallet data must not belong to an already registered wallet
|
|
539
|
+
function __ecdsaWalletCreatedCallback(
|
|
540
|
+
bytes32 ecdsaWalletID,
|
|
541
|
+
bytes32 publicKeyX,
|
|
542
|
+
bytes32 publicKeyY
|
|
543
|
+
) external override {
|
|
544
|
+
wallets.registerNewWallet(ecdsaWalletID, publicKeyX, publicKeyY);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/// @notice A callback function that is called by the ECDSA Wallet Registry
|
|
548
|
+
/// once a wallet heartbeat failure is detected.
|
|
549
|
+
/// @param publicKeyX Wallet's public key's X coordinate
|
|
550
|
+
/// @param publicKeyY Wallet's public key's Y coordinate
|
|
551
|
+
/// @dev Requirements:
|
|
552
|
+
/// - The only caller authorized to call this function is `registry`
|
|
553
|
+
/// - Wallet must be in Live state
|
|
554
|
+
function __ecdsaWalletHeartbeatFailedCallback(
|
|
555
|
+
bytes32,
|
|
556
|
+
bytes32 publicKeyX,
|
|
557
|
+
bytes32 publicKeyY
|
|
558
|
+
) external override {
|
|
559
|
+
wallets.notifyWalletHeartbeatFailed(publicKeyX, publicKeyY);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/// @notice Notifies that the wallet is either old enough or has too few
|
|
563
|
+
/// satoshis left and qualifies to be closed.
|
|
564
|
+
/// @param walletPubKeyHash 20-byte public key hash of the wallet
|
|
565
|
+
/// @param walletMainUtxo Data of the wallet's main UTXO, as currently
|
|
566
|
+
/// known on the Ethereum chain.
|
|
567
|
+
/// @dev Requirements:
|
|
568
|
+
/// - Wallet must not be set as the current active wallet
|
|
569
|
+
/// - Wallet must exceed the wallet maximum age OR the wallet BTC
|
|
570
|
+
/// balance must be lesser than the minimum threshold. If the latter
|
|
571
|
+
/// case is true, the `walletMainUtxo` components must point to the
|
|
572
|
+
/// recent main UTXO of the given wallet, as currently known on the
|
|
573
|
+
/// Ethereum chain. If the wallet has no main UTXO, this parameter
|
|
574
|
+
/// can be empty as it is ignored since the wallet balance is
|
|
575
|
+
/// assumed to be zero.
|
|
576
|
+
/// - Wallet must be in Live state
|
|
577
|
+
function notifyCloseableWallet(
|
|
578
|
+
bytes20 walletPubKeyHash,
|
|
579
|
+
BitcoinTx.UTXO calldata walletMainUtxo
|
|
580
|
+
) external {
|
|
581
|
+
wallets.notifyCloseableWallet(walletPubKeyHash, walletMainUtxo);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/// @notice Gets details about a registered wallet.
|
|
585
|
+
/// @param walletPubKeyHash The 20-byte wallet public key hash (computed
|
|
586
|
+
/// using Bitcoin HASH160 over the compressed ECDSA public key)
|
|
587
|
+
/// @return Wallet details.
|
|
588
|
+
function getWallet(bytes20 walletPubKeyHash)
|
|
589
|
+
external
|
|
590
|
+
view
|
|
591
|
+
returns (Wallets.Wallet memory)
|
|
592
|
+
{
|
|
593
|
+
return wallets.registeredWallets[walletPubKeyHash];
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/// @notice Gets the public key hash of the active wallet.
|
|
597
|
+
/// @return The 20-byte public key hash (computed using Bitcoin HASH160
|
|
598
|
+
/// over the compressed ECDSA public key) of the active wallet.
|
|
599
|
+
/// Returns bytes20(0) if there is no active wallet at the moment.
|
|
600
|
+
function getActiveWalletPubKeyHash() external view returns (bytes20) {
|
|
601
|
+
return wallets.activeWalletPubKeyHash;
|
|
602
|
+
}
|
|
603
|
+
|
|
422
604
|
/// @notice Determines the current Bitcoin SPV proof difficulty context.
|
|
423
605
|
/// @return proofDifficulty Bitcoin proof difficulty context.
|
|
424
606
|
function proofDifficultyContext()
|
|
@@ -477,7 +659,7 @@ contract Bridge is Ownable {
|
|
|
477
659
|
"Vault is not trusted"
|
|
478
660
|
);
|
|
479
661
|
|
|
480
|
-
// TODO: Validate if `walletPubKeyHash` is a known and
|
|
662
|
+
// TODO: Validate if `walletPubKeyHash` is a known and live wallet.
|
|
481
663
|
// TODO: Should we enforce a specific locktime at contract level?
|
|
482
664
|
|
|
483
665
|
bytes memory expectedScript = abi.encodePacked(
|
|
@@ -646,7 +828,11 @@ contract Bridge is Ownable {
|
|
|
646
828
|
uint64 sweepTxOutputValue
|
|
647
829
|
) = processSweepTxOutput(sweepTx.outputVector);
|
|
648
830
|
|
|
649
|
-
|
|
831
|
+
Wallets.Wallet storage wallet = wallets.registeredWallets[
|
|
832
|
+
walletPubKeyHash
|
|
833
|
+
];
|
|
834
|
+
|
|
835
|
+
// TODO: Validate if `walletPubKeyHash` is a known and live wallet.
|
|
650
836
|
|
|
651
837
|
// Check if the main UTXO for given wallet exists. If so, validate
|
|
652
838
|
// passed main UTXO data against the stored hash and use them for
|
|
@@ -656,7 +842,7 @@ contract Bridge is Ownable {
|
|
|
656
842
|
0,
|
|
657
843
|
0
|
|
658
844
|
);
|
|
659
|
-
bytes32 mainUtxoHash =
|
|
845
|
+
bytes32 mainUtxoHash = wallet.mainUtxoHash;
|
|
660
846
|
if (mainUtxoHash != bytes32(0)) {
|
|
661
847
|
require(
|
|
662
848
|
keccak256(
|
|
@@ -724,7 +910,7 @@ contract Bridge is Ownable {
|
|
|
724
910
|
// Record this sweep data and assign them to the wallet public key hash
|
|
725
911
|
// as new main UTXO. Transaction output index is always 0 as sweep
|
|
726
912
|
// transaction always contains only one output.
|
|
727
|
-
|
|
913
|
+
wallet.mainUtxoHash = keccak256(
|
|
728
914
|
abi.encodePacked(sweepTxHash, uint32(0), sweepTxOutputValue)
|
|
729
915
|
);
|
|
730
916
|
|
|
@@ -896,6 +1082,15 @@ contract Bridge is Ownable {
|
|
|
896
1082
|
// the expected main UTXO.
|
|
897
1083
|
info.inputsTotalValue += mainUtxo.txOutputValue;
|
|
898
1084
|
mainUtxoFound = true;
|
|
1085
|
+
|
|
1086
|
+
// Main UTXO used as an input, mark it as spent.
|
|
1087
|
+
spentMainUTXOs[
|
|
1088
|
+
uint256(
|
|
1089
|
+
keccak256(
|
|
1090
|
+
abi.encodePacked(outpointTxHash, outpointIndex)
|
|
1091
|
+
)
|
|
1092
|
+
)
|
|
1093
|
+
] = true;
|
|
899
1094
|
} else {
|
|
900
1095
|
revert("Unknown input type");
|
|
901
1096
|
}
|
|
@@ -995,7 +1190,7 @@ contract Bridge is Ownable {
|
|
|
995
1190
|
/// @notice Requests redemption of the given amount from the specified
|
|
996
1191
|
/// wallet to the redeemer Bitcoin output script.
|
|
997
1192
|
/// @param walletPubKeyHash The 20-byte wallet public key hash (computed
|
|
998
|
-
|
|
1193
|
+
/// using Bitcoin HASH160 over the compressed ECDSA public key)
|
|
999
1194
|
/// @param mainUtxo Data of the wallet's main UTXO, as currently known on
|
|
1000
1195
|
/// the Ethereum chain
|
|
1001
1196
|
/// @param redeemerOutputScript The redeemer's length-prefixed output
|
|
@@ -1010,7 +1205,7 @@ contract Bridge is Ownable {
|
|
|
1010
1205
|
/// `amount - (amount / redemptionTreasuryFeeDivisor) - redemptionTxMaxFee`.
|
|
1011
1206
|
/// Fees values are taken at the moment of request creation.
|
|
1012
1207
|
/// @dev Requirements:
|
|
1013
|
-
/// - Wallet behind `walletPubKeyHash` must be
|
|
1208
|
+
/// - Wallet behind `walletPubKeyHash` must be live
|
|
1014
1209
|
/// - `mainUtxo` components must point to the recent main UTXO
|
|
1015
1210
|
/// of the given wallet, as currently known on the Ethereum chain.
|
|
1016
1211
|
/// - `redeemerOutputScript` must be a proper Bitcoin script
|
|
@@ -1027,12 +1222,16 @@ contract Bridge is Ownable {
|
|
|
1027
1222
|
bytes calldata redeemerOutputScript,
|
|
1028
1223
|
uint64 amount
|
|
1029
1224
|
) external {
|
|
1225
|
+
Wallets.Wallet storage wallet = wallets.registeredWallets[
|
|
1226
|
+
walletPubKeyHash
|
|
1227
|
+
];
|
|
1228
|
+
|
|
1030
1229
|
require(
|
|
1031
|
-
|
|
1032
|
-
"Wallet must be in
|
|
1230
|
+
wallet.state == Wallets.WalletState.Live,
|
|
1231
|
+
"Wallet must be in Live state"
|
|
1033
1232
|
);
|
|
1034
1233
|
|
|
1035
|
-
bytes32 mainUtxoHash =
|
|
1234
|
+
bytes32 mainUtxoHash = wallet.mainUtxoHash;
|
|
1036
1235
|
require(
|
|
1037
1236
|
mainUtxoHash != bytes32(0),
|
|
1038
1237
|
"No main UTXO for the given wallet"
|
|
@@ -1094,7 +1293,7 @@ contract Bridge is Ownable {
|
|
|
1094
1293
|
|
|
1095
1294
|
// Check if given redemption key is not used by a pending redemption.
|
|
1096
1295
|
// There is no need to check for existence in `timedOutRedemptions`
|
|
1097
|
-
// since the wallet's state is changed to other than
|
|
1296
|
+
// since the wallet's state is changed to other than Live after
|
|
1098
1297
|
// first time out is reported so making new requests is not possible.
|
|
1099
1298
|
// slither-disable-next-line incorrect-equality
|
|
1100
1299
|
require(
|
|
@@ -1115,12 +1314,9 @@ contract Bridge is Ownable {
|
|
|
1115
1314
|
// wallet we need to subtract the total value of all pending redemptions
|
|
1116
1315
|
// from that wallet's main UTXO value. Given that the treasury fee is
|
|
1117
1316
|
// not redeemed from the wallet, we are subtracting it.
|
|
1118
|
-
|
|
1119
|
-
amount -
|
|
1120
|
-
treasuryFee;
|
|
1317
|
+
wallet.pendingRedemptionsValue += amount - treasuryFee;
|
|
1121
1318
|
require(
|
|
1122
|
-
mainUtxo.txOutputValue >=
|
|
1123
|
-
wallets[walletPubKeyHash].pendingRedemptionsValue,
|
|
1319
|
+
mainUtxo.txOutputValue >= wallet.pendingRedemptionsValue,
|
|
1124
1320
|
"Insufficient wallet funds"
|
|
1125
1321
|
);
|
|
1126
1322
|
|
|
@@ -1217,11 +1413,15 @@ contract Bridge is Ownable {
|
|
|
1217
1413
|
walletPubKeyHash
|
|
1218
1414
|
);
|
|
1219
1415
|
|
|
1220
|
-
|
|
1416
|
+
Wallets.Wallet storage wallet = wallets.registeredWallets[
|
|
1417
|
+
walletPubKeyHash
|
|
1418
|
+
];
|
|
1419
|
+
|
|
1420
|
+
Wallets.WalletState walletState = wallet.state;
|
|
1221
1421
|
require(
|
|
1222
|
-
walletState == WalletState.
|
|
1223
|
-
walletState == WalletState.MovingFunds,
|
|
1224
|
-
"Wallet must be in
|
|
1422
|
+
walletState == Wallets.WalletState.Live ||
|
|
1423
|
+
walletState == Wallets.WalletState.MovingFunds,
|
|
1424
|
+
"Wallet must be in Live or MovingFuds state"
|
|
1225
1425
|
);
|
|
1226
1426
|
|
|
1227
1427
|
// Process redemption transaction outputs to extract some info required
|
|
@@ -1234,7 +1434,7 @@ contract Bridge is Ownable {
|
|
|
1234
1434
|
if (outputsInfo.changeValue > 0) {
|
|
1235
1435
|
// If the change value is grater than zero, it means the change
|
|
1236
1436
|
// output exists and can be used as new wallet's main UTXO.
|
|
1237
|
-
|
|
1437
|
+
wallet.mainUtxoHash = keccak256(
|
|
1238
1438
|
abi.encodePacked(
|
|
1239
1439
|
redemptionTxHash,
|
|
1240
1440
|
outputsInfo.changeIndex,
|
|
@@ -1245,11 +1445,10 @@ contract Bridge is Ownable {
|
|
|
1245
1445
|
// If the change value is zero, it means the change output doesn't
|
|
1246
1446
|
// exists and no funds left on the wallet. Delete the main UTXO
|
|
1247
1447
|
// for that wallet to represent that state in a proper way.
|
|
1248
|
-
delete
|
|
1448
|
+
delete wallet.mainUtxoHash;
|
|
1249
1449
|
}
|
|
1250
1450
|
|
|
1251
|
-
|
|
1252
|
-
.totalBurnableValue;
|
|
1451
|
+
wallet.pendingRedemptionsValue -= outputsInfo.totalBurnableValue;
|
|
1253
1452
|
|
|
1254
1453
|
emit RedemptionsCompleted(walletPubKeyHash, redemptionTxHash);
|
|
1255
1454
|
|
|
@@ -1257,6 +1456,187 @@ contract Bridge is Ownable {
|
|
|
1257
1456
|
bank.transferBalance(treasury, outputsInfo.totalTreasuryFee);
|
|
1258
1457
|
}
|
|
1259
1458
|
|
|
1459
|
+
/// @notice Submits a fraud challenge indicating that a UTXO being under
|
|
1460
|
+
/// wallet control was unlocked by the wallet but was not used
|
|
1461
|
+
/// according to the protocol rules. That means the wallet signed
|
|
1462
|
+
/// a transaction input pointing to that UTXO and there is a unique
|
|
1463
|
+
/// sighash and signature pair associated with that input. This
|
|
1464
|
+
/// function uses those parameters to create a fraud accusation that
|
|
1465
|
+
/// proves a given transaction input unlocking the given UTXO was
|
|
1466
|
+
/// actually signed by the wallet. This function cannot determine
|
|
1467
|
+
/// whether the transaction was actually broadcast and the input was
|
|
1468
|
+
/// consumed in a fraudulent way so it just opens a challenge period
|
|
1469
|
+
/// during which the wallet can defeat the challenge by submitting
|
|
1470
|
+
/// proof of a transaction that consumes the given input according
|
|
1471
|
+
/// to protocol rules. To prevent spurious allegations, the caller
|
|
1472
|
+
/// must deposit ETH that is returned back upon justified fraud
|
|
1473
|
+
/// challenge or confiscated otherwise.
|
|
1474
|
+
/// @param walletPublicKey The public key of the wallet in the uncompressed
|
|
1475
|
+
/// and unprefixed format (64 bytes)
|
|
1476
|
+
/// @param sighash The hash that was used to produce the ECDSA signature
|
|
1477
|
+
/// that is the subject of the fraud claim. This hash is constructed
|
|
1478
|
+
/// by applying double SHA-256 over a serialized subset of the
|
|
1479
|
+
/// transaction. The exact subset used as hash preimage depends on
|
|
1480
|
+
/// the transaction input the signature is produced for. See BIP-143
|
|
1481
|
+
/// for reference
|
|
1482
|
+
/// @param signature Bitcoin signature in the R/S/V format
|
|
1483
|
+
/// @dev Requirements:
|
|
1484
|
+
/// - Wallet behind `walletPubKey` must be in `Live` or `MovingFunds`
|
|
1485
|
+
/// state
|
|
1486
|
+
/// - The challenger must send appropriate amount of ETH used as
|
|
1487
|
+
/// fraud challenge deposit
|
|
1488
|
+
/// - The signature (represented by r, s and v) must be generated by
|
|
1489
|
+
/// the wallet behind `walletPubKey` during signing of `sighash`
|
|
1490
|
+
/// - Wallet can be challenged for the given signature only once
|
|
1491
|
+
/// TODO: Consider using wallet public key in the X/Y form to avoid slicing.
|
|
1492
|
+
function submitFraudChallenge(
|
|
1493
|
+
bytes calldata walletPublicKey,
|
|
1494
|
+
bytes32 sighash,
|
|
1495
|
+
BitcoinTx.RSVSignature calldata signature
|
|
1496
|
+
) external payable {
|
|
1497
|
+
bytes memory compressedWalletPublicKey = EcdsaLib.compressPublicKey(
|
|
1498
|
+
walletPublicKey.slice32(0),
|
|
1499
|
+
walletPublicKey.slice32(32)
|
|
1500
|
+
);
|
|
1501
|
+
bytes20 walletPubKeyHash = compressedWalletPublicKey.hash160View();
|
|
1502
|
+
|
|
1503
|
+
Wallets.Wallet storage wallet = wallets.registeredWallets[
|
|
1504
|
+
walletPubKeyHash
|
|
1505
|
+
];
|
|
1506
|
+
|
|
1507
|
+
require(
|
|
1508
|
+
wallet.state == Wallets.WalletState.Live ||
|
|
1509
|
+
wallet.state == Wallets.WalletState.MovingFunds,
|
|
1510
|
+
"Wallet is neither in Live nor MovingFunds state"
|
|
1511
|
+
);
|
|
1512
|
+
|
|
1513
|
+
frauds.submitFraudChallenge(
|
|
1514
|
+
walletPublicKey,
|
|
1515
|
+
walletPubKeyHash,
|
|
1516
|
+
sighash,
|
|
1517
|
+
signature
|
|
1518
|
+
);
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
/// @notice Allows to defeat a pending fraud challenge against a wallet if
|
|
1522
|
+
/// the transaction that spends the UTXO follows the protocol rules.
|
|
1523
|
+
/// In order to defeat the challenge the same `walletPublicKey` and
|
|
1524
|
+
/// signature (represented by `r`, `s` and `v`) must be provided as
|
|
1525
|
+
/// were used in the fraud challenge. Additionally a preimage must
|
|
1526
|
+
/// be provided which was used to calculate the sighash during input
|
|
1527
|
+
/// signing. The fraud challenge defeat attempt will only succeed if
|
|
1528
|
+
/// the inputs in the preimage are considered honestly spent by the
|
|
1529
|
+
/// wallet. Therefore the transaction spending the UTXO must be
|
|
1530
|
+
/// proven in the Bridge before a challenge defeat is called.
|
|
1531
|
+
/// If successfully defeated, the fraud challenge is marked as
|
|
1532
|
+
/// resolved and the amount of ether deposited by the challenger is
|
|
1533
|
+
/// sent to the treasury.
|
|
1534
|
+
/// @param walletPublicKey The public key of the wallet in the uncompressed
|
|
1535
|
+
/// and unprefixed format (64 bytes)
|
|
1536
|
+
/// @param preimage The preimage which produces sighash used to generate the
|
|
1537
|
+
/// ECDSA signature that is the subject of the fraud claim. It is a
|
|
1538
|
+
/// serialized subset of the transaction. The exact subset used as
|
|
1539
|
+
/// the preimage depends on the transaction input the signature is
|
|
1540
|
+
/// produced for. See BIP-143 for reference
|
|
1541
|
+
/// @param witness Flag indicating whether the preimage was produced for a
|
|
1542
|
+
/// witness input. True for witness, false for non-witness input
|
|
1543
|
+
/// @dev Requirements:
|
|
1544
|
+
/// - `walletPublicKey` and `sighash` calculated as `hash256(preimage)`
|
|
1545
|
+
/// must identify an open fraud challenge
|
|
1546
|
+
/// - the preimage must be a valid preimage of a transaction generated
|
|
1547
|
+
/// according to the protocol rules and already proved in the Bridge
|
|
1548
|
+
/// - before a defeat attempt is made the transaction that spends the
|
|
1549
|
+
/// given UTXO must be proven in the Bridge
|
|
1550
|
+
/// TODO: Consider using wallet public key in the X/Y form to avoid slicing.
|
|
1551
|
+
function defeatFraudChallenge(
|
|
1552
|
+
bytes calldata walletPublicKey,
|
|
1553
|
+
bytes calldata preimage,
|
|
1554
|
+
bool witness
|
|
1555
|
+
) external {
|
|
1556
|
+
uint256 utxoKey = frauds.unwrapChallenge(
|
|
1557
|
+
walletPublicKey,
|
|
1558
|
+
preimage,
|
|
1559
|
+
witness
|
|
1560
|
+
);
|
|
1561
|
+
|
|
1562
|
+
// Check that the UTXO key identifies a correctly spent UTXO.
|
|
1563
|
+
require(
|
|
1564
|
+
deposits[utxoKey].sweptAt > 0 || spentMainUTXOs[utxoKey],
|
|
1565
|
+
"Spent UTXO not found among correctly spent UTXOs"
|
|
1566
|
+
);
|
|
1567
|
+
|
|
1568
|
+
frauds.defeatChallenge(walletPublicKey, preimage, treasury);
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
/// @notice Notifies about defeat timeout for the given fraud challenge.
|
|
1572
|
+
/// Can be called only if there was a fraud challenge identified by
|
|
1573
|
+
/// the provided `walletPublicKey` and `sighash` and it was not
|
|
1574
|
+
/// defeated on time. The amount of time that needs to pass after
|
|
1575
|
+
/// a fraud challenge is reported is indicated by the
|
|
1576
|
+
/// `challengeDefeatTimeout`. After a successful fraud challenge
|
|
1577
|
+
/// defeat timeout notification the fraud challenge is marked as
|
|
1578
|
+
/// resolved, the stake of each operator is slashed, the ether
|
|
1579
|
+
/// deposited is returned to the challenger and the challenger is
|
|
1580
|
+
/// rewarded.
|
|
1581
|
+
/// @param walletPublicKey The public key of the wallet in the uncompressed
|
|
1582
|
+
/// and unprefixed format (64 bytes)
|
|
1583
|
+
/// @param sighash The hash that was used to produce the ECDSA signature
|
|
1584
|
+
/// that is the subject of the fraud claim. This hash is constructed
|
|
1585
|
+
/// by applying double SHA-256 over a serialized subset of the
|
|
1586
|
+
/// transaction. The exact subset used as hash preimage depends on
|
|
1587
|
+
/// the transaction input the signature is produced for. See BIP-143
|
|
1588
|
+
/// for reference
|
|
1589
|
+
/// @dev Requirements:
|
|
1590
|
+
/// - `walletPublicKey`and `sighash` must identify an open fraud
|
|
1591
|
+
/// challenge
|
|
1592
|
+
/// - the amount of time indicated by `challengeDefeatTimeout` must
|
|
1593
|
+
/// pass after the challenge was reported
|
|
1594
|
+
/// TODO: Consider using wallet public key in the X/Y form to avoid slicing.
|
|
1595
|
+
function notifyFraudChallengeDefeatTimeout(
|
|
1596
|
+
bytes calldata walletPublicKey,
|
|
1597
|
+
bytes32 sighash
|
|
1598
|
+
) external {
|
|
1599
|
+
frauds.notifyFraudChallengeDefeatTimeout(walletPublicKey, sighash);
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
/// @notice Returns parameters used by the `Frauds` library.
|
|
1603
|
+
/// @return slashingAmount Value of the slashing amount
|
|
1604
|
+
/// @return notifierRewardMultiplier Value of the notifier reward multiplier
|
|
1605
|
+
/// @return challengeDefeatTimeout Value of the challenge defeat timeout
|
|
1606
|
+
/// @return challengeDepositAmount Value of the challenge deposit amount
|
|
1607
|
+
function getFraudParameters()
|
|
1608
|
+
external
|
|
1609
|
+
view
|
|
1610
|
+
returns (
|
|
1611
|
+
uint256 slashingAmount,
|
|
1612
|
+
uint256 notifierRewardMultiplier,
|
|
1613
|
+
uint256 challengeDefeatTimeout,
|
|
1614
|
+
uint256 challengeDepositAmount
|
|
1615
|
+
)
|
|
1616
|
+
{
|
|
1617
|
+
slashingAmount = frauds.slashingAmount;
|
|
1618
|
+
notifierRewardMultiplier = frauds.notifierRewardMultiplier;
|
|
1619
|
+
challengeDefeatTimeout = frauds.challengeDefeatTimeout;
|
|
1620
|
+
challengeDepositAmount = frauds.challengeDepositAmount;
|
|
1621
|
+
|
|
1622
|
+
return (
|
|
1623
|
+
slashingAmount,
|
|
1624
|
+
notifierRewardMultiplier,
|
|
1625
|
+
challengeDefeatTimeout,
|
|
1626
|
+
challengeDepositAmount
|
|
1627
|
+
);
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
/// @notice Returns the fraud challenge identified by the given key built
|
|
1631
|
+
/// as keccak256(walletPublicKey|sighash|v|r|s).
|
|
1632
|
+
function fraudChallenges(uint256 challengeKey)
|
|
1633
|
+
external
|
|
1634
|
+
view
|
|
1635
|
+
returns (Frauds.FraudChallenge memory)
|
|
1636
|
+
{
|
|
1637
|
+
return frauds.challenges[challengeKey];
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1260
1640
|
/// @notice Validates whether the redemption Bitcoin transaction input
|
|
1261
1641
|
/// vector contains a single input referring to the wallet's main
|
|
1262
1642
|
/// UTXO. Reverts in case the validation fails.
|
|
@@ -1273,9 +1653,11 @@ contract Bridge is Ownable {
|
|
|
1273
1653
|
bytes memory redemptionTxInputVector,
|
|
1274
1654
|
BitcoinTx.UTXO calldata mainUtxo,
|
|
1275
1655
|
bytes20 walletPubKeyHash
|
|
1276
|
-
) internal
|
|
1656
|
+
) internal {
|
|
1277
1657
|
// Assert that main UTXO for passed wallet exists in storage.
|
|
1278
|
-
bytes32 mainUtxoHash =
|
|
1658
|
+
bytes32 mainUtxoHash = wallets
|
|
1659
|
+
.registeredWallets[walletPubKeyHash]
|
|
1660
|
+
.mainUtxoHash;
|
|
1279
1661
|
require(mainUtxoHash != bytes32(0), "No main UTXO for given wallet");
|
|
1280
1662
|
|
|
1281
1663
|
// Assert that passed main UTXO parameter is the same as in storage and
|
|
@@ -1302,6 +1684,15 @@ contract Bridge is Ownable {
|
|
|
1302
1684
|
mainUtxo.txOutputIndex == redemptionTxOutpointIndex,
|
|
1303
1685
|
"Redemption transaction input must point to the wallet's main UTXO"
|
|
1304
1686
|
);
|
|
1687
|
+
|
|
1688
|
+
// Main UTXO used as an input, mark it as spent.
|
|
1689
|
+
spentMainUTXOs[
|
|
1690
|
+
uint256(
|
|
1691
|
+
keccak256(
|
|
1692
|
+
abi.encodePacked(mainUtxo.txHash, mainUtxo.txOutputIndex)
|
|
1693
|
+
)
|
|
1694
|
+
)
|
|
1695
|
+
] = true;
|
|
1305
1696
|
}
|
|
1306
1697
|
|
|
1307
1698
|
/// @notice Processes the Bitcoin redemption transaction input vector. It
|
|
@@ -1538,7 +1929,7 @@ contract Bridge is Ownable {
|
|
|
1538
1929
|
// by copying the entire `RedemptionRequest` struct there. No need
|
|
1539
1930
|
// to check if `timedOutRedemptions` mapping already contains
|
|
1540
1931
|
// that key because `requestRedemption` blocks requests targeting
|
|
1541
|
-
// non-
|
|
1932
|
+
// non-live wallets. Because `notifyRedemptionTimeout` changes
|
|
1542
1933
|
// wallet state after first call (point 9), there is no possibility
|
|
1543
1934
|
// that the given redemption key could be reported as timed out
|
|
1544
1935
|
// multiple times. At the same time, if the given redemption key
|
|
@@ -1549,83 +1940,6 @@ contract Bridge is Ownable {
|
|
|
1549
1940
|
// 7. Reduce the `pendingRedemptionsValue` (`wallets` mapping) for
|
|
1550
1941
|
// given wallet by request's redeemable amount computed as
|
|
1551
1942
|
// `requestedAmount - treasuryFee`.
|
|
1552
|
-
// 8.
|
|
1553
|
-
//
|
|
1554
|
-
// order to prevent against new redemption requests hitting
|
|
1555
|
-
// that wallet.
|
|
1556
|
-
// 10. Expect the wallet to transfer its funds to another healthy
|
|
1557
|
-
// wallet (just as in case of failed heartbeat). The wallet is
|
|
1558
|
-
// expected to finish the already queued redemption requests
|
|
1559
|
-
// before moving funds but we are not going to enforce it on-chain.
|
|
1560
|
-
|
|
1561
|
-
// TODO: Function `submitRedemptionFraudProof`
|
|
1562
|
-
//
|
|
1563
|
-
// Deposit and redemption fraud proofs are challenging to implement
|
|
1564
|
-
// and it may happen we will have to rely on the coverage pool
|
|
1565
|
-
// (https://github.com/keep-network/coverage-pools) and DAO to
|
|
1566
|
-
// reimburse unlucky depositors and bring back the balance to the
|
|
1567
|
-
// system in case of a wallet fraud by liquidating a part of the
|
|
1568
|
-
// coverage pool manually.
|
|
1569
|
-
//
|
|
1570
|
-
// The probability of 51-of-100 wallet being fraudulent is negligible:
|
|
1571
|
-
// https://github.com/keep-network/tbtc-v2/blob/main/docs/rfc/rfc-2.adoc#111-group-size-and-threshold
|
|
1572
|
-
// and the coverage pool would be there to bring the balance back in
|
|
1573
|
-
// case we are unlucky and malicious wallet emerges.
|
|
1574
|
-
//
|
|
1575
|
-
// We do not want to slash for a misbehavior that is not provable
|
|
1576
|
-
// on-chain and it is possible to construct such a Bitcoin transaction
|
|
1577
|
-
// that is not provable on Ethereum, see
|
|
1578
|
-
// https://consensys.net/diligence/blog/2020/05/tbtc-navigating-the-cross-chain-conundrum
|
|
1579
|
-
//
|
|
1580
|
-
// The algorithm described below assumes we will be able to prove the
|
|
1581
|
-
// TX on Ethereum which may not always be the case. Consider the steps
|
|
1582
|
-
// below as an idea, and not necessarily how this function will be
|
|
1583
|
-
// implemented because it may happen this function will never be
|
|
1584
|
-
// implemented, given the Bitcoin transaction size problems.
|
|
1585
|
-
//
|
|
1586
|
-
// The algorithm:
|
|
1587
|
-
// 1. Take a `BitcoinTx.Info` and `BitcoinTx.Proof` of the
|
|
1588
|
-
// fraudulent transaction. It should also accept `walletPubKeyHash`
|
|
1589
|
-
// and index of fraudulent output. Probably index of fraudulent
|
|
1590
|
-
// input will be also required if the transaction is supposed
|
|
1591
|
-
// to have a bad input vector.
|
|
1592
|
-
// 2. Perform SPV proof to make sure it occurred on Bitcoin chain.
|
|
1593
|
-
// If not - revert.
|
|
1594
|
-
// 3. Check if wallet state is Active or MovingFunds. If not, revert.
|
|
1595
|
-
// 4. Validate the number of inputs. If there is one input and it
|
|
1596
|
-
// points to the wallet's main UTXO - move to point 5. If there
|
|
1597
|
-
// are multiple inputs and there is wallet's main UTXO in the set,
|
|
1598
|
-
// check if this is a sweep transaction. If it's not a sweep,
|
|
1599
|
-
// consider it as fraudulent and move to point 6.
|
|
1600
|
-
// In all other cases revert the call.
|
|
1601
|
-
// 5. Extract the output and analyze its type. The output is not
|
|
1602
|
-
// a fraud and the call should be reverted ONLY IF one of the
|
|
1603
|
-
// following conditions is true:
|
|
1604
|
-
// - Output is a requested redemption held by `pendingRedemptions`
|
|
1605
|
-
// and output value fulfills the request range. There is an
|
|
1606
|
-
// open question if a misfunded request should be removed
|
|
1607
|
-
// from `pendingRedemptions` (probably yes) and whether the
|
|
1608
|
-
// redeemer should be reimbursed in case of an underfund.
|
|
1609
|
-
// - Output is a timed out redemption held by `timedOutRedemptions`
|
|
1610
|
-
// and output value fulfills the request range.
|
|
1611
|
-
// - Output is a proper change i.e. a single output targeting
|
|
1612
|
-
// the wallet PKH back and having a non-zero value.
|
|
1613
|
-
// - Wallet is in MovingFunds state, the output points to the
|
|
1614
|
-
// expected target wallet, have non-zero value, and is a single
|
|
1615
|
-
// output in the vector.
|
|
1616
|
-
// In all other cases consider the transaction as fraud and
|
|
1617
|
-
// proceed to point 6.
|
|
1618
|
-
// 6. Punish the wallet, probably by severely slashing its operators.
|
|
1619
|
-
// 7. Change wallet's state in `wallets` mapping to `Terminated` in
|
|
1620
|
-
// order to prevent against new redemption requests hitting
|
|
1621
|
-
// that wallet. This also prevents against reporting a fraud
|
|
1622
|
-
// multiple times for one transaction (see point 3) and blocks
|
|
1623
|
-
// submission of sweep and redemption proofs. `Terminated` wallet
|
|
1624
|
-
// is blocked in the Bridge forever. If the fraud was a mistake
|
|
1625
|
-
// done by the wallet and the wallet is still honest deep in its
|
|
1626
|
-
// heart, the wallet can coordinate off-chain to recover the BTC
|
|
1627
|
-
// and donate it to another wallet. If they recover all of the
|
|
1628
|
-
// remaining BTC, DAO might decide to reward them with tokens so
|
|
1629
|
-
// that they can have at least some portion of their slashed
|
|
1630
|
-
// tokens back.
|
|
1943
|
+
// 8. Call `wallets.notifyRedemptionTimedOut` to propagate timeout
|
|
1944
|
+
// consequences to the wallet.
|
|
1631
1945
|
}
|