@keep-network/tbtc-v2 0.1.1-dev.25 → 0.1.1-dev.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/artifacts/TBTC.json +10 -10
- package/artifacts/TBTCToken.json +10 -10
- package/artifacts/VendingMachine.json +11 -11
- package/artifacts/solcInputs/{7823c0ea8f2f46d2393b3380efff7ecb.json → 0b49a4d3512802a64309adfce0fc83c4.json} +10 -4
- 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 +421 -24
- package/build/contracts/bridge/Bridge.sol/IRelay.dbg.json +1 -1
- package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.dbg.json +1 -1
- 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 +1 -1
- package/build/contracts/bridge/Wallets.sol/Wallets.json +2 -2
- package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
- package/build/contracts/vault/IVault.sol/IVault.dbg.json +1 -1
- package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +1 -1
- package/contracts/bridge/BitcoinTx.sol +10 -0
- package/contracts/bridge/Bridge.sol +333 -28
- package/contracts/bridge/Frauds.sol +531 -0
- package/contracts/bridge/Wallets.sol +4 -3
- package/package.json +1 -1
|
@@ -25,6 +25,7 @@ import "../bank/Bank.sol";
|
|
|
25
25
|
import "./BitcoinTx.sol";
|
|
26
26
|
import "./EcdsaLib.sol";
|
|
27
27
|
import "./Wallets.sol";
|
|
28
|
+
import "./Frauds.sol";
|
|
28
29
|
|
|
29
30
|
/// @title Interface for the Bitcoin relay
|
|
30
31
|
/// @notice Contains only the methods needed by tBTC v2. The Bitcoin relay
|
|
@@ -61,6 +62,7 @@ contract Bridge is Ownable, EcdsaWalletOwner {
|
|
|
61
62
|
using BTCUtils for bytes;
|
|
62
63
|
using BTCUtils for uint256;
|
|
63
64
|
using BytesLib for bytes;
|
|
65
|
+
using Frauds for Frauds.Data;
|
|
64
66
|
using Wallets for Wallets.Data;
|
|
65
67
|
|
|
66
68
|
/// @notice Represents data which must be revealed by the depositor during
|
|
@@ -261,6 +263,16 @@ contract Bridge is Ownable, EcdsaWalletOwner {
|
|
|
261
263
|
/// validating them before attempting to execute a sweep.
|
|
262
264
|
mapping(uint256 => DepositRequest) public deposits;
|
|
263
265
|
|
|
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;
|
|
275
|
+
|
|
264
276
|
/// @notice Collection of all pending redemption requests indexed by
|
|
265
277
|
/// redemption key built as
|
|
266
278
|
/// keccak256(walletPubKeyHash | redeemerOutputScript). The
|
|
@@ -290,11 +302,12 @@ contract Bridge is Ownable, EcdsaWalletOwner {
|
|
|
290
302
|
/// - `notifyRedemptionTimeout` which puts the redemption key
|
|
291
303
|
/// to this mapping basing on a timed out request stored
|
|
292
304
|
/// previously in `pendingRedemptions` mapping.
|
|
293
|
-
///
|
|
294
|
-
// TODO: Remove that Slither disable once this variable is used.
|
|
295
|
-
// slither-disable-next-line uninitialized-state
|
|
296
305
|
mapping(uint256 => RedemptionRequest) public timedOutRedemptions;
|
|
297
306
|
|
|
307
|
+
/// @notice Contains parameters related to frauds and the collection of all
|
|
308
|
+
/// submitted fraud challenges.
|
|
309
|
+
Frauds.Data internal frauds;
|
|
310
|
+
|
|
298
311
|
/// @notice State related with wallets.
|
|
299
312
|
Wallets.Data internal wallets;
|
|
300
313
|
|
|
@@ -331,6 +344,20 @@ contract Bridge is Ownable, EcdsaWalletOwner {
|
|
|
331
344
|
|
|
332
345
|
event VaultStatusUpdated(address indexed vault, bool isTrusted);
|
|
333
346
|
|
|
347
|
+
event FraudSlashingAmountUpdated(uint256 newFraudSlashingAmount);
|
|
348
|
+
|
|
349
|
+
event FraudNotifierRewardMultiplierUpdated(
|
|
350
|
+
uint256 newFraudNotifierRewardMultiplier
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
event FraudChallengeDefeatTimeoutUpdated(
|
|
354
|
+
uint256 newFraudChallengeDefeatTimeout
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
event FraudChallengeDepositAmountUpdated(
|
|
358
|
+
uint256 newFraudChallengeDepositAmount
|
|
359
|
+
);
|
|
360
|
+
|
|
334
361
|
event DepositRevealed(
|
|
335
362
|
bytes32 fundingTxHash,
|
|
336
363
|
uint32 fundingOutputIndex,
|
|
@@ -359,6 +386,26 @@ contract Bridge is Ownable, EcdsaWalletOwner {
|
|
|
359
386
|
bytes32 redemptionTxHash
|
|
360
387
|
);
|
|
361
388
|
|
|
389
|
+
event RedemptionTimedOut(
|
|
390
|
+
bytes20 walletPubKeyHash,
|
|
391
|
+
bytes redeemerOutputScript
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
event FraudChallengeSubmitted(
|
|
395
|
+
bytes20 walletPublicKeyHash,
|
|
396
|
+
bytes32 sighash,
|
|
397
|
+
uint8 v,
|
|
398
|
+
bytes32 r,
|
|
399
|
+
bytes32 s
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
event FraudChallengeDefeated(bytes20 walletPublicKeyHash, bytes32 sighash);
|
|
403
|
+
|
|
404
|
+
event FraudChallengeDefeatTimedOut(
|
|
405
|
+
bytes20 walletPublicKeyHash,
|
|
406
|
+
bytes32 sighash
|
|
407
|
+
);
|
|
408
|
+
|
|
362
409
|
constructor(
|
|
363
410
|
address _bank,
|
|
364
411
|
address _relay,
|
|
@@ -385,6 +432,10 @@ contract Bridge is Ownable, EcdsaWalletOwner {
|
|
|
385
432
|
redemptionTreasuryFeeDivisor = 2000; // 1/2000 == 5bps == 0.05% == 0.0005
|
|
386
433
|
redemptionTxMaxFee = 1000; // 1000 satoshi
|
|
387
434
|
redemptionTimeout = 172800; // 48 hours
|
|
435
|
+
frauds.setSlashingAmount(10000 * 1e18); // 10000 T
|
|
436
|
+
frauds.setNotifierRewardMultiplier(100); // 100%
|
|
437
|
+
frauds.setChallengeDefeatTimeout(7 days);
|
|
438
|
+
frauds.setChallengeDepositAmount(2 ether);
|
|
388
439
|
|
|
389
440
|
// TODO: Revisit initial values.
|
|
390
441
|
wallets.init(_ecdsaWalletRegistry);
|
|
@@ -1033,6 +1084,15 @@ contract Bridge is Ownable, EcdsaWalletOwner {
|
|
|
1033
1084
|
// the expected main UTXO.
|
|
1034
1085
|
info.inputsTotalValue += mainUtxo.txOutputValue;
|
|
1035
1086
|
mainUtxoFound = true;
|
|
1087
|
+
|
|
1088
|
+
// Main UTXO used as an input, mark it as spent.
|
|
1089
|
+
spentMainUTXOs[
|
|
1090
|
+
uint256(
|
|
1091
|
+
keccak256(
|
|
1092
|
+
abi.encodePacked(outpointTxHash, outpointIndex)
|
|
1093
|
+
)
|
|
1094
|
+
)
|
|
1095
|
+
] = true;
|
|
1036
1096
|
} else {
|
|
1037
1097
|
revert("Unknown input type");
|
|
1038
1098
|
}
|
|
@@ -1398,6 +1458,187 @@ contract Bridge is Ownable, EcdsaWalletOwner {
|
|
|
1398
1458
|
bank.transferBalance(treasury, outputsInfo.totalTreasuryFee);
|
|
1399
1459
|
}
|
|
1400
1460
|
|
|
1461
|
+
/// @notice Submits a fraud challenge indicating that a UTXO being under
|
|
1462
|
+
/// wallet control was unlocked by the wallet but was not used
|
|
1463
|
+
/// according to the protocol rules. That means the wallet signed
|
|
1464
|
+
/// a transaction input pointing to that UTXO and there is a unique
|
|
1465
|
+
/// sighash and signature pair associated with that input. This
|
|
1466
|
+
/// function uses those parameters to create a fraud accusation that
|
|
1467
|
+
/// proves a given transaction input unlocking the given UTXO was
|
|
1468
|
+
/// actually signed by the wallet. This function cannot determine
|
|
1469
|
+
/// whether the transaction was actually broadcast and the input was
|
|
1470
|
+
/// consumed in a fraudulent way so it just opens a challenge period
|
|
1471
|
+
/// during which the wallet can defeat the challenge by submitting
|
|
1472
|
+
/// proof of a transaction that consumes the given input according
|
|
1473
|
+
/// to protocol rules. To prevent spurious allegations, the caller
|
|
1474
|
+
/// must deposit ETH that is returned back upon justified fraud
|
|
1475
|
+
/// challenge or confiscated otherwise.
|
|
1476
|
+
/// @param walletPublicKey The public key of the wallet in the uncompressed
|
|
1477
|
+
/// and unprefixed format (64 bytes)
|
|
1478
|
+
/// @param sighash The hash that was used to produce the ECDSA signature
|
|
1479
|
+
/// that is the subject of the fraud claim. This hash is constructed
|
|
1480
|
+
/// by applying double SHA-256 over a serialized subset of the
|
|
1481
|
+
/// transaction. The exact subset used as hash preimage depends on
|
|
1482
|
+
/// the transaction input the signature is produced for. See BIP-143
|
|
1483
|
+
/// for reference
|
|
1484
|
+
/// @param signature Bitcoin signature in the R/S/V format
|
|
1485
|
+
/// @dev Requirements:
|
|
1486
|
+
/// - Wallet behind `walletPubKey` must be in `Live` or `MovingFunds`
|
|
1487
|
+
/// state
|
|
1488
|
+
/// - The challenger must send appropriate amount of ETH used as
|
|
1489
|
+
/// fraud challenge deposit
|
|
1490
|
+
/// - The signature (represented by r, s and v) must be generated by
|
|
1491
|
+
/// the wallet behind `walletPubKey` during signing of `sighash`
|
|
1492
|
+
/// - Wallet can be challenged for the given signature only once
|
|
1493
|
+
/// TODO: Consider using wallet public key in the X/Y form to avoid slicing.
|
|
1494
|
+
function submitFraudChallenge(
|
|
1495
|
+
bytes calldata walletPublicKey,
|
|
1496
|
+
bytes32 sighash,
|
|
1497
|
+
BitcoinTx.RSVSignature calldata signature
|
|
1498
|
+
) external payable {
|
|
1499
|
+
bytes memory compressedWalletPublicKey = EcdsaLib.compressPublicKey(
|
|
1500
|
+
walletPublicKey.slice32(0),
|
|
1501
|
+
walletPublicKey.slice32(32)
|
|
1502
|
+
);
|
|
1503
|
+
bytes20 walletPubKeyHash = compressedWalletPublicKey.hash160View();
|
|
1504
|
+
|
|
1505
|
+
Wallets.Wallet storage wallet = wallets.registeredWallets[
|
|
1506
|
+
walletPubKeyHash
|
|
1507
|
+
];
|
|
1508
|
+
|
|
1509
|
+
require(
|
|
1510
|
+
wallet.state == Wallets.WalletState.Live ||
|
|
1511
|
+
wallet.state == Wallets.WalletState.MovingFunds,
|
|
1512
|
+
"Wallet is neither in Live nor MovingFunds state"
|
|
1513
|
+
);
|
|
1514
|
+
|
|
1515
|
+
frauds.submitFraudChallenge(
|
|
1516
|
+
walletPublicKey,
|
|
1517
|
+
walletPubKeyHash,
|
|
1518
|
+
sighash,
|
|
1519
|
+
signature
|
|
1520
|
+
);
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
/// @notice Allows to defeat a pending fraud challenge against a wallet if
|
|
1524
|
+
/// the transaction that spends the UTXO follows the protocol rules.
|
|
1525
|
+
/// In order to defeat the challenge the same `walletPublicKey` and
|
|
1526
|
+
/// signature (represented by `r`, `s` and `v`) must be provided as
|
|
1527
|
+
/// were used in the fraud challenge. Additionally a preimage must
|
|
1528
|
+
/// be provided which was used to calculate the sighash during input
|
|
1529
|
+
/// signing. The fraud challenge defeat attempt will only succeed if
|
|
1530
|
+
/// the inputs in the preimage are considered honestly spent by the
|
|
1531
|
+
/// wallet. Therefore the transaction spending the UTXO must be
|
|
1532
|
+
/// proven in the Bridge before a challenge defeat is called.
|
|
1533
|
+
/// If successfully defeated, the fraud challenge is marked as
|
|
1534
|
+
/// resolved and the amount of ether deposited by the challenger is
|
|
1535
|
+
/// sent to the treasury.
|
|
1536
|
+
/// @param walletPublicKey The public key of the wallet in the uncompressed
|
|
1537
|
+
/// and unprefixed format (64 bytes)
|
|
1538
|
+
/// @param preimage The preimage which produces sighash used to generate the
|
|
1539
|
+
/// ECDSA signature that is the subject of the fraud claim. It is a
|
|
1540
|
+
/// serialized subset of the transaction. The exact subset used as
|
|
1541
|
+
/// the preimage depends on the transaction input the signature is
|
|
1542
|
+
/// produced for. See BIP-143 for reference
|
|
1543
|
+
/// @param witness Flag indicating whether the preimage was produced for a
|
|
1544
|
+
/// witness input. True for witness, false for non-witness input
|
|
1545
|
+
/// @dev Requirements:
|
|
1546
|
+
/// - `walletPublicKey` and `sighash` calculated as `hash256(preimage)`
|
|
1547
|
+
/// must identify an open fraud challenge
|
|
1548
|
+
/// - the preimage must be a valid preimage of a transaction generated
|
|
1549
|
+
/// according to the protocol rules and already proved in the Bridge
|
|
1550
|
+
/// - before a defeat attempt is made the transaction that spends the
|
|
1551
|
+
/// given UTXO must be proven in the Bridge
|
|
1552
|
+
/// TODO: Consider using wallet public key in the X/Y form to avoid slicing.
|
|
1553
|
+
function defeatFraudChallenge(
|
|
1554
|
+
bytes calldata walletPublicKey,
|
|
1555
|
+
bytes calldata preimage,
|
|
1556
|
+
bool witness
|
|
1557
|
+
) external {
|
|
1558
|
+
uint256 utxoKey = frauds.unwrapChallenge(
|
|
1559
|
+
walletPublicKey,
|
|
1560
|
+
preimage,
|
|
1561
|
+
witness
|
|
1562
|
+
);
|
|
1563
|
+
|
|
1564
|
+
// Check that the UTXO key identifies a correctly spent UTXO.
|
|
1565
|
+
require(
|
|
1566
|
+
deposits[utxoKey].sweptAt > 0 || spentMainUTXOs[utxoKey],
|
|
1567
|
+
"Spent UTXO not found among correctly spent UTXOs"
|
|
1568
|
+
);
|
|
1569
|
+
|
|
1570
|
+
frauds.defeatChallenge(walletPublicKey, preimage, treasury);
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
/// @notice Notifies about defeat timeout for the given fraud challenge.
|
|
1574
|
+
/// Can be called only if there was a fraud challenge identified by
|
|
1575
|
+
/// the provided `walletPublicKey` and `sighash` and it was not
|
|
1576
|
+
/// defeated on time. The amount of time that needs to pass after
|
|
1577
|
+
/// a fraud challenge is reported is indicated by the
|
|
1578
|
+
/// `challengeDefeatTimeout`. After a successful fraud challenge
|
|
1579
|
+
/// defeat timeout notification the fraud challenge is marked as
|
|
1580
|
+
/// resolved, the stake of each operator is slashed, the ether
|
|
1581
|
+
/// deposited is returned to the challenger and the challenger is
|
|
1582
|
+
/// rewarded.
|
|
1583
|
+
/// @param walletPublicKey The public key of the wallet in the uncompressed
|
|
1584
|
+
/// and unprefixed format (64 bytes)
|
|
1585
|
+
/// @param sighash The hash that was used to produce the ECDSA signature
|
|
1586
|
+
/// that is the subject of the fraud claim. This hash is constructed
|
|
1587
|
+
/// by applying double SHA-256 over a serialized subset of the
|
|
1588
|
+
/// transaction. The exact subset used as hash preimage depends on
|
|
1589
|
+
/// the transaction input the signature is produced for. See BIP-143
|
|
1590
|
+
/// for reference
|
|
1591
|
+
/// @dev Requirements:
|
|
1592
|
+
/// - `walletPublicKey`and `sighash` must identify an open fraud
|
|
1593
|
+
/// challenge
|
|
1594
|
+
/// - the amount of time indicated by `challengeDefeatTimeout` must
|
|
1595
|
+
/// pass after the challenge was reported
|
|
1596
|
+
/// TODO: Consider using wallet public key in the X/Y form to avoid slicing.
|
|
1597
|
+
function notifyFraudChallengeDefeatTimeout(
|
|
1598
|
+
bytes calldata walletPublicKey,
|
|
1599
|
+
bytes32 sighash
|
|
1600
|
+
) external {
|
|
1601
|
+
frauds.notifyFraudChallengeDefeatTimeout(walletPublicKey, sighash);
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
/// @notice Returns parameters used by the `Frauds` library.
|
|
1605
|
+
/// @return slashingAmount Value of the slashing amount
|
|
1606
|
+
/// @return notifierRewardMultiplier Value of the notifier reward multiplier
|
|
1607
|
+
/// @return challengeDefeatTimeout Value of the challenge defeat timeout
|
|
1608
|
+
/// @return challengeDepositAmount Value of the challenge deposit amount
|
|
1609
|
+
function getFraudParameters()
|
|
1610
|
+
external
|
|
1611
|
+
view
|
|
1612
|
+
returns (
|
|
1613
|
+
uint256 slashingAmount,
|
|
1614
|
+
uint256 notifierRewardMultiplier,
|
|
1615
|
+
uint256 challengeDefeatTimeout,
|
|
1616
|
+
uint256 challengeDepositAmount
|
|
1617
|
+
)
|
|
1618
|
+
{
|
|
1619
|
+
slashingAmount = frauds.slashingAmount;
|
|
1620
|
+
notifierRewardMultiplier = frauds.notifierRewardMultiplier;
|
|
1621
|
+
challengeDefeatTimeout = frauds.challengeDefeatTimeout;
|
|
1622
|
+
challengeDepositAmount = frauds.challengeDepositAmount;
|
|
1623
|
+
|
|
1624
|
+
return (
|
|
1625
|
+
slashingAmount,
|
|
1626
|
+
notifierRewardMultiplier,
|
|
1627
|
+
challengeDefeatTimeout,
|
|
1628
|
+
challengeDepositAmount
|
|
1629
|
+
);
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
/// @notice Returns the fraud challenge identified by the given key built
|
|
1633
|
+
/// as keccak256(walletPublicKey|sighash|v|r|s).
|
|
1634
|
+
function fraudChallenges(uint256 challengeKey)
|
|
1635
|
+
external
|
|
1636
|
+
view
|
|
1637
|
+
returns (Frauds.FraudChallenge memory)
|
|
1638
|
+
{
|
|
1639
|
+
return frauds.challenges[challengeKey];
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1401
1642
|
/// @notice Validates whether the redemption Bitcoin transaction input
|
|
1402
1643
|
/// vector contains a single input referring to the wallet's main
|
|
1403
1644
|
/// UTXO. Reverts in case the validation fails.
|
|
@@ -1414,7 +1655,7 @@ contract Bridge is Ownable, EcdsaWalletOwner {
|
|
|
1414
1655
|
bytes memory redemptionTxInputVector,
|
|
1415
1656
|
BitcoinTx.UTXO calldata mainUtxo,
|
|
1416
1657
|
bytes20 walletPubKeyHash
|
|
1417
|
-
) internal
|
|
1658
|
+
) internal {
|
|
1418
1659
|
// Assert that main UTXO for passed wallet exists in storage.
|
|
1419
1660
|
bytes32 mainUtxoHash = wallets
|
|
1420
1661
|
.registeredWallets[walletPubKeyHash]
|
|
@@ -1445,6 +1686,15 @@ contract Bridge is Ownable, EcdsaWalletOwner {
|
|
|
1445
1686
|
mainUtxo.txOutputIndex == redemptionTxOutpointIndex,
|
|
1446
1687
|
"Redemption transaction input must point to the wallet's main UTXO"
|
|
1447
1688
|
);
|
|
1689
|
+
|
|
1690
|
+
// Main UTXO used as an input, mark it as spent.
|
|
1691
|
+
spentMainUTXOs[
|
|
1692
|
+
uint256(
|
|
1693
|
+
keccak256(
|
|
1694
|
+
abi.encodePacked(mainUtxo.txHash, mainUtxo.txOutputIndex)
|
|
1695
|
+
)
|
|
1696
|
+
)
|
|
1697
|
+
] = true;
|
|
1448
1698
|
}
|
|
1449
1699
|
|
|
1450
1700
|
/// @notice Processes the Bitcoin redemption transaction input vector. It
|
|
@@ -1670,28 +1920,83 @@ contract Bridge is Ownable, EcdsaWalletOwner {
|
|
|
1670
1920
|
return info;
|
|
1671
1921
|
}
|
|
1672
1922
|
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1923
|
+
/// @notice Notifies that there is a pending redemption request associated
|
|
1924
|
+
/// with the given wallet, that has timed out. The redemption
|
|
1925
|
+
/// request is identified by the key built as
|
|
1926
|
+
/// `keccak256(walletPubKeyHash | redeemerOutputScript)`.
|
|
1927
|
+
/// The results of calling this function: the pending redemptions
|
|
1928
|
+
/// value for the wallet will be decreased by the requested amount
|
|
1929
|
+
/// (minus treasury fee), the tokens taken from the redeemer on
|
|
1930
|
+
/// redemption request will be returned to the redeemer, the request
|
|
1931
|
+
/// will be moved from pending redemptions to timed-out redemptions.
|
|
1932
|
+
/// If the state of the wallet is `Live` or `MovingFunds`, the
|
|
1933
|
+
/// wallet operators will be slashed.
|
|
1934
|
+
/// Additionally, if the state of wallet is `Live`, the wallet will
|
|
1935
|
+
/// be closed or marked as `MovingFunds` (depending on the presence
|
|
1936
|
+
/// or absence of the wallet's main UTXO) and the wallet will no
|
|
1937
|
+
/// longer be marked as the active wallet (if it was marked as such).
|
|
1938
|
+
/// @param walletPubKeyHash 20-byte public key hash of the wallet
|
|
1939
|
+
/// @param redeemerOutputScript The redeemer's length-prefixed output
|
|
1940
|
+
/// script (P2PKH, P2WPKH, P2SH or P2WSH)
|
|
1941
|
+
/// @dev Requirements:
|
|
1942
|
+
/// - The redemption request identified by `walletPubKeyHash` and
|
|
1943
|
+
/// `redeemerOutputScript` must exist
|
|
1944
|
+
/// - The amount of time defined by `redemptionTimeout` must have
|
|
1945
|
+
/// passed since the redemption was requested (the request must be
|
|
1946
|
+
/// timed-out).
|
|
1947
|
+
function notifyRedemptionTimeout(
|
|
1948
|
+
bytes20 walletPubKeyHash,
|
|
1949
|
+
bytes calldata redeemerOutputScript
|
|
1950
|
+
) external {
|
|
1951
|
+
uint256 redemptionKey = uint256(
|
|
1952
|
+
keccak256(abi.encodePacked(walletPubKeyHash, redeemerOutputScript))
|
|
1953
|
+
);
|
|
1954
|
+
RedemptionRequest memory request = pendingRedemptions[redemptionKey];
|
|
1955
|
+
|
|
1956
|
+
require(request.requestedAt > 0, "Redemption request does not exist");
|
|
1957
|
+
require(
|
|
1958
|
+
/* solhint-disable-next-line not-rely-on-time */
|
|
1959
|
+
request.requestedAt + redemptionTimeout < block.timestamp,
|
|
1960
|
+
"Redemption request has not timed out"
|
|
1961
|
+
);
|
|
1962
|
+
|
|
1963
|
+
// Update the wallet's pending redemptions value
|
|
1964
|
+
Wallets.Wallet storage wallet = wallets.registeredWallets[
|
|
1965
|
+
walletPubKeyHash
|
|
1966
|
+
];
|
|
1967
|
+
wallet.pendingRedemptionsValue -=
|
|
1968
|
+
request.requestedAmount -
|
|
1969
|
+
request.treasuryFee;
|
|
1970
|
+
|
|
1971
|
+
require(
|
|
1972
|
+
// TODO: Allow the wallets in `Closing` state when the state is added
|
|
1973
|
+
wallet.state == Wallets.WalletState.Live ||
|
|
1974
|
+
wallet.state == Wallets.WalletState.MovingFunds ||
|
|
1975
|
+
wallet.state == Wallets.WalletState.Terminated,
|
|
1976
|
+
"The wallet must be in Live, MovingFunds or Terminated state"
|
|
1977
|
+
);
|
|
1978
|
+
|
|
1979
|
+
// It is worth noting that there is no need to check if
|
|
1980
|
+
// `timedOutRedemption` mapping already contains the given redemption
|
|
1981
|
+
// key. There is no possibility to re-use a key of a reported timed-out
|
|
1982
|
+
// redemption because the wallet responsible for causing the timeout is
|
|
1983
|
+
// moved to a state that prevents it to receive new redemption requests.
|
|
1984
|
+
|
|
1985
|
+
// Move the redemption from pending redemptions to timed-out redemptions
|
|
1986
|
+
timedOutRedemptions[redemptionKey] = request;
|
|
1987
|
+
delete pendingRedemptions[redemptionKey];
|
|
1988
|
+
|
|
1989
|
+
if (
|
|
1990
|
+
wallet.state == Wallets.WalletState.Live ||
|
|
1991
|
+
wallet.state == Wallets.WalletState.MovingFunds
|
|
1992
|
+
) {
|
|
1993
|
+
// Propagate timeout consequences to the wallet
|
|
1994
|
+
wallets.notifyRedemptionTimedOut(walletPubKeyHash);
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
emit RedemptionTimedOut(walletPubKeyHash, redeemerOutputScript);
|
|
1998
|
+
|
|
1999
|
+
// Return the requested amount of tokens to the redeemer
|
|
2000
|
+
bank.transferBalance(request.redeemer, request.requestedAmount);
|
|
2001
|
+
}
|
|
1697
2002
|
}
|