@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.
Files changed (25) hide show
  1. package/artifacts/TBTC.json +10 -10
  2. package/artifacts/TBTCToken.json +10 -10
  3. package/artifacts/VendingMachine.json +11 -11
  4. package/artifacts/solcInputs/{7823c0ea8f2f46d2393b3380efff7ecb.json → 0b49a4d3512802a64309adfce0fc83c4.json} +10 -4
  5. package/build/contracts/GovernanceUtils.sol/GovernanceUtils.dbg.json +1 -1
  6. package/build/contracts/bank/Bank.sol/Bank.dbg.json +1 -1
  7. package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.dbg.json +1 -1
  8. package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.json +2 -2
  9. package/build/contracts/bridge/Bridge.sol/Bridge.dbg.json +1 -1
  10. package/build/contracts/bridge/Bridge.sol/Bridge.json +421 -24
  11. package/build/contracts/bridge/Bridge.sol/IRelay.dbg.json +1 -1
  12. package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.dbg.json +1 -1
  13. package/build/contracts/bridge/Frauds.sol/Frauds.dbg.json +4 -0
  14. package/build/contracts/bridge/Frauds.sol/Frauds.json +138 -0
  15. package/build/contracts/bridge/VendingMachine.sol/VendingMachine.dbg.json +1 -1
  16. package/build/contracts/bridge/Wallets.sol/Wallets.dbg.json +1 -1
  17. package/build/contracts/bridge/Wallets.sol/Wallets.json +2 -2
  18. package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
  19. package/build/contracts/vault/IVault.sol/IVault.dbg.json +1 -1
  20. package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +1 -1
  21. package/contracts/bridge/BitcoinTx.sol +10 -0
  22. package/contracts/bridge/Bridge.sol +333 -28
  23. package/contracts/bridge/Frauds.sol +531 -0
  24. package/contracts/bridge/Wallets.sol +4 -3
  25. 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 view {
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
- // TODO: Function `notifyRedemptionTimeout. That function must:
1674
- // 1. Take a the `walletPubKey` and `redeemerOutputScript` as params.
1675
- // 2. Build the redemption key using those params.
1676
- // 3. Use the redemption key and take the request from
1677
- // `pendingRedemptions` mapping.
1678
- // 4. If request doesn't exist in mapping - revert.
1679
- // 5. If request exits, and is timed out - remove the redemption key
1680
- // from `pendingRedemptions` and put it to `timedOutRedemptions`
1681
- // by copying the entire `RedemptionRequest` struct there. No need
1682
- // to check if `timedOutRedemptions` mapping already contains
1683
- // that key because `requestRedemption` blocks requests targeting
1684
- // non-live wallets. Because `notifyRedemptionTimeout` changes
1685
- // wallet state after first call (point 9), there is no possibility
1686
- // that the given redemption key could be reported as timed out
1687
- // multiple times. At the same time, if the given redemption key
1688
- // was already marked as fraudulent due to an amount-related fraud,
1689
- // it will not be possible to report a time out on it since it
1690
- // won't be present in `pendingRedemptions` mapping.
1691
- // 6. Return the `requestedAmount` to the `redeemer`.
1692
- // 7. Reduce the `pendingRedemptionsValue` (`wallets` mapping) for
1693
- // given wallet by request's redeemable amount computed as
1694
- // `requestedAmount - treasuryFee`.
1695
- // 8. Call `wallets.notifyRedemptionTimedOut` to propagate timeout
1696
- // consequences to the wallet.
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
  }