@keep-network/tbtc-v2 0.1.1-dev.31 → 0.1.1-dev.34

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 (28) hide show
  1. package/artifacts/TBTC.json +3 -3
  2. package/artifacts/TBTCToken.json +3 -3
  3. package/artifacts/VendingMachine.json +10 -10
  4. package/artifacts/solcInputs/{d9d0873fb324e55f80390065c7fe0dff.json → a67a2c11233ce411fb55a9f369ced323.json} +9 -3
  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/Bridge.sol/Bridge.dbg.json +1 -1
  9. package/build/contracts/bridge/Bridge.sol/Bridge.json +234 -82
  10. package/build/contracts/bridge/Bridge.sol/IRelay.dbg.json +1 -1
  11. package/build/contracts/bridge/BridgeState.sol/BridgeState.dbg.json +4 -0
  12. package/build/contracts/bridge/BridgeState.sol/BridgeState.json +10 -0
  13. package/build/contracts/bridge/Deposit.sol/Deposit.dbg.json +4 -0
  14. package/build/contracts/bridge/Deposit.sol/Deposit.json +72 -0
  15. package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.dbg.json +1 -1
  16. package/build/contracts/bridge/Frauds.sol/Frauds.dbg.json +1 -1
  17. package/build/contracts/bridge/Frauds.sol/Frauds.json +2 -2
  18. package/build/contracts/bridge/VendingMachine.sol/VendingMachine.dbg.json +1 -1
  19. package/build/contracts/bridge/Wallets.sol/Wallets.dbg.json +1 -1
  20. package/build/contracts/bridge/Wallets.sol/Wallets.json +2 -2
  21. package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
  22. package/build/contracts/vault/IVault.sol/IVault.dbg.json +1 -1
  23. package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +1 -1
  24. package/contracts/bridge/Bridge.sol +439 -278
  25. package/contracts/bridge/BridgeState.sol +54 -0
  26. package/contracts/bridge/Deposit.sol +247 -0
  27. package/contracts/bridge/Wallets.sol +89 -19
  28. package/package.json +1 -1
@@ -22,6 +22,7 @@ import {BytesLib} from "@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol";
22
22
  import {IWalletOwner as EcdsaWalletOwner} from "@keep-network/ecdsa/contracts/api/IWalletOwner.sol";
23
23
 
24
24
  import "../bank/Bank.sol";
25
+ import "./BridgeState.sol";
25
26
  import "./BitcoinTx.sol";
26
27
  import "./EcdsaLib.sol";
27
28
  import "./Wallets.sol";
@@ -58,59 +59,20 @@ interface IRelay {
58
59
  /// wallet informs the Bridge about the sweep increasing appropriate
59
60
  /// balances in the Bank.
60
61
  /// @dev Bridge is an upgradeable component of the Bank.
62
+ ///
63
+ /// TODO: All wallets-related operations that are currently done directly
64
+ /// by the Bridge can be probably delegated to the Wallets library.
65
+ /// Examples of such operations are main UTXO or pending redemptions
66
+ /// value updates.
61
67
  contract Bridge is Ownable, EcdsaWalletOwner {
62
- using BTCUtils for bytes;
63
- using BTCUtils for uint256;
64
- using BytesLib for bytes;
68
+ using BridgeState for BridgeState.Storage;
69
+ using Deposit for BridgeState.Storage;
65
70
  using Frauds for Frauds.Data;
66
71
  using Wallets for Wallets.Data;
67
72
 
68
- /// @notice Represents data which must be revealed by the depositor during
69
- /// deposit reveal.
70
- struct RevealInfo {
71
- // Index of the funding output belonging to the funding transaction.
72
- uint32 fundingOutputIndex;
73
- // Ethereum depositor address.
74
- address depositor;
75
- // The blinding factor as 8 bytes. Byte endianness doesn't matter
76
- // as this factor is not interpreted as uint.
77
- bytes8 blindingFactor;
78
- // The compressed Bitcoin public key (33 bytes and 02 or 03 prefix)
79
- // of the deposit's wallet hashed in the HASH160 Bitcoin opcode style.
80
- bytes20 walletPubKeyHash;
81
- // The compressed Bitcoin public key (33 bytes and 02 or 03 prefix)
82
- // that can be used to make the deposit refund after the refund
83
- // locktime passes. Hashed in the HASH160 Bitcoin opcode style.
84
- bytes20 refundPubKeyHash;
85
- // The refund locktime (4-byte LE). Interpreted according to locktime
86
- // parsing rules described in:
87
- // https://developer.bitcoin.org/devguide/transactions.html#locktime-and-sequence-number
88
- // and used with OP_CHECKLOCKTIMEVERIFY opcode as described in:
89
- // https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki
90
- bytes4 refundLocktime;
91
- // Address of the Bank vault to which the deposit is routed to.
92
- // Optional, can be 0x0. The vault must be trusted by the Bridge.
93
- address vault;
94
- }
95
-
96
- /// @notice Represents tBTC deposit data.
97
- struct DepositRequest {
98
- // Ethereum depositor address.
99
- address depositor;
100
- // Deposit amount in satoshi.
101
- uint64 amount;
102
- // UNIX timestamp the deposit was revealed at.
103
- uint32 revealedAt;
104
- // Address of the Bank vault the deposit is routed to.
105
- // Optional, can be 0x0.
106
- address vault;
107
- // Treasury TBTC fee in satoshi at the moment of deposit reveal.
108
- uint64 treasuryFee;
109
- // UNIX timestamp the deposit was swept at. Note this is not the
110
- // time when the deposit was swept on the Bitcoin chain but actually
111
- // the time when the sweep proof was delivered to the Ethereum chain.
112
- uint32 sweptAt;
113
- }
73
+ using BTCUtils for bytes;
74
+ using BTCUtils for uint256;
75
+ using BytesLib for bytes;
114
76
 
115
77
  /// @notice Represents an outcome of the sweep Bitcoin transaction
116
78
  /// inputs processing.
@@ -173,44 +135,29 @@ contract Bridge is Ownable, EcdsaWalletOwner {
173
135
 
174
136
  /// @notice The number of confirmations on the Bitcoin chain required to
175
137
  /// successfully evaluate an SPV proof.
176
- uint256 public immutable txProofDifficultyFactor;
138
+ uint256 public txProofDifficultyFactor;
177
139
 
178
140
  /// TODO: Revisit whether it should be governable or not.
179
141
  /// @notice Address of the Bank this Bridge belongs to.
180
- Bank public immutable bank;
142
+ Bank public bank;
181
143
 
182
144
  /// TODO: Make it governable.
183
145
  /// @notice Handle to the Bitcoin relay.
184
- IRelay public immutable relay;
146
+ IRelay public relay;
185
147
 
186
148
  /// TODO: Revisit whether it should be governable or not.
187
149
  /// @notice Address where the redemptions treasury fees will be sent to.
188
150
  /// Treasury takes part in the operators rewarding process.
189
- address public immutable treasury;
151
+ address public treasury;
190
152
 
191
- /// TODO: Make it governable.
192
- /// @notice The minimal amount that can be requested for deposit.
193
- /// Value of this parameter must take into account the value of
194
- /// `depositTreasuryFeeDivisor` and `depositTxMaxFee`
195
- /// parameters in order to make requests that can incur the
196
- /// treasury and transaction fee and still satisfy the depositor.
197
- uint64 public depositDustThreshold;
198
-
199
- /// TODO: Make it governable.
200
- /// @notice Divisor used to compute the treasury fee taken from each
201
- /// deposit and transferred to the treasury upon sweep proof
202
- /// submission. That fee is computed as follows:
203
- /// `treasuryFee = depositedAmount / depositTreasuryFeeDivisor`
204
- /// For example, if the treasury fee needs to be 2% of each deposit,
205
- /// the `depositTreasuryFeeDivisor` should be set to `50`
206
- /// because `1/50 = 0.02 = 2%`.
207
- uint64 public depositTreasuryFeeDivisor;
153
+ BridgeState.Storage internal self;
208
154
 
209
155
  /// TODO: Make it governable.
210
156
  /// @notice Maximum amount of BTC transaction fee that can be incurred by
211
157
  /// each swept deposit being part of the given sweep
212
158
  /// transaction. If the maximum BTC transaction fee is exceeded,
213
159
  /// such transaction is considered a fraud.
160
+ /// @dev This is a per-deposit input max fee for the sweep transaction.
214
161
  uint64 public depositTxMaxFee;
215
162
 
216
163
  /// TODO: Make it governable.
@@ -236,6 +183,7 @@ contract Bridge is Ownable, EcdsaWalletOwner {
236
183
  /// each redemption request being part of the given redemption
237
184
  /// transaction. If the maximum BTC transaction fee is exceeded, such
238
185
  /// transaction is considered a fraud.
186
+ /// @dev This is a per-redemption output max fee for the redemption transaction.
239
187
  uint64 public redemptionTxMaxFee;
240
188
 
241
189
  /// TODO: Make it governable.
@@ -246,25 +194,15 @@ contract Bridge is Ownable, EcdsaWalletOwner {
246
194
  /// to the redeemer in full amount.
247
195
  uint256 public redemptionTimeout;
248
196
 
249
- /// @notice Indicates if the vault with the given address is trusted or not.
250
- /// Depositors can route their revealed deposits only to trusted
251
- /// vaults and have trusted vaults notified about new deposits as
252
- /// soon as these deposits get swept. Vaults not trusted by the
253
- /// Bridge can still be used by Bank balance owners on their own
254
- /// responsibility - anyone can approve their Bank balance to any
255
- /// address.
256
- mapping(address => bool) public isVaultTrusted;
257
-
258
- /// @notice Collection of all revealed deposits indexed by
259
- /// keccak256(fundingTxHash | fundingOutputIndex).
260
- /// The fundingTxHash is bytes32 (ordered as in Bitcoin internally)
261
- /// and fundingOutputIndex an uint32. This mapping may contain valid
262
- /// and invalid deposits and the wallet is responsible for
263
- /// validating them before attempting to execute a sweep.
264
- mapping(uint256 => DepositRequest) public deposits;
197
+ /// TODO: Make it governable.
198
+ /// @notice Maximum amount of the total BTC transaction fee that is
199
+ /// acceptable in a single moving funds transaction.
200
+ /// @dev This is a TOTAL max fee for the moving funds transaction. Note that
201
+ /// `depositTxMaxFee` is per single deposit and `redemptionTxMaxFee`
202
+ /// if per single redemption. `movingFundsTxMaxTotalFee` is a total fee
203
+ /// for the entire transaction.
204
+ uint64 public movingFundsTxMaxTotalFee;
265
205
 
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
206
  /// @notice Collection of main UTXOs that are honestly spent indexed by
269
207
  /// keccak256(fundingTxHash | fundingOutputIndex). The fundingTxHash
270
208
  /// is bytes32 (ordered as in Bitcoin internally) and
@@ -406,6 +344,11 @@ contract Bridge is Ownable, EcdsaWalletOwner {
406
344
  bytes32 sighash
407
345
  );
408
346
 
347
+ event MovingFundsCompleted(
348
+ bytes20 walletPubKeyHash,
349
+ bytes32 movingFundsTxHash
350
+ );
351
+
409
352
  constructor(
410
353
  address _bank,
411
354
  address _relay,
@@ -425,13 +368,16 @@ contract Bridge is Ownable, EcdsaWalletOwner {
425
368
  txProofDifficultyFactor = _txProofDifficultyFactor;
426
369
 
427
370
  // TODO: Revisit initial values.
428
- depositDustThreshold = 1000000; // 1000000 satoshi = 0.01 BTC
429
- depositTxMaxFee = 1000; // 1000 satoshi
430
- depositTreasuryFeeDivisor = 2000; // 1/2000 == 5bps == 0.05% == 0.0005
371
+ self.depositDustThreshold = 1000000; // 1000000 satoshi = 0.01 BTC
372
+ depositTxMaxFee = 10000; // 10000 satoshi
373
+ self.depositTreasuryFeeDivisor = 2000; // 1/2000 == 5bps == 0.05% == 0.0005
431
374
  redemptionDustThreshold = 1000000; // 1000000 satoshi = 0.01 BTC
432
375
  redemptionTreasuryFeeDivisor = 2000; // 1/2000 == 5bps == 0.05% == 0.0005
433
- redemptionTxMaxFee = 1000; // 1000 satoshi
376
+ redemptionTxMaxFee = 10000; // 10000 satoshi
434
377
  redemptionTimeout = 172800; // 48 hours
378
+ movingFundsTxMaxTotalFee = 10000; // 10000 satoshi
379
+
380
+ // TODO: Revisit initial values.
435
381
  frauds.setSlashingAmount(10000 * 1e18); // 10000 T
436
382
  frauds.setNotifierRewardMultiplier(100); // 100%
437
383
  frauds.setChallengeDefeatTimeout(7 days);
@@ -500,7 +446,7 @@ contract Bridge is Ownable, EcdsaWalletOwner {
500
446
  /// @param isTrusted flag indicating whether the vault is trusted or not
501
447
  /// @dev Can only be called by the Governance.
502
448
  function setVaultStatus(address vault, bool isTrusted) external onlyOwner {
503
- isVaultTrusted[vault] = isTrusted;
449
+ self.isVaultTrusted[vault] = isTrusted;
504
450
  emit VaultStatusUpdated(vault, isTrusted);
505
451
  }
506
452
 
@@ -655,124 +601,9 @@ contract Bridge is Ownable, EcdsaWalletOwner {
655
601
  /// deposit script unlocks to receive their BTC back.
656
602
  function revealDeposit(
657
603
  BitcoinTx.Info calldata fundingTx,
658
- RevealInfo calldata reveal
604
+ Deposit.RevealInfo calldata reveal
659
605
  ) external {
660
- require(
661
- wallets.registeredWallets[reveal.walletPubKeyHash].state ==
662
- Wallets.WalletState.Live,
663
- "Wallet is not in Live state"
664
- );
665
-
666
- require(
667
- reveal.vault == address(0) || isVaultTrusted[reveal.vault],
668
- "Vault is not trusted"
669
- );
670
-
671
- // TODO: Should we enforce a specific locktime at contract level?
672
-
673
- bytes memory expectedScript = abi.encodePacked(
674
- hex"14", // Byte length of depositor Ethereum address.
675
- reveal.depositor,
676
- hex"75", // OP_DROP
677
- hex"08", // Byte length of blinding factor value.
678
- reveal.blindingFactor,
679
- hex"75", // OP_DROP
680
- hex"76", // OP_DUP
681
- hex"a9", // OP_HASH160
682
- hex"14", // Byte length of a compressed Bitcoin public key hash.
683
- reveal.walletPubKeyHash,
684
- hex"87", // OP_EQUAL
685
- hex"63", // OP_IF
686
- hex"ac", // OP_CHECKSIG
687
- hex"67", // OP_ELSE
688
- hex"76", // OP_DUP
689
- hex"a9", // OP_HASH160
690
- hex"14", // Byte length of a compressed Bitcoin public key hash.
691
- reveal.refundPubKeyHash,
692
- hex"88", // OP_EQUALVERIFY
693
- hex"04", // Byte length of refund locktime value.
694
- reveal.refundLocktime,
695
- hex"b1", // OP_CHECKLOCKTIMEVERIFY
696
- hex"75", // OP_DROP
697
- hex"ac", // OP_CHECKSIG
698
- hex"68" // OP_ENDIF
699
- );
700
-
701
- bytes memory fundingOutput = fundingTx
702
- .outputVector
703
- .extractOutputAtIndex(reveal.fundingOutputIndex);
704
- bytes memory fundingOutputHash = fundingOutput.extractHash();
705
-
706
- if (fundingOutputHash.length == 20) {
707
- // A 20-byte output hash is used by P2SH. That hash is constructed
708
- // by applying OP_HASH160 on the locking script. A 20-byte output
709
- // hash is used as well by P2PKH and P2WPKH (OP_HASH160 on the
710
- // public key). However, since we compare the actual output hash
711
- // with an expected locking script hash, this check will succeed only
712
- // for P2SH transaction type with expected script hash value. For
713
- // P2PKH and P2WPKH, it will fail on the output hash comparison with
714
- // the expected locking script hash.
715
- require(
716
- fundingOutputHash.slice20(0) == expectedScript.hash160View(),
717
- "Wrong 20-byte script hash"
718
- );
719
- } else if (fundingOutputHash.length == 32) {
720
- // A 32-byte output hash is used by P2WSH. That hash is constructed
721
- // by applying OP_SHA256 on the locking script.
722
- require(
723
- fundingOutputHash.toBytes32() == sha256(expectedScript),
724
- "Wrong 32-byte script hash"
725
- );
726
- } else {
727
- revert("Wrong script hash length");
728
- }
729
-
730
- // Resulting TX hash is in native Bitcoin little-endian format.
731
- bytes32 fundingTxHash = abi
732
- .encodePacked(
733
- fundingTx.version,
734
- fundingTx.inputVector,
735
- fundingTx.outputVector,
736
- fundingTx.locktime
737
- )
738
- .hash256View();
739
-
740
- DepositRequest storage deposit = deposits[
741
- uint256(
742
- keccak256(
743
- abi.encodePacked(fundingTxHash, reveal.fundingOutputIndex)
744
- )
745
- )
746
- ];
747
- require(deposit.revealedAt == 0, "Deposit already revealed");
748
-
749
- uint64 fundingOutputAmount = fundingOutput.extractValue();
750
-
751
- require(
752
- fundingOutputAmount >= depositDustThreshold,
753
- "Deposit amount too small"
754
- );
755
-
756
- deposit.amount = fundingOutputAmount;
757
- deposit.depositor = reveal.depositor;
758
- /* solhint-disable-next-line not-rely-on-time */
759
- deposit.revealedAt = uint32(block.timestamp);
760
- deposit.vault = reveal.vault;
761
- deposit.treasuryFee = depositTreasuryFeeDivisor > 0
762
- ? fundingOutputAmount / depositTreasuryFeeDivisor
763
- : 0;
764
-
765
- emit DepositRevealed(
766
- fundingTxHash,
767
- reveal.fundingOutputIndex,
768
- reveal.depositor,
769
- fundingOutputAmount,
770
- reveal.blindingFactor,
771
- reveal.walletPubKeyHash,
772
- reveal.refundPubKeyHash,
773
- reveal.refundLocktime,
774
- reveal.vault
775
- );
606
+ self.revealDeposit(wallets, fundingTx, reveal);
776
607
  }
777
608
 
778
609
  /// @notice Used by the wallet to prove the BTC deposit sweep transaction
@@ -836,39 +667,10 @@ contract Bridge is Ownable, EcdsaWalletOwner {
836
667
  uint64 sweepTxOutputValue
837
668
  ) = processSweepTxOutput(sweepTx.outputVector);
838
669
 
839
- Wallets.Wallet storage wallet = wallets.registeredWallets[
840
- walletPubKeyHash
841
- ];
842
-
843
- Wallets.WalletState walletState = wallet.state;
844
- require(
845
- walletState == Wallets.WalletState.Live ||
846
- walletState == Wallets.WalletState.MovingFunds,
847
- "Wallet must be in Live or MovingFunds state"
848
- );
849
-
850
- // Check if the main UTXO for given wallet exists. If so, validate
851
- // passed main UTXO data against the stored hash and use them for
852
- // further processing. If no main UTXO exists, use empty data.
853
- BitcoinTx.UTXO memory resolvedMainUtxo = BitcoinTx.UTXO(
854
- bytes32(0),
855
- 0,
856
- 0
857
- );
858
- bytes32 mainUtxoHash = wallet.mainUtxoHash;
859
- if (mainUtxoHash != bytes32(0)) {
860
- require(
861
- keccak256(
862
- abi.encodePacked(
863
- mainUtxo.txHash,
864
- mainUtxo.txOutputIndex,
865
- mainUtxo.txOutputValue
866
- )
867
- ) == mainUtxoHash,
868
- "Invalid main UTXO data"
869
- );
870
- resolvedMainUtxo = mainUtxo;
871
- }
670
+ (
671
+ Wallets.Wallet storage wallet,
672
+ BitcoinTx.UTXO memory resolvedMainUtxo
673
+ ) = resolveSweepingWallet(walletPubKeyHash, mainUtxo);
872
674
 
873
675
  // Process sweep transaction inputs and extract all information needed
874
676
  // to perform deposit bookkeeping.
@@ -940,6 +742,57 @@ contract Bridge is Ownable, EcdsaWalletOwner {
940
742
  // TODO: Handle deposits having `vault` set.
941
743
  }
942
744
 
745
+ /// @notice Resolves sweeping wallet based on the provided wallet public key
746
+ /// hash. Validates the wallet state and current main UTXO, as
747
+ /// currently known on the Ethereum chain.
748
+ /// @param walletPubKeyHash public key hash of the wallet proving the sweep
749
+ /// Bitcoin transaction.
750
+ /// @param mainUtxo Data of the wallet's main UTXO, as currently known on
751
+ /// the Ethereum chain. If no main UTXO exists for the given wallet,
752
+ /// this parameter is ignored
753
+ /// @dev Requirements:
754
+ /// - Sweeping wallet must be either in Live or MovingFunds state.
755
+ /// - If the main UTXO of the sweeping wallet exists in the storage,
756
+ /// the passed `mainUTXO` parameter must be equal to the stored one.
757
+ function resolveSweepingWallet(
758
+ bytes20 walletPubKeyHash,
759
+ BitcoinTx.UTXO calldata mainUtxo
760
+ )
761
+ internal
762
+ returns (
763
+ Wallets.Wallet storage wallet,
764
+ BitcoinTx.UTXO memory resolvedMainUtxo
765
+ )
766
+ {
767
+ wallet = wallets.registeredWallets[walletPubKeyHash];
768
+
769
+ Wallets.WalletState walletState = wallet.state;
770
+ require(
771
+ walletState == Wallets.WalletState.Live ||
772
+ walletState == Wallets.WalletState.MovingFunds,
773
+ "Wallet must be in Live or MovingFunds state"
774
+ );
775
+
776
+ // Check if the main UTXO for given wallet exists. If so, validate
777
+ // passed main UTXO data against the stored hash and use them for
778
+ // further processing. If no main UTXO exists, use empty data.
779
+ resolvedMainUtxo = BitcoinTx.UTXO(bytes32(0), 0, 0);
780
+ bytes32 mainUtxoHash = wallet.mainUtxoHash;
781
+ if (mainUtxoHash != bytes32(0)) {
782
+ require(
783
+ keccak256(
784
+ abi.encodePacked(
785
+ mainUtxo.txHash,
786
+ mainUtxo.txOutputIndex,
787
+ mainUtxo.txOutputValue
788
+ )
789
+ ) == mainUtxoHash,
790
+ "Invalid main UTXO data"
791
+ );
792
+ resolvedMainUtxo = mainUtxo;
793
+ }
794
+ }
795
+
943
796
  /// @notice Processes the Bitcoin sweep transaction output vector by
944
797
  /// extracting the single output and using it to gain additional
945
798
  /// information required for further processing (e.g. value and
@@ -1055,7 +908,7 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1055
908
  uint256 inputLength
1056
909
  ) = parseTxInputAt(sweepTxInputVector, inputStartingIndex);
1057
910
 
1058
- DepositRequest storage deposit = deposits[
911
+ Deposit.Request storage deposit = self.deposits[
1059
912
  uint256(
1060
913
  keccak256(abi.encodePacked(outpointTxHash, outpointIndex))
1061
914
  )
@@ -1302,7 +1155,7 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1302
1155
 
1303
1156
  // Check that the UTXO key identifies a correctly spent UTXO.
1304
1157
  require(
1305
- deposits[utxoKey].sweptAt > 0 || spentMainUTXOs[utxoKey],
1158
+ self.deposits[utxoKey].sweptAt > 0 || spentMainUTXOs[utxoKey],
1306
1159
  "Spent UTXO not found among correctly spent UTXOs"
1307
1160
  );
1308
1161
 
@@ -1595,9 +1448,9 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1595
1448
  proofDifficultyContext()
1596
1449
  );
1597
1450
 
1598
- // Perform validation of the redemption transaction input. Specifically,
1599
- // check if it refers to the expected wallet's main UTXO.
1600
- validateRedemptionTxInput(
1451
+ // Process the redemption transaction input. Specifically, check if it
1452
+ // refers to the expected wallet's main UTXO.
1453
+ processWalletOutboundTxInput(
1601
1454
  redemptionTx.inputVector,
1602
1455
  mainUtxo,
1603
1456
  walletPubKeyHash
@@ -1646,10 +1499,14 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1646
1499
  bank.transferBalance(treasury, outputsInfo.totalTreasuryFee);
1647
1500
  }
1648
1501
 
1649
- /// @notice Validates whether the redemption Bitcoin transaction input
1650
- /// vector contains a single input referring to the wallet's main
1651
- /// UTXO. Reverts in case the validation fails.
1652
- /// @param redemptionTxInputVector Bitcoin redemption transaction input
1502
+ /// @notice Checks whether an outbound Bitcoin transaction performed from
1503
+ /// the given wallet has an input vector that contains a single
1504
+ /// input referring to the wallet's main UTXO. Marks that main UTXO
1505
+ /// as correctly spent if the validation succeeds. Reverts otherwise.
1506
+ /// There are two outbound transactions from a wallet possible: a
1507
+ /// redemption transaction or a moving funds to another wallet
1508
+ /// transaction.
1509
+ /// @param walletOutboundTxInputVector Bitcoin outbound transaction's input
1653
1510
  /// vector. This function assumes vector's structure is valid so it
1654
1511
  /// must be validated using e.g. `BTCUtils.validateVin` function
1655
1512
  /// before it is passed here
@@ -1657,9 +1514,9 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1657
1514
  /// the Ethereum chain.
1658
1515
  /// @param walletPubKeyHash 20-byte public key hash (computed using Bitcoin
1659
1516
  // HASH160 over the compressed ECDSA public key) of the wallet which
1660
- /// performed the redemption transaction.
1661
- function validateRedemptionTxInput(
1662
- bytes memory redemptionTxInputVector,
1517
+ /// performed the outbound transaction.
1518
+ function processWalletOutboundTxInput(
1519
+ bytes memory walletOutboundTxInputVector,
1663
1520
  BitcoinTx.UTXO calldata mainUtxo,
1664
1521
  bytes20 walletPubKeyHash
1665
1522
  ) internal {
@@ -1682,16 +1539,16 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1682
1539
  "Invalid main UTXO data"
1683
1540
  );
1684
1541
 
1685
- // Assert that the single redemption transaction input actually
1542
+ // Assert that the single outbound transaction input actually
1686
1543
  // refers to the wallet's main UTXO.
1687
1544
  (
1688
- bytes32 redemptionTxOutpointTxHash,
1689
- uint32 redemptionTxOutpointIndex
1690
- ) = processRedemptionTxInput(redemptionTxInputVector);
1545
+ bytes32 outpointTxHash,
1546
+ uint32 outpointIndex
1547
+ ) = parseWalletOutboundTxInput(walletOutboundTxInputVector);
1691
1548
  require(
1692
- mainUtxo.txHash == redemptionTxOutpointTxHash &&
1693
- mainUtxo.txOutputIndex == redemptionTxOutpointIndex,
1694
- "Redemption transaction input must point to the wallet's main UTXO"
1549
+ mainUtxo.txHash == outpointTxHash &&
1550
+ mainUtxo.txOutputIndex == outpointIndex,
1551
+ "Outbound transaction input must point to the wallet's main UTXO"
1695
1552
  );
1696
1553
 
1697
1554
  // Main UTXO used as an input, mark it as spent.
@@ -1704,10 +1561,13 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1704
1561
  ] = true;
1705
1562
  }
1706
1563
 
1707
- /// @notice Processes the Bitcoin redemption transaction input vector. It
1708
- /// extracts the single input then the transaction hash and output
1709
- /// index from its outpoint.
1710
- /// @param redemptionTxInputVector Bitcoin redemption transaction input
1564
+ /// @notice Parses the input vector of an outbound Bitcoin transaction
1565
+ /// performed from the given wallet. It extracts the single input
1566
+ /// then the transaction hash and output index from its outpoint.
1567
+ /// There are two outbound transactions from a wallet possible: a
1568
+ /// redemption transaction or a moving funds to another wallet
1569
+ /// transaction.
1570
+ /// @param walletOutboundTxInputVector Bitcoin outbound transaction input
1711
1571
  /// vector. This function assumes vector's structure is valid so it
1712
1572
  /// must be validated using e.g. `BTCUtils.validateVin` function
1713
1573
  /// before it is passed here
@@ -1715,12 +1575,10 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1715
1575
  /// pointed in the input's outpoint.
1716
1576
  /// @return outpointIndex 4-byte index of the Bitcoin transaction output
1717
1577
  /// which is pointed in the input's outpoint.
1718
- function processRedemptionTxInput(bytes memory redemptionTxInputVector)
1719
- internal
1720
- pure
1721
- returns (bytes32 outpointTxHash, uint32 outpointIndex)
1722
- {
1723
- // To determine the total number of redemption transaction inputs,
1578
+ function parseWalletOutboundTxInput(
1579
+ bytes memory walletOutboundTxInputVector
1580
+ ) internal pure returns (bytes32 outpointTxHash, uint32 outpointIndex) {
1581
+ // To determine the total number of Bitcoin transaction inputs,
1724
1582
  // we need to parse the compactSize uint (VarInt) the input vector is
1725
1583
  // prepended by. That compactSize uint encodes the number of vector
1726
1584
  // elements using the format presented in:
@@ -1728,13 +1586,13 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1728
1586
  // We don't need asserting the compactSize uint is parseable since it
1729
1587
  // was already checked during `validateVin` validation.
1730
1588
  // See `BitcoinTx.inputVector` docs for more details.
1731
- (, uint256 inputsCount) = redemptionTxInputVector.parseVarInt();
1589
+ (, uint256 inputsCount) = walletOutboundTxInputVector.parseVarInt();
1732
1590
  require(
1733
1591
  inputsCount == 1,
1734
- "Redemption transaction must have a single input"
1592
+ "Outbound transaction must have a single input"
1735
1593
  );
1736
1594
 
1737
- bytes memory input = redemptionTxInputVector.extractInputAtIndex(0);
1595
+ bytes memory input = walletOutboundTxInputVector.extractInputAtIndex(0);
1738
1596
 
1739
1597
  outpointTxHash = input.extractInputTxIdLE();
1740
1598
 
@@ -1797,11 +1655,26 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1797
1655
  // scripts that can be used to lock the change. This is done upfront to
1798
1656
  // save on gas. Both scripts have a strict format defined by Bitcoin.
1799
1657
  //
1800
- // The P2PKH script has format <0x1976a914> <20-byte PKH> <0x88ac>.
1658
+ // The P2PKH script has the byte format: <0x1976a914> <20-byte PKH> <0x88ac>.
1659
+ // According to https://en.bitcoin.it/wiki/Script#Opcodes this translates to:
1660
+ // - 0x19: Byte length of the entire script
1661
+ // - 0x76: OP_DUP
1662
+ // - 0xa9: OP_HASH160
1663
+ // - 0x14: Byte length of the public key hash
1664
+ // - 0x88: OP_EQUALVERIFY
1665
+ // - 0xac: OP_CHECKSIG
1666
+ // which matches the P2PKH structure as per:
1667
+ // https://en.bitcoin.it/wiki/Transaction#Pay-to-PubkeyHash
1801
1668
  bytes32 walletP2PKHScriptKeccak = keccak256(
1802
1669
  abi.encodePacked(hex"1976a914", walletPubKeyHash, hex"88ac")
1803
1670
  );
1804
- // The P2WPKH script has format <0x160014> <20-byte PKH>.
1671
+ // The P2WPKH script has the byte format: <0x160014> <20-byte PKH>.
1672
+ // According to https://en.bitcoin.it/wiki/Script#Opcodes this translates to:
1673
+ // - 0x16: Byte length of the entire script
1674
+ // - 0x00: OP_0
1675
+ // - 0x14: Byte length of the public key hash
1676
+ // which matches the P2WPKH structure as per:
1677
+ // https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#P2WPKH
1805
1678
  bytes32 walletP2WPKHScriptKeccak = keccak256(
1806
1679
  abi.encodePacked(hex"160014", walletPubKeyHash)
1807
1680
  );
@@ -1827,7 +1700,7 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1827
1700
  // Extract the value from given output.
1828
1701
  uint64 outputValue = output.extractValue();
1829
1702
  // The output consists of an 8-byte value and a variable length
1830
- // script. To extract that script we slice the output staring from
1703
+ // script. To extract that script we slice the output starting from
1831
1704
  // 9th byte until the end.
1832
1705
  bytes memory outputScript = output.slice(8, output.length - 8);
1833
1706
 
@@ -2006,4 +1879,292 @@ contract Bridge is Ownable, EcdsaWalletOwner {
2006
1879
  // Return the requested amount of tokens to the redeemer
2007
1880
  bank.transferBalance(request.redeemer, request.requestedAmount);
2008
1881
  }
1882
+
1883
+ /// @notice Used by the wallet to prove the BTC moving funds transaction
1884
+ /// and to make the necessary state changes. Moving funds is only
1885
+ /// accepted if it satisfies SPV proof.
1886
+ ///
1887
+ /// The function validates the moving funds transaction structure
1888
+ /// by checking if it actually spends the main UTXO of the declared
1889
+ /// wallet and locks the value on the pre-committed target wallets
1890
+ /// using a reasonable transaction fee. If all preconditions are
1891
+ /// met, this functions closes the source wallet.
1892
+ ///
1893
+ /// It is possible to prove the given moving funds transaction only
1894
+ /// one time.
1895
+ /// @param movingFundsTx Bitcoin moving funds transaction data
1896
+ /// @param movingFundsProof Bitcoin moving funds proof data
1897
+ /// @param mainUtxo Data of the wallet's main UTXO, as currently known on
1898
+ /// the Ethereum chain
1899
+ /// @param walletPubKeyHash 20-byte public key hash (computed using Bitcoin
1900
+ /// HASH160 over the compressed ECDSA public key) of the wallet
1901
+ /// which performed the moving funds transaction
1902
+ /// @dev Requirements:
1903
+ /// - `movingFundsTx` components must match the expected structure. See
1904
+ /// `BitcoinTx.Info` docs for reference. Their values must exactly
1905
+ /// correspond to appropriate Bitcoin transaction fields to produce
1906
+ /// a provable transaction hash.
1907
+ /// - The `movingFundsTx` should represent a Bitcoin transaction with
1908
+ /// exactly 1 input that refers to the wallet's main UTXO. That
1909
+ /// transaction should have 1..n outputs corresponding to the
1910
+ /// pre-committed target wallets. Outputs must be ordered in the
1911
+ /// same way as their corresponding target wallets are ordered
1912
+ /// within the target wallets commitment.
1913
+ /// - `movingFundsProof` components must match the expected structure.
1914
+ /// See `BitcoinTx.Proof` docs for reference. The `bitcoinHeaders`
1915
+ /// field must contain a valid number of block headers, not less
1916
+ /// than the `txProofDifficultyFactor` contract constant.
1917
+ /// - `mainUtxo` components must point to the recent main UTXO
1918
+ /// of the given wallet, as currently known on the Ethereum chain.
1919
+ /// Additionally, the recent main UTXO on Ethereum must be set.
1920
+ /// - `walletPubKeyHash` must be connected with the main UTXO used
1921
+ /// as transaction single input.
1922
+ /// - The wallet that `walletPubKeyHash` points to must be in the
1923
+ /// MovingFunds state.
1924
+ /// - The target wallets commitment must be submitted by the wallet
1925
+ /// that `walletPubKeyHash` points to.
1926
+ /// - The total Bitcoin transaction fee must be lesser or equal
1927
+ /// to `movingFundsTxMaxTotalFee` governable parameter.
1928
+ function submitMovingFundsProof(
1929
+ BitcoinTx.Info calldata movingFundsTx,
1930
+ BitcoinTx.Proof calldata movingFundsProof,
1931
+ BitcoinTx.UTXO calldata mainUtxo,
1932
+ bytes20 walletPubKeyHash
1933
+ ) external {
1934
+ // The actual transaction proof is performed here. After that point, we
1935
+ // can assume the transaction happened on Bitcoin chain and has
1936
+ // a sufficient number of confirmations as determined by
1937
+ // `txProofDifficultyFactor` constant.
1938
+ bytes32 movingFundsTxHash = BitcoinTx.validateProof(
1939
+ movingFundsTx,
1940
+ movingFundsProof,
1941
+ proofDifficultyContext()
1942
+ );
1943
+
1944
+ // Process the moving funds transaction input. Specifically, check if
1945
+ // it refers to the expected wallet's main UTXO.
1946
+ processWalletOutboundTxInput(
1947
+ movingFundsTx.inputVector,
1948
+ mainUtxo,
1949
+ walletPubKeyHash
1950
+ );
1951
+
1952
+ (
1953
+ bytes32 targetWalletsHash,
1954
+ uint256 outputsTotalValue
1955
+ ) = processMovingFundsTxOutputs(movingFundsTx.outputVector);
1956
+
1957
+ require(
1958
+ mainUtxo.txOutputValue - outputsTotalValue <=
1959
+ movingFundsTxMaxTotalFee,
1960
+ "Transaction fee is too high"
1961
+ );
1962
+
1963
+ wallets.notifyFundsMoved(walletPubKeyHash, targetWalletsHash);
1964
+
1965
+ emit MovingFundsCompleted(walletPubKeyHash, movingFundsTxHash);
1966
+ }
1967
+
1968
+ /// @notice Processes the moving funds Bitcoin transaction output vector
1969
+ /// and extracts information required for further processing.
1970
+ /// @param movingFundsTxOutputVector Bitcoin moving funds transaction output
1971
+ /// vector. This function assumes vector's structure is valid so it
1972
+ /// must be validated using e.g. `BTCUtils.validateVout` function
1973
+ /// before it is passed here
1974
+ /// @return targetWalletsHash keccak256 hash over the list of actual
1975
+ /// target wallets used in the transaction.
1976
+ /// @return outputsTotalValue Sum of all outputs values.
1977
+ /// @dev Requirements:
1978
+ /// - The `movingFundsTxOutputVector` must be parseable, i.e. must
1979
+ /// be validated by the caller as stated in their parameter doc.
1980
+ /// - Each output must refer to a 20-byte public key hash.
1981
+ /// - The total outputs value must be evenly divided over all outputs.
1982
+ function processMovingFundsTxOutputs(bytes memory movingFundsTxOutputVector)
1983
+ internal
1984
+ view
1985
+ returns (bytes32 targetWalletsHash, uint256 outputsTotalValue)
1986
+ {
1987
+ // Determining the total number of Bitcoin transaction outputs in
1988
+ // the same way as for number of inputs. See `BitcoinTx.outputVector`
1989
+ // docs for more details.
1990
+ (
1991
+ uint256 outputsCompactSizeUintLength,
1992
+ uint256 outputsCount
1993
+ ) = movingFundsTxOutputVector.parseVarInt();
1994
+
1995
+ // To determine the first output starting index, we must jump over
1996
+ // the compactSize uint which prepends the output vector. One byte
1997
+ // must be added because `BtcUtils.parseVarInt` does not include
1998
+ // compactSize uint tag in the returned length.
1999
+ //
2000
+ // For >= 0 && <= 252, `BTCUtils.determineVarIntDataLengthAt`
2001
+ // returns `0`, so we jump over one byte of compactSize uint.
2002
+ //
2003
+ // For >= 253 && <= 0xffff there is `0xfd` tag,
2004
+ // `BTCUtils.determineVarIntDataLengthAt` returns `2` (no
2005
+ // tag byte included) so we need to jump over 1+2 bytes of
2006
+ // compactSize uint.
2007
+ //
2008
+ // Please refer `BTCUtils` library and compactSize uint
2009
+ // docs in `BitcoinTx` library for more details.
2010
+ uint256 outputStartingIndex = 1 + outputsCompactSizeUintLength;
2011
+
2012
+ bytes20[] memory targetWallets = new bytes20[](outputsCount);
2013
+ uint64[] memory outputsValues = new uint64[](outputsCount);
2014
+
2015
+ // Outputs processing loop.
2016
+ for (uint256 i = 0; i < outputsCount; i++) {
2017
+ uint256 outputLength = movingFundsTxOutputVector
2018
+ .determineOutputLengthAt(outputStartingIndex);
2019
+
2020
+ bytes memory output = movingFundsTxOutputVector.slice(
2021
+ outputStartingIndex,
2022
+ outputLength
2023
+ );
2024
+
2025
+ // Extract the output script payload.
2026
+ bytes memory targetWalletPubKeyHashBytes = output.extractHash();
2027
+ // Output script payload must refer to a known wallet public key
2028
+ // hash which is always 20-byte.
2029
+ require(
2030
+ targetWalletPubKeyHashBytes.length == 20,
2031
+ "Target wallet public key hash must have 20 bytes"
2032
+ );
2033
+
2034
+ bytes20 targetWalletPubKeyHash = targetWalletPubKeyHashBytes
2035
+ .slice20(0);
2036
+
2037
+ // The next step is making sure that the 20-byte public key hash
2038
+ // is actually used in the right context of a P2PKH or P2WPKH
2039
+ // output. To do so, we must extract the full script from the output
2040
+ // and compare with the expected P2PKH and P2WPKH scripts
2041
+ // referring to that 20-byte public key hash. The output consists
2042
+ // of an 8-byte value and a variable length script. To extract the
2043
+ // script we slice the output starting from 9th byte until the end.
2044
+ bytes32 outputScriptKeccak = keccak256(
2045
+ output.slice(8, output.length - 8)
2046
+ );
2047
+ // Build the expected P2PKH script which has the following byte
2048
+ // format: <0x1976a914> <20-byte PKH> <0x88ac>. According to
2049
+ // https://en.bitcoin.it/wiki/Script#Opcodes this translates to:
2050
+ // - 0x19: Byte length of the entire script
2051
+ // - 0x76: OP_DUP
2052
+ // - 0xa9: OP_HASH160
2053
+ // - 0x14: Byte length of the public key hash
2054
+ // - 0x88: OP_EQUALVERIFY
2055
+ // - 0xac: OP_CHECKSIG
2056
+ // which matches the P2PKH structure as per:
2057
+ // https://en.bitcoin.it/wiki/Transaction#Pay-to-PubkeyHash
2058
+ bytes32 targetWalletP2PKHScriptKeccak = keccak256(
2059
+ abi.encodePacked(
2060
+ hex"1976a914",
2061
+ targetWalletPubKeyHash,
2062
+ hex"88ac"
2063
+ )
2064
+ );
2065
+ // Build the expected P2WPKH script which has the following format:
2066
+ // <0x160014> <20-byte PKH>. According to
2067
+ // https://en.bitcoin.it/wiki/Script#Opcodes this translates to:
2068
+ // - 0x16: Byte length of the entire script
2069
+ // - 0x00: OP_0
2070
+ // - 0x14: Byte length of the public key hash
2071
+ // which matches the P2WPKH structure as per:
2072
+ // https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#P2WPKH
2073
+ bytes32 targetWalletP2WPKHScriptKeccak = keccak256(
2074
+ abi.encodePacked(hex"160014", targetWalletPubKeyHash)
2075
+ );
2076
+ // Make sure the actual output script matches either the P2PKH
2077
+ // or P2WPKH format.
2078
+ require(
2079
+ outputScriptKeccak == targetWalletP2PKHScriptKeccak ||
2080
+ outputScriptKeccak == targetWalletP2WPKHScriptKeccak,
2081
+ "Output must be P2PKH or P2WPKH"
2082
+ );
2083
+
2084
+ // Add the wallet public key hash to the list that will be used
2085
+ // to build the result list hash. There is no need to check if
2086
+ // given output is a change here because the actual target wallet
2087
+ // list must be exactly the same as the pre-committed target wallet
2088
+ // list which is guaranteed to be valid.
2089
+ targetWallets[i] = targetWalletPubKeyHash;
2090
+
2091
+ // Extract the value from given output.
2092
+ outputsValues[i] = output.extractValue();
2093
+ outputsTotalValue += outputsValues[i];
2094
+
2095
+ // Make the `outputStartingIndex` pointing to the next output by
2096
+ // increasing it by current output's length.
2097
+ outputStartingIndex += outputLength;
2098
+ }
2099
+
2100
+ // Compute the indivisible remainder that remains after dividing the
2101
+ // outputs total value over all outputs evenly.
2102
+ uint256 outputsTotalValueRemainder = outputsTotalValue % outputsCount;
2103
+ // Compute the minimum allowed output value by dividing the outputs
2104
+ // total value (reduced by the remainder) by the number of outputs.
2105
+ uint256 minOutputValue = (outputsTotalValue -
2106
+ outputsTotalValueRemainder) / outputsCount;
2107
+ // Maximum possible value is the minimum value with the remainder included.
2108
+ uint256 maxOutputValue = minOutputValue + outputsTotalValueRemainder;
2109
+
2110
+ for (uint256 i = 0; i < outputsCount; i++) {
2111
+ require(
2112
+ minOutputValue <= outputsValues[i] &&
2113
+ outputsValues[i] <= maxOutputValue,
2114
+ "Transaction amount is not distributed evenly"
2115
+ );
2116
+ }
2117
+
2118
+ targetWalletsHash = keccak256(abi.encodePacked(targetWallets));
2119
+
2120
+ return (targetWalletsHash, outputsTotalValue);
2121
+ }
2122
+
2123
+ /// @notice Returns the current values of Bridge deposit parameters.
2124
+ /// @return depositDustThreshold The minimal amount that can be requested
2125
+ /// to deposit. Value of this parameter must take into account the
2126
+ /// value of `depositTreasuryFeeDivisor` and `depositTxMaxFee`
2127
+ /// parameters in order to make requests that can incur the
2128
+ /// treasury and transaction fee and still satisfy the depositor.
2129
+ /// @return depositTreasuryFeeDivisor Divisor used to compute the treasury
2130
+ /// fee taken from each deposit and transferred to the treasury upon
2131
+ /// sweep proof submission. That fee is computed as follows:
2132
+ /// `treasuryFee = depositedAmount / depositTreasuryFeeDivisor`
2133
+ /// For example, if the treasury fee needs to be 2% of each deposit,
2134
+ /// the `depositTreasuryFeeDivisor` should be set to `50`
2135
+ /// because `1/50 = 0.02 = 2%`.
2136
+ function depositParameters()
2137
+ external
2138
+ view
2139
+ returns (uint64 depositDustThreshold, uint64 depositTreasuryFeeDivisor)
2140
+ {
2141
+ depositDustThreshold = self.depositDustThreshold;
2142
+ depositTreasuryFeeDivisor = self.depositTreasuryFeeDivisor;
2143
+ }
2144
+
2145
+ /// @notice Indicates if the vault with the given address is trusted or not.
2146
+ /// Depositors can route their revealed deposits only to trusted
2147
+ /// vaults and have trusted vaults notified about new deposits as
2148
+ /// soon as these deposits get swept. Vaults not trusted by the
2149
+ /// Bridge can still be used by Bank balance owners on their own
2150
+ /// responsibility - anyone can approve their Bank balance to any
2151
+ /// address.
2152
+ function isVaultTrusted(address vault) external view returns (bool) {
2153
+ return self.isVaultTrusted[vault];
2154
+ }
2155
+
2156
+ /// @notice Collection of all revealed deposits indexed by
2157
+ /// keccak256(fundingTxHash | fundingOutputIndex).
2158
+ /// The fundingTxHash is bytes32 (ordered as in Bitcoin internally)
2159
+ /// and fundingOutputIndex an uint32. This mapping may contain valid
2160
+ /// and invalid deposits and the wallet is responsible for
2161
+ /// validating them before attempting to execute a sweep.
2162
+ function deposits(uint256 depositKey)
2163
+ external
2164
+ view
2165
+ returns (Deposit.Request memory)
2166
+ {
2167
+ // TODO: rename to getDeposit?
2168
+ return self.deposits[depositKey];
2169
+ }
2009
2170
  }