@keep-network/tbtc-v2 0.1.1-dev.33 → 0.1.1-dev.36

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 (31) 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/{7e4974a220c8c12bc2c31c8235131416.json → 4718d6e944ad9d1fc247efda870cf51a.json} +12 -6
  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 +89 -113
  10. package/build/contracts/bridge/BridgeState.sol/BridgeState.dbg.json +1 -1
  11. package/build/contracts/bridge/BridgeState.sol/BridgeState.json +2 -2
  12. package/build/contracts/bridge/Deposit.sol/Deposit.dbg.json +1 -1
  13. package/build/contracts/bridge/Deposit.sol/Deposit.json +2 -2
  14. package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.dbg.json +1 -1
  15. package/build/contracts/bridge/Frauds.sol/Frauds.dbg.json +1 -1
  16. package/build/contracts/bridge/Frauds.sol/Frauds.json +2 -2
  17. package/build/contracts/bridge/IRelay.sol/IRelay.dbg.json +4 -0
  18. package/build/contracts/bridge/{Bridge.sol → IRelay.sol}/IRelay.json +1 -1
  19. package/build/contracts/bridge/Sweep.sol/Sweep.dbg.json +4 -0
  20. package/build/contracts/bridge/Sweep.sol/Sweep.json +48 -0
  21. package/build/contracts/bridge/VendingMachine.sol/VendingMachine.dbg.json +1 -1
  22. package/build/contracts/bridge/Wallets.sol/Wallets.dbg.json +1 -1
  23. package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
  24. package/build/contracts/vault/IVault.sol/IVault.dbg.json +1 -1
  25. package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +1 -1
  26. package/contracts/bridge/Bridge.sol +220 -558
  27. package/contracts/bridge/BridgeState.sol +50 -0
  28. package/contracts/bridge/IRelay.sol +28 -0
  29. package/contracts/bridge/Sweep.sol +510 -0
  30. package/package.json +1 -1
  31. package/build/contracts/bridge/Bridge.sol/IRelay.dbg.json +0 -4
@@ -19,26 +19,19 @@ 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
+
22
23
  import {IWalletOwner as EcdsaWalletOwner} from "@keep-network/ecdsa/contracts/api/IWalletOwner.sol";
23
24
 
24
- import "../bank/Bank.sol";
25
+ import "./IRelay.sol";
25
26
  import "./BridgeState.sol";
27
+ import "./Deposit.sol";
28
+ import "./Sweep.sol";
26
29
  import "./BitcoinTx.sol";
27
30
  import "./EcdsaLib.sol";
28
31
  import "./Wallets.sol";
29
32
  import "./Frauds.sol";
30
33
 
31
- /// @title Interface for the Bitcoin relay
32
- /// @notice Contains only the methods needed by tBTC v2. The Bitcoin relay
33
- /// provides the difficulty of the previous and current epoch. One
34
- /// difficulty epoch spans 2016 blocks.
35
- interface IRelay {
36
- /// @notice Returns the difficulty of the current epoch.
37
- function getCurrentEpochDifficulty() external view returns (uint256);
38
-
39
- /// @notice Returns the difficulty of the previous epoch.
40
- function getPrevEpochDifficulty() external view returns (uint256);
41
- }
34
+ import "../bank/Bank.sol";
42
35
 
43
36
  /// @title Bitcoin Bridge
44
37
  /// @notice Bridge manages BTC deposit and redemption flow and is increasing and
@@ -67,6 +60,7 @@ interface IRelay {
67
60
  contract Bridge is Ownable, EcdsaWalletOwner {
68
61
  using BridgeState for BridgeState.Storage;
69
62
  using Deposit for BridgeState.Storage;
63
+ using Sweep for BridgeState.Storage;
70
64
  using Frauds for Frauds.Data;
71
65
  using Wallets for Wallets.Data;
72
66
 
@@ -74,32 +68,6 @@ contract Bridge is Ownable, EcdsaWalletOwner {
74
68
  using BTCUtils for uint256;
75
69
  using BytesLib for bytes;
76
70
 
77
- /// @notice Represents an outcome of the sweep Bitcoin transaction
78
- /// inputs processing.
79
- struct SweepTxInputsInfo {
80
- // Sum of all inputs values i.e. all deposits and main UTXO value,
81
- // if present.
82
- uint256 inputsTotalValue;
83
- // Addresses of depositors who performed processed deposits. Ordered in
84
- // the same order as deposits inputs in the input vector. Size of this
85
- // array is either equal to the number of inputs (main UTXO doesn't
86
- // exist) or less by one (main UTXO exists and is pointed by one of
87
- // the inputs).
88
- address[] depositors;
89
- // Amounts of deposits corresponding to processed deposits. Ordered in
90
- // the same order as deposits inputs in the input vector. Size of this
91
- // array is either equal to the number of inputs (main UTXO doesn't
92
- // exist) or less by one (main UTXO exists and is pointed by one of
93
- // the inputs).
94
- uint256[] depositedAmounts;
95
- // Values of the treasury fee corresponding to processed deposits.
96
- // Ordered in the same order as deposits inputs in the input vector.
97
- // Size of this array is either equal to the number of inputs (main
98
- // UTXO doesn't exist) or less by one (main UTXO exists and is pointed
99
- // by one of the inputs).
100
- uint256[] treasuryFees;
101
- }
102
-
103
71
  /// @notice Represents a redemption request.
104
72
  struct RedemptionRequest {
105
73
  // ETH address of the redeemer who created the request.
@@ -133,33 +101,24 @@ contract Bridge is Ownable, EcdsaWalletOwner {
133
101
  uint64 changeValue;
134
102
  }
135
103
 
136
- /// @notice The number of confirmations on the Bitcoin chain required to
137
- /// successfully evaluate an SPV proof.
138
- uint256 public immutable txProofDifficultyFactor;
139
-
140
- /// TODO: Revisit whether it should be governable or not.
141
- /// @notice Address of the Bank this Bridge belongs to.
142
- Bank public immutable bank;
143
-
144
- /// TODO: Make it governable.
145
- /// @notice Handle to the Bitcoin relay.
146
- IRelay public immutable relay;
147
-
148
- /// TODO: Revisit whether it should be governable or not.
149
- /// @notice Address where the redemptions treasury fees will be sent to.
150
- /// Treasury takes part in the operators rewarding process.
151
- address public immutable treasury;
104
+ /// @notice Represents temporary information needed during the processing of
105
+ /// the redemption Bitcoin transaction outputs. This structure is an
106
+ /// internal one and should not be exported outside of the redemption
107
+ /// transaction processing code.
108
+ /// @dev Allows to mitigate "stack too deep" errors on EVM.
109
+ struct RedemptionTxOutputsProcessingInfo {
110
+ // The first output starting index in the transaction.
111
+ uint256 outputStartingIndex;
112
+ // The number of outputs in the transaction.
113
+ uint256 outputsCount;
114
+ // P2PKH script for the wallet. Needed to determine the change output.
115
+ bytes32 walletP2PKHScriptKeccak;
116
+ // P2WPKH script for the wallet. Needed to determine the change output.
117
+ bytes32 walletP2WPKHScriptKeccak;
118
+ }
152
119
 
153
120
  BridgeState.Storage internal self;
154
121
 
155
- /// TODO: Make it governable.
156
- /// @notice Maximum amount of BTC transaction fee that can be incurred by
157
- /// each swept deposit being part of the given sweep
158
- /// transaction. If the maximum BTC transaction fee is exceeded,
159
- /// such transaction is considered a fraud.
160
- /// @dev This is a per-deposit input max fee for the sweep transaction.
161
- uint64 public depositTxMaxFee;
162
-
163
122
  /// TODO: Make it governable.
164
123
  /// @notice The minimal amount that can be requested for redemption.
165
124
  /// Value of this parameter must take into account the value of
@@ -203,14 +162,6 @@ contract Bridge is Ownable, EcdsaWalletOwner {
203
162
  /// for the entire transaction.
204
163
  uint64 public movingFundsTxMaxTotalFee;
205
164
 
206
- /// @notice Collection of main UTXOs that are honestly spent indexed by
207
- /// keccak256(fundingTxHash | fundingOutputIndex). The fundingTxHash
208
- /// is bytes32 (ordered as in Bitcoin internally) and
209
- /// fundingOutputIndex an uint32. A main UTXO is considered honestly
210
- /// spent if it was used as an input of a transaction that have been
211
- /// proven in the Bridge.
212
- mapping(uint256 => bool) public spentMainUTXOs;
213
-
214
165
  /// @notice Collection of all pending redemption requests indexed by
215
166
  /// redemption key built as
216
167
  /// keccak256(walletPubKeyHash | redeemerOutputScript). The
@@ -357,19 +308,19 @@ contract Bridge is Ownable, EcdsaWalletOwner {
357
308
  uint256 _txProofDifficultyFactor
358
309
  ) {
359
310
  require(_bank != address(0), "Bank address cannot be zero");
360
- bank = Bank(_bank);
311
+ self.bank = Bank(_bank);
361
312
 
362
313
  require(_relay != address(0), "Relay address cannot be zero");
363
- relay = IRelay(_relay);
314
+ self.relay = IRelay(_relay);
364
315
 
365
316
  require(_treasury != address(0), "Treasury address cannot be zero");
366
- treasury = _treasury;
317
+ self.treasury = _treasury;
367
318
 
368
- txProofDifficultyFactor = _txProofDifficultyFactor;
319
+ self.txProofDifficultyFactor = _txProofDifficultyFactor;
369
320
 
370
321
  // TODO: Revisit initial values.
371
322
  self.depositDustThreshold = 1000000; // 1000000 satoshi = 0.01 BTC
372
- depositTxMaxFee = 10000; // 10000 satoshi
323
+ self.depositTxMaxFee = 10000; // 10000 satoshi
373
324
  self.depositTreasuryFeeDivisor = 2000; // 1/2000 == 5bps == 0.05% == 0.0005
374
325
  redemptionDustThreshold = 1000000; // 1000000 satoshi = 0.01 BTC
375
326
  redemptionTreasuryFeeDivisor = 2000; // 1/2000 == 5bps == 0.05% == 0.0005
@@ -549,22 +500,6 @@ contract Bridge is Ownable, EcdsaWalletOwner {
549
500
  return wallets.activeWalletPubKeyHash;
550
501
  }
551
502
 
552
- /// @notice Determines the current Bitcoin SPV proof difficulty context.
553
- /// @return proofDifficulty Bitcoin proof difficulty context.
554
- function proofDifficultyContext()
555
- internal
556
- view
557
- returns (BitcoinTx.ProofDifficulty memory proofDifficulty)
558
- {
559
- proofDifficulty.currentEpochDifficulty = relay
560
- .getCurrentEpochDifficulty();
561
- proofDifficulty.previousEpochDifficulty = relay
562
- .getPrevEpochDifficulty();
563
- proofDifficulty.difficultyFactor = txProofDifficultyFactor;
564
-
565
- return proofDifficulty;
566
- }
567
-
568
503
  /// @notice Used by the depositor to reveal information about their P2(W)SH
569
504
  /// Bitcoin deposit to the Bridge on Ethereum chain. The off-chain
570
505
  /// wallet listens for revealed deposit events and may decide to
@@ -647,388 +582,7 @@ contract Bridge is Ownable, EcdsaWalletOwner {
647
582
  BitcoinTx.Proof calldata sweepProof,
648
583
  BitcoinTx.UTXO calldata mainUtxo
649
584
  ) external {
650
- // TODO: Fail early if the function call gets frontrunned. See discussion:
651
- // https://github.com/keep-network/tbtc-v2/pull/106#discussion_r801745204
652
-
653
- // The actual transaction proof is performed here. After that point, we
654
- // can assume the transaction happened on Bitcoin chain and has
655
- // a sufficient number of confirmations as determined by
656
- // `txProofDifficultyFactor` constant.
657
- bytes32 sweepTxHash = BitcoinTx.validateProof(
658
- sweepTx,
659
- sweepProof,
660
- proofDifficultyContext()
661
- );
662
-
663
- // Process sweep transaction output and extract its target wallet
664
- // public key hash and value.
665
- (
666
- bytes20 walletPubKeyHash,
667
- uint64 sweepTxOutputValue
668
- ) = processSweepTxOutput(sweepTx.outputVector);
669
-
670
- Wallets.Wallet storage wallet = wallets.registeredWallets[
671
- walletPubKeyHash
672
- ];
673
-
674
- Wallets.WalletState walletState = wallet.state;
675
- require(
676
- walletState == Wallets.WalletState.Live ||
677
- walletState == Wallets.WalletState.MovingFunds,
678
- "Wallet must be in Live or MovingFunds state"
679
- );
680
-
681
- // Check if the main UTXO for given wallet exists. If so, validate
682
- // passed main UTXO data against the stored hash and use them for
683
- // further processing. If no main UTXO exists, use empty data.
684
- BitcoinTx.UTXO memory resolvedMainUtxo = BitcoinTx.UTXO(
685
- bytes32(0),
686
- 0,
687
- 0
688
- );
689
- bytes32 mainUtxoHash = wallet.mainUtxoHash;
690
- if (mainUtxoHash != bytes32(0)) {
691
- require(
692
- keccak256(
693
- abi.encodePacked(
694
- mainUtxo.txHash,
695
- mainUtxo.txOutputIndex,
696
- mainUtxo.txOutputValue
697
- )
698
- ) == mainUtxoHash,
699
- "Invalid main UTXO data"
700
- );
701
- resolvedMainUtxo = mainUtxo;
702
- }
703
-
704
- // Process sweep transaction inputs and extract all information needed
705
- // to perform deposit bookkeeping.
706
- SweepTxInputsInfo memory inputsInfo = processSweepTxInputs(
707
- sweepTx.inputVector,
708
- resolvedMainUtxo
709
- );
710
-
711
- // Helper variable that will hold the sum of treasury fees paid by
712
- // all deposits.
713
- uint256 totalTreasuryFee = 0;
714
-
715
- // Determine the transaction fee that should be incurred by each deposit
716
- // and the indivisible remainder that should be additionally incurred
717
- // by the last deposit.
718
- (
719
- uint256 depositTxFee,
720
- uint256 depositTxFeeRemainder
721
- ) = sweepTxFeeDistribution(
722
- inputsInfo.inputsTotalValue,
723
- sweepTxOutputValue,
724
- inputsInfo.depositedAmounts.length
725
- );
726
-
727
- // Make sure the highest value of the deposit transaction fee does not
728
- // exceed the maximum value limited by the governable parameter.
729
- require(
730
- depositTxFee + depositTxFeeRemainder <= depositTxMaxFee,
731
- "Transaction fee is too high"
732
- );
733
-
734
- // Reduce each deposit amount by treasury fee and transaction fee.
735
- for (uint256 i = 0; i < inputsInfo.depositedAmounts.length; i++) {
736
- // The last deposit should incur the deposit transaction fee
737
- // remainder.
738
- uint256 depositTxFeeIncurred = i ==
739
- inputsInfo.depositedAmounts.length - 1
740
- ? depositTxFee + depositTxFeeRemainder
741
- : depositTxFee;
742
-
743
- // There is no need to check whether
744
- // `inputsInfo.depositedAmounts[i] - inputsInfo.treasuryFees[i] - txFee > 0`
745
- // since the `depositDustThreshold` should force that condition
746
- // to be always true.
747
- inputsInfo.depositedAmounts[i] =
748
- inputsInfo.depositedAmounts[i] -
749
- inputsInfo.treasuryFees[i] -
750
- depositTxFeeIncurred;
751
- totalTreasuryFee += inputsInfo.treasuryFees[i];
752
- }
753
-
754
- // Record this sweep data and assign them to the wallet public key hash
755
- // as new main UTXO. Transaction output index is always 0 as sweep
756
- // transaction always contains only one output.
757
- wallet.mainUtxoHash = keccak256(
758
- abi.encodePacked(sweepTxHash, uint32(0), sweepTxOutputValue)
759
- );
760
-
761
- emit DepositsSwept(walletPubKeyHash, sweepTxHash);
762
-
763
- // Update depositors balances in the Bank.
764
- bank.increaseBalances(
765
- inputsInfo.depositors,
766
- inputsInfo.depositedAmounts
767
- );
768
- // Pass the treasury fee to the treasury address.
769
- bank.increaseBalance(treasury, totalTreasuryFee);
770
-
771
- // TODO: Handle deposits having `vault` set.
772
- }
773
-
774
- /// @notice Processes the Bitcoin sweep transaction output vector by
775
- /// extracting the single output and using it to gain additional
776
- /// information required for further processing (e.g. value and
777
- /// wallet public key hash).
778
- /// @param sweepTxOutputVector Bitcoin sweep transaction output vector.
779
- /// This function assumes vector's structure is valid so it must be
780
- /// validated using e.g. `BTCUtils.validateVout` function before
781
- /// it is passed here
782
- /// @return walletPubKeyHash 20-byte wallet public key hash.
783
- /// @return value 8-byte sweep transaction output value.
784
- function processSweepTxOutput(bytes memory sweepTxOutputVector)
785
- internal
786
- pure
787
- returns (bytes20 walletPubKeyHash, uint64 value)
788
- {
789
- // To determine the total number of sweep transaction outputs, we need to
790
- // parse the compactSize uint (VarInt) the output vector is prepended by.
791
- // That compactSize uint encodes the number of vector elements using the
792
- // format presented in:
793
- // https://developer.bitcoin.org/reference/transactions.html#compactsize-unsigned-integers
794
- // We don't need asserting the compactSize uint is parseable since it
795
- // was already checked during `validateVout` validation.
796
- // See `BitcoinTx.outputVector` docs for more details.
797
- (, uint256 outputsCount) = sweepTxOutputVector.parseVarInt();
798
- require(
799
- outputsCount == 1,
800
- "Sweep transaction must have a single output"
801
- );
802
-
803
- bytes memory output = sweepTxOutputVector.extractOutputAtIndex(0);
804
- value = output.extractValue();
805
- bytes memory walletPubKeyHashBytes = output.extractHash();
806
- // The sweep transaction output should always be P2PKH or P2WPKH.
807
- // In both cases, the wallet public key hash should be 20 bytes length.
808
- require(
809
- walletPubKeyHashBytes.length == 20,
810
- "Wallet public key hash should have 20 bytes"
811
- );
812
- /* solhint-disable-next-line no-inline-assembly */
813
- assembly {
814
- walletPubKeyHash := mload(add(walletPubKeyHashBytes, 32))
815
- }
816
-
817
- return (walletPubKeyHash, value);
818
- }
819
-
820
- /// @notice Processes the Bitcoin sweep transaction input vector. It
821
- /// extracts each input and tries to obtain associated deposit or
822
- /// main UTXO data, depending on the input type. Reverts
823
- /// if one of the inputs cannot be recognized as a pointer to a
824
- /// revealed deposit or expected main UTXO.
825
- /// This function also marks each processed deposit as swept.
826
- /// @param sweepTxInputVector Bitcoin sweep transaction input vector.
827
- /// This function assumes vector's structure is valid so it must be
828
- /// validated using e.g. `BTCUtils.validateVin` function before
829
- /// it is passed here
830
- /// @param mainUtxo Data of the wallet's main UTXO. If no main UTXO
831
- /// exists for the given the wallet, this parameter's fields should
832
- /// be zeroed to bypass the main UTXO validation
833
- /// @return info Outcomes of the processing.
834
- function processSweepTxInputs(
835
- bytes memory sweepTxInputVector,
836
- BitcoinTx.UTXO memory mainUtxo
837
- ) internal returns (SweepTxInputsInfo memory info) {
838
- // If the passed `mainUtxo` parameter's values are zeroed, the main UTXO
839
- // for the given wallet doesn't exist and it is not expected to be
840
- // included in the sweep transaction input vector.
841
- bool mainUtxoExpected = mainUtxo.txHash != bytes32(0);
842
- bool mainUtxoFound = false;
843
-
844
- // Determining the total number of sweep transaction inputs in the same
845
- // way as for number of outputs. See `BitcoinTx.inputVector` docs for
846
- // more details.
847
- (
848
- uint256 inputsCompactSizeUintLength,
849
- uint256 inputsCount
850
- ) = sweepTxInputVector.parseVarInt();
851
-
852
- // To determine the first input starting index, we must jump over
853
- // the compactSize uint which prepends the input vector. One byte
854
- // must be added because `BtcUtils.parseVarInt` does not include
855
- // compactSize uint tag in the returned length.
856
- //
857
- // For >= 0 && <= 252, `BTCUtils.determineVarIntDataLengthAt`
858
- // returns `0`, so we jump over one byte of compactSize uint.
859
- //
860
- // For >= 253 && <= 0xffff there is `0xfd` tag,
861
- // `BTCUtils.determineVarIntDataLengthAt` returns `2` (no
862
- // tag byte included) so we need to jump over 1+2 bytes of
863
- // compactSize uint.
864
- //
865
- // Please refer `BTCUtils` library and compactSize uint
866
- // docs in `BitcoinTx` library for more details.
867
- uint256 inputStartingIndex = 1 + inputsCompactSizeUintLength;
868
-
869
- // Determine the swept deposits count. If main UTXO is NOT expected,
870
- // all inputs should be deposits. If main UTXO is expected, one input
871
- // should point to that main UTXO.
872
- info.depositors = new address[](
873
- !mainUtxoExpected ? inputsCount : inputsCount - 1
874
- );
875
- info.depositedAmounts = new uint256[](info.depositors.length);
876
- info.treasuryFees = new uint256[](info.depositors.length);
877
-
878
- // Initialize helper variables.
879
- uint256 processedDepositsCount = 0;
880
-
881
- // Inputs processing loop.
882
- for (uint256 i = 0; i < inputsCount; i++) {
883
- (
884
- bytes32 outpointTxHash,
885
- uint32 outpointIndex,
886
- uint256 inputLength
887
- ) = parseTxInputAt(sweepTxInputVector, inputStartingIndex);
888
-
889
- Deposit.Request storage deposit = self.deposits[
890
- uint256(
891
- keccak256(abi.encodePacked(outpointTxHash, outpointIndex))
892
- )
893
- ];
894
-
895
- if (deposit.revealedAt != 0) {
896
- // If we entered here, that means the input was identified as
897
- // a revealed deposit.
898
- require(deposit.sweptAt == 0, "Deposit already swept");
899
-
900
- if (processedDepositsCount == info.depositors.length) {
901
- // If this condition is true, that means a deposit input
902
- // took place of an expected main UTXO input.
903
- // In other words, there is no expected main UTXO
904
- // input and all inputs come from valid, revealed deposits.
905
- revert(
906
- "Expected main UTXO not present in sweep transaction inputs"
907
- );
908
- }
909
-
910
- /* solhint-disable-next-line not-rely-on-time */
911
- deposit.sweptAt = uint32(block.timestamp);
912
-
913
- info.depositors[processedDepositsCount] = deposit.depositor;
914
- info.depositedAmounts[processedDepositsCount] = deposit.amount;
915
- info.inputsTotalValue += info.depositedAmounts[
916
- processedDepositsCount
917
- ];
918
- info.treasuryFees[processedDepositsCount] = deposit.treasuryFee;
919
-
920
- processedDepositsCount++;
921
- } else if (
922
- mainUtxoExpected != mainUtxoFound &&
923
- mainUtxo.txHash == outpointTxHash
924
- ) {
925
- // If we entered here, that means the input was identified as
926
- // the expected main UTXO.
927
- info.inputsTotalValue += mainUtxo.txOutputValue;
928
- mainUtxoFound = true;
929
-
930
- // Main UTXO used as an input, mark it as spent.
931
- spentMainUTXOs[
932
- uint256(
933
- keccak256(
934
- abi.encodePacked(outpointTxHash, outpointIndex)
935
- )
936
- )
937
- ] = true;
938
- } else {
939
- revert("Unknown input type");
940
- }
941
-
942
- // Make the `inputStartingIndex` pointing to the next input by
943
- // increasing it by current input's length.
944
- inputStartingIndex += inputLength;
945
- }
946
-
947
- // Construction of the input processing loop guarantees that:
948
- // `processedDepositsCount == info.depositors.length == info.depositedAmounts.length`
949
- // is always true at this point. We just use the first variable
950
- // to assert the total count of swept deposit is bigger than zero.
951
- require(
952
- processedDepositsCount > 0,
953
- "Sweep transaction must process at least one deposit"
954
- );
955
-
956
- // Assert the main UTXO was used as one of current sweep's inputs if
957
- // it was actually expected.
958
- require(
959
- mainUtxoExpected == mainUtxoFound,
960
- "Expected main UTXO not present in sweep transaction inputs"
961
- );
962
-
963
- return info;
964
- }
965
-
966
- /// @notice Parses a Bitcoin transaction input starting at the given index.
967
- /// @param inputVector Bitcoin transaction input vector
968
- /// @param inputStartingIndex Index the given input starts at
969
- /// @return outpointTxHash 32-byte hash of the Bitcoin transaction which is
970
- /// pointed in the given input's outpoint.
971
- /// @return outpointIndex 4-byte index of the Bitcoin transaction output
972
- /// which is pointed in the given input's outpoint.
973
- /// @return inputLength Byte length of the given input.
974
- /// @dev This function assumes vector's structure is valid so it must be
975
- /// validated using e.g. `BTCUtils.validateVin` function before it
976
- /// is passed here.
977
- function parseTxInputAt(
978
- bytes memory inputVector,
979
- uint256 inputStartingIndex
980
- )
981
- internal
982
- pure
983
- returns (
984
- bytes32 outpointTxHash,
985
- uint32 outpointIndex,
986
- uint256 inputLength
987
- )
988
- {
989
- outpointTxHash = inputVector.extractInputTxIdLeAt(inputStartingIndex);
990
-
991
- outpointIndex = BTCUtils.reverseUint32(
992
- uint32(inputVector.extractTxIndexLeAt(inputStartingIndex))
993
- );
994
-
995
- inputLength = inputVector.determineInputLengthAt(inputStartingIndex);
996
-
997
- return (outpointTxHash, outpointIndex, inputLength);
998
- }
999
-
1000
- /// @notice Determines the distribution of the sweep transaction fee
1001
- /// over swept deposits.
1002
- /// @param sweepTxInputsTotalValue Total value of all sweep transaction inputs.
1003
- /// @param sweepTxOutputValue Value of the sweep transaction output.
1004
- /// @param depositsCount Count of the deposits swept by the sweep transaction.
1005
- /// @return depositTxFee Transaction fee per deposit determined by evenly
1006
- /// spreading the divisible part of the sweep transaction fee
1007
- /// over all deposits.
1008
- /// @return depositTxFeeRemainder The indivisible part of the sweep
1009
- /// transaction fee than cannot be distributed over all deposits.
1010
- /// @dev It is up to the caller to decide how the remainder should be
1011
- /// counted in. This function only computes its value.
1012
- function sweepTxFeeDistribution(
1013
- uint256 sweepTxInputsTotalValue,
1014
- uint256 sweepTxOutputValue,
1015
- uint256 depositsCount
1016
- )
1017
- internal
1018
- pure
1019
- returns (uint256 depositTxFee, uint256 depositTxFeeRemainder)
1020
- {
1021
- // The sweep transaction fee is just the difference between inputs
1022
- // amounts sum and the output amount.
1023
- uint256 sweepTxFee = sweepTxInputsTotalValue - sweepTxOutputValue;
1024
- // Compute the indivisible remainder that remains after dividing the
1025
- // sweep transaction fee over all deposits evenly.
1026
- depositTxFeeRemainder = sweepTxFee % depositsCount;
1027
- // Compute the transaction fee per deposit by dividing the sweep
1028
- // transaction fee (reduced by the remainder) by the number of deposits.
1029
- depositTxFee = (sweepTxFee - depositTxFeeRemainder) / depositsCount;
1030
-
1031
- return (depositTxFee, depositTxFeeRemainder);
585
+ self.submitSweepProof(wallets, sweepTx, sweepProof, mainUtxo);
1032
586
  }
1033
587
 
1034
588
  /// @notice Submits a fraud challenge indicating that a UTXO being under
@@ -1133,11 +687,11 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1133
687
 
1134
688
  // Check that the UTXO key identifies a correctly spent UTXO.
1135
689
  require(
1136
- self.deposits[utxoKey].sweptAt > 0 || spentMainUTXOs[utxoKey],
690
+ self.deposits[utxoKey].sweptAt > 0 || self.spentMainUTXOs[utxoKey],
1137
691
  "Spent UTXO not found among correctly spent UTXOs"
1138
692
  );
1139
693
 
1140
- frauds.defeatChallenge(walletPublicKey, preimage, treasury);
694
+ frauds.defeatChallenge(walletPublicKey, preimage, self.treasury);
1141
695
  }
1142
696
 
1143
697
  /// @notice Notifies about defeat timeout for the given fraud challenge.
@@ -1359,7 +913,7 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1359
913
  txMaxFee
1360
914
  );
1361
915
 
1362
- bank.transferBalanceFrom(msg.sender, address(this), amount);
916
+ self.bank.transferBalanceFrom(msg.sender, address(this), amount);
1363
917
  }
1364
918
 
1365
919
  /// @notice Used by the wallet to prove the BTC redemption transaction
@@ -1423,7 +977,7 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1423
977
  bytes32 redemptionTxHash = BitcoinTx.validateProof(
1424
978
  redemptionTx,
1425
979
  redemptionProof,
1426
- proofDifficultyContext()
980
+ self.proofDifficultyContext()
1427
981
  );
1428
982
 
1429
983
  // Process the redemption transaction input. Specifically, check if it
@@ -1473,8 +1027,8 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1473
1027
 
1474
1028
  emit RedemptionsCompleted(walletPubKeyHash, redemptionTxHash);
1475
1029
 
1476
- bank.decreaseBalance(outputsInfo.totalBurnableValue);
1477
- bank.transferBalance(treasury, outputsInfo.totalTreasuryFee);
1030
+ self.bank.decreaseBalance(outputsInfo.totalBurnableValue);
1031
+ self.bank.transferBalance(self.treasury, outputsInfo.totalTreasuryFee);
1478
1032
  }
1479
1033
 
1480
1034
  /// @notice Checks whether an outbound Bitcoin transaction performed from
@@ -1530,7 +1084,7 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1530
1084
  );
1531
1085
 
1532
1086
  // Main UTXO used as an input, mark it as spent.
1533
- spentMainUTXOs[
1087
+ self.spentMainUTXOs[
1534
1088
  uint256(
1535
1089
  keccak256(
1536
1090
  abi.encodePacked(mainUtxo.txHash, mainUtxo.txOutputIndex)
@@ -1657,6 +1211,39 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1657
1211
  abi.encodePacked(hex"160014", walletPubKeyHash)
1658
1212
  );
1659
1213
 
1214
+ return
1215
+ processRedemptionTxOutputs(
1216
+ redemptionTxOutputVector,
1217
+ walletPubKeyHash,
1218
+ RedemptionTxOutputsProcessingInfo(
1219
+ outputStartingIndex,
1220
+ outputsCount,
1221
+ walletP2PKHScriptKeccak,
1222
+ walletP2WPKHScriptKeccak
1223
+ )
1224
+ );
1225
+ }
1226
+
1227
+ /// @notice Processes all outputs from the redemption transaction. Tries to
1228
+ /// identify output as a change output, pending redemption request
1229
+ // or reported redemption. Reverts if one of the outputs cannot be
1230
+ /// recognized properly. Marks each request as processed by removing
1231
+ /// them from `pendingRedemptions` mapping.
1232
+ /// @param redemptionTxOutputVector Bitcoin redemption transaction output
1233
+ /// vector. This function assumes vector's structure is valid so it
1234
+ /// must be validated using e.g. `BTCUtils.validateVout` function
1235
+ /// before it is passed here
1236
+ /// @param walletPubKeyHash 20-byte public key hash (computed using Bitcoin
1237
+ // HASH160 over the compressed ECDSA public key) of the wallet which
1238
+ /// performed the redemption transaction.
1239
+ /// @param processInfo RedemptionTxOutputsProcessingInfo identifying output
1240
+ /// starting index, the number of outputs and possible wallet change
1241
+ /// P2PKH and P2WPKH scripts.
1242
+ function processRedemptionTxOutputs(
1243
+ bytes memory redemptionTxOutputVector,
1244
+ bytes20 walletPubKeyHash,
1245
+ RedemptionTxOutputsProcessingInfo memory processInfo
1246
+ ) internal returns (RedemptionTxOutputsInfo memory resultInfo) {
1660
1247
  // Helper variable that counts the number of processed redemption
1661
1248
  // outputs. Redemptions can be either pending or reported as timed out.
1662
1249
  // TODO: Revisit the approach with redemptions count according to
@@ -1664,14 +1251,14 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1664
1251
  uint256 processedRedemptionsCount = 0;
1665
1252
 
1666
1253
  // Outputs processing loop.
1667
- for (uint256 i = 0; i < outputsCount; i++) {
1254
+ for (uint256 i = 0; i < processInfo.outputsCount; i++) {
1668
1255
  // TODO: Check if we can optimize gas costs by adding
1669
1256
  // `extractValueAt` and `extractHashAt` in `bitcoin-spv-sol`
1670
1257
  // in order to avoid allocating bytes in memory.
1671
1258
  uint256 outputLength = redemptionTxOutputVector
1672
- .determineOutputLengthAt(outputStartingIndex);
1259
+ .determineOutputLengthAt(processInfo.outputStartingIndex);
1673
1260
  bytes memory output = redemptionTxOutputVector.slice(
1674
- outputStartingIndex,
1261
+ processInfo.outputStartingIndex,
1675
1262
  outputLength
1676
1263
  );
1677
1264
 
@@ -1683,88 +1270,36 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1683
1270
  bytes memory outputScript = output.slice(8, output.length - 8);
1684
1271
 
1685
1272
  if (
1686
- info.changeValue == 0 &&
1687
- (keccak256(outputScript) == walletP2PKHScriptKeccak ||
1688
- keccak256(outputScript) == walletP2WPKHScriptKeccak) &&
1273
+ resultInfo.changeValue == 0 &&
1274
+ (keccak256(outputScript) ==
1275
+ processInfo.walletP2PKHScriptKeccak ||
1276
+ keccak256(outputScript) ==
1277
+ processInfo.walletP2WPKHScriptKeccak) &&
1689
1278
  outputValue > 0
1690
1279
  ) {
1691
1280
  // If we entered here, that means the change output with a
1692
1281
  // proper non-zero value was found.
1693
- info.changeIndex = uint32(i);
1694
- info.changeValue = outputValue;
1282
+ resultInfo.changeIndex = uint32(i);
1283
+ resultInfo.changeValue = outputValue;
1695
1284
  } else {
1696
1285
  // If we entered here, that the means the given output is
1697
- // supposed to represent a redemption. Build the redemption key
1698
- // to perform that check.
1699
- uint256 redemptionKey = uint256(
1700
- keccak256(abi.encodePacked(walletPubKeyHash, outputScript))
1701
- );
1702
-
1703
- if (pendingRedemptions[redemptionKey].requestedAt != 0) {
1704
- // If we entered here, that means the output was identified
1705
- // as a pending redemption request.
1706
- RedemptionRequest storage request = pendingRedemptions[
1707
- redemptionKey
1708
- ];
1709
- // Compute the request's redeemable amount as the requested
1710
- // amount reduced by the treasury fee. The request's
1711
- // minimal amount is then the redeemable amount reduced by
1712
- // the maximum transaction fee.
1713
- uint64 redeemableAmount = request.requestedAmount -
1714
- request.treasuryFee;
1715
- // Output value must fit between the request's redeemable
1716
- // and minimal amounts to be deemed valid.
1717
- require(
1718
- redeemableAmount - request.txMaxFee <= outputValue &&
1719
- outputValue <= redeemableAmount,
1720
- "Output value is not within the acceptable range of the pending request"
1721
- );
1722
- // Add the redeemable amount to the total burnable value
1723
- // the Bridge will use to decrease its balance in the Bank.
1724
- info.totalBurnableValue += redeemableAmount;
1725
- // Add the request's treasury fee to the total treasury fee
1726
- // value the Bridge will transfer to the treasury.
1727
- info.totalTreasuryFee += request.treasuryFee;
1728
- // Request was properly handled so remove its redemption
1729
- // key from the mapping to make it reusable for further
1730
- // requests.
1731
- delete pendingRedemptions[redemptionKey];
1732
-
1733
- processedRedemptionsCount++;
1734
- } else {
1735
- // If we entered here, the output is not a redemption
1736
- // request but there is still a chance the given output is
1737
- // related to a reported timed out redemption request.
1738
- // If so, check if the output value matches the request
1739
- // amount to confirm this is an overdue request fulfillment
1740
- // then bypass this output and process the subsequent
1741
- // ones. That also means the wallet was already punished
1742
- // for the inactivity. Otherwise, just revert.
1743
- RedemptionRequest storage request = timedOutRedemptions[
1744
- redemptionKey
1745
- ];
1746
-
1747
- require(
1748
- request.requestedAt != 0,
1749
- "Output is a non-requested redemption"
1750
- );
1751
-
1752
- uint64 redeemableAmount = request.requestedAmount -
1753
- request.treasuryFee;
1754
-
1755
- require(
1756
- redeemableAmount - request.txMaxFee <= outputValue &&
1757
- outputValue <= redeemableAmount,
1758
- "Output value is not within the acceptable range of the timed out request"
1286
+ // supposed to represent a redemption.
1287
+ (
1288
+ uint64 burnableValue,
1289
+ uint64 treasuryFee
1290
+ ) = processNonChangeRedemptionTxOutput(
1291
+ walletPubKeyHash,
1292
+ outputScript,
1293
+ outputValue
1759
1294
  );
1760
-
1761
- processedRedemptionsCount++;
1762
- }
1295
+ resultInfo.totalBurnableValue += burnableValue;
1296
+ resultInfo.totalTreasuryFee += treasuryFee;
1297
+ processedRedemptionsCount++;
1763
1298
  }
1764
1299
 
1765
1300
  // Make the `outputStartingIndex` pointing to the next output by
1766
1301
  // increasing it by current output's length.
1767
- outputStartingIndex += outputLength;
1302
+ processInfo.outputStartingIndex += outputLength;
1768
1303
  }
1769
1304
 
1770
1305
  // Protect against the cases when there is only a single change output
@@ -1774,8 +1309,100 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1774
1309
  processedRedemptionsCount > 0,
1775
1310
  "Redemption transaction must process at least one redemption"
1776
1311
  );
1312
+ }
1313
+
1314
+ /// @notice Processes a single redemption transaction output. Tries to
1315
+ /// identify output as a pending redemption request or reported
1316
+ /// redemption timeout. Output script passed to this function must
1317
+ /// not be the change output. Such output needs to be identified
1318
+ /// separately before calling this function.
1319
+ /// Reverts if output is neither requested pending redemption nor
1320
+ /// requested and reported timed-out redemption.
1321
+ /// This function also marks each pending request as processed by
1322
+ /// removing it from `pendingRedemptions` mapping.
1323
+ /// @param walletPubKeyHash 20-byte public key hash (computed using Bitcoin
1324
+ // HASH160 over the compressed ECDSA public key) of the wallet which
1325
+ /// performed the redemption transaction.
1326
+ /// @param outputScript Non-change output script to be processed
1327
+ /// @param outputValue Value of the output being processed
1328
+ /// @return burnableValue The value burnable as a result of processing this
1329
+ /// single redemption output. This value needs to be summed up with
1330
+ /// burnable values of all other outputs to evaluate total burnable
1331
+ /// value for the entire redemption transaction. This value is 0
1332
+ /// for a timed-out redemption request.
1333
+ /// @return treasuryFee The treasury fee from this single redemption output.
1334
+ /// This value needs to be summed up with treasury fees of all other
1335
+ /// outputs to evaluate the total treasury fee for the entire
1336
+ /// redemption transaction. This value is 0 for a timed-out
1337
+ /// redemption request.
1338
+ function processNonChangeRedemptionTxOutput(
1339
+ bytes20 walletPubKeyHash,
1340
+ bytes memory outputScript,
1341
+ uint64 outputValue
1342
+ ) internal returns (uint64 burnableValue, uint64 treasuryFee) {
1343
+ // This function should be called only if the given output is
1344
+ // supposed to represent a redemption. Build the redemption key
1345
+ // to perform that check.
1346
+ uint256 redemptionKey = uint256(
1347
+ keccak256(abi.encodePacked(walletPubKeyHash, outputScript))
1348
+ );
1349
+
1350
+ if (pendingRedemptions[redemptionKey].requestedAt != 0) {
1351
+ // If we entered here, that means the output was identified
1352
+ // as a pending redemption request.
1353
+ RedemptionRequest storage request = pendingRedemptions[
1354
+ redemptionKey
1355
+ ];
1356
+ // Compute the request's redeemable amount as the requested
1357
+ // amount reduced by the treasury fee. The request's
1358
+ // minimal amount is then the redeemable amount reduced by
1359
+ // the maximum transaction fee.
1360
+ uint64 redeemableAmount = request.requestedAmount -
1361
+ request.treasuryFee;
1362
+ // Output value must fit between the request's redeemable
1363
+ // and minimal amounts to be deemed valid.
1364
+ require(
1365
+ redeemableAmount - request.txMaxFee <= outputValue &&
1366
+ outputValue <= redeemableAmount,
1367
+ "Output value is not within the acceptable range of the pending request"
1368
+ );
1369
+ // Add the redeemable amount to the total burnable value
1370
+ // the Bridge will use to decrease its balance in the Bank.
1371
+ burnableValue = redeemableAmount;
1372
+ // Add the request's treasury fee to the total treasury fee
1373
+ // value the Bridge will transfer to the treasury.
1374
+ treasuryFee = request.treasuryFee;
1375
+ // Request was properly handled so remove its redemption
1376
+ // key from the mapping to make it reusable for further
1377
+ // requests.
1378
+ delete pendingRedemptions[redemptionKey];
1379
+ } else {
1380
+ // If we entered here, the output is not a redemption
1381
+ // request but there is still a chance the given output is
1382
+ // related to a reported timed out redemption request.
1383
+ // If so, check if the output value matches the request
1384
+ // amount to confirm this is an overdue request fulfillment
1385
+ // then bypass this output and process the subsequent
1386
+ // ones. That also means the wallet was already punished
1387
+ // for the inactivity. Otherwise, just revert.
1388
+ RedemptionRequest storage request = timedOutRedemptions[
1389
+ redemptionKey
1390
+ ];
1391
+
1392
+ require(
1393
+ request.requestedAt != 0,
1394
+ "Output is a non-requested redemption"
1395
+ );
1777
1396
 
1778
- return info;
1397
+ uint64 redeemableAmount = request.requestedAmount -
1398
+ request.treasuryFee;
1399
+
1400
+ require(
1401
+ redeemableAmount - request.txMaxFee <= outputValue &&
1402
+ outputValue <= redeemableAmount,
1403
+ "Output value is not within the acceptable range of the timed out request"
1404
+ );
1405
+ }
1779
1406
  }
1780
1407
 
1781
1408
  /// @notice Notifies that there is a pending redemption request associated
@@ -1855,7 +1482,7 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1855
1482
  emit RedemptionTimedOut(walletPubKeyHash, redeemerOutputScript);
1856
1483
 
1857
1484
  // Return the requested amount of tokens to the redeemer
1858
- bank.transferBalance(request.redeemer, request.requestedAmount);
1485
+ self.bank.transferBalance(request.redeemer, request.requestedAmount);
1859
1486
  }
1860
1487
 
1861
1488
  /// @notice Used by the wallet to prove the BTC moving funds transaction
@@ -1916,7 +1543,7 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1916
1543
  bytes32 movingFundsTxHash = BitcoinTx.validateProof(
1917
1544
  movingFundsTx,
1918
1545
  movingFundsProof,
1919
- proofDifficultyContext()
1546
+ self.proofDifficultyContext()
1920
1547
  );
1921
1548
 
1922
1549
  // Process the moving funds transaction input. Specifically, check if
@@ -2098,6 +1725,14 @@ contract Bridge is Ownable, EcdsaWalletOwner {
2098
1725
  return (targetWalletsHash, outputsTotalValue);
2099
1726
  }
2100
1727
 
1728
+ /// @return bank Address of the Bank the Bridge belongs to.
1729
+ /// @return relay Address of the Bitcoin relay providing the current Bitcoin
1730
+ /// network difficulty.
1731
+ function getContracts() external view returns (Bank bank, IRelay relay) {
1732
+ bank = self.bank;
1733
+ relay = self.relay;
1734
+ }
1735
+
2101
1736
  /// @notice Returns the current values of Bridge deposit parameters.
2102
1737
  /// @return depositDustThreshold The minimal amount that can be requested
2103
1738
  /// to deposit. Value of this parameter must take into account the
@@ -2111,13 +1746,30 @@ contract Bridge is Ownable, EcdsaWalletOwner {
2111
1746
  /// For example, if the treasury fee needs to be 2% of each deposit,
2112
1747
  /// the `depositTreasuryFeeDivisor` should be set to `50`
2113
1748
  /// because `1/50 = 0.02 = 2%`.
1749
+ /// @return depositTxMaxFee Maximum amount of BTC transaction fee that can
1750
+ /// be incurred by each swept deposit being part of the given sweep
1751
+ /// transaction. If the maximum BTC transaction fee is exceeded,
1752
+ /// such transaction is considered a fraud.
1753
+ /// @return treasury Address where the deposit treasury fees will be
1754
+ /// sent to. Treasury takes part in the operators rewarding process.
1755
+ /// @return txProofDifficultyFactor The number of confirmations on the
1756
+ /// Bitcoin chain required to successfully evaluate an SPV proof.
2114
1757
  function depositParameters()
2115
1758
  external
2116
1759
  view
2117
- returns (uint64 depositDustThreshold, uint64 depositTreasuryFeeDivisor)
1760
+ returns (
1761
+ uint64 depositDustThreshold,
1762
+ uint64 depositTreasuryFeeDivisor,
1763
+ uint64 depositTxMaxFee,
1764
+ address treasury,
1765
+ uint256 txProofDifficultyFactor
1766
+ )
2118
1767
  {
2119
1768
  depositDustThreshold = self.depositDustThreshold;
2120
1769
  depositTreasuryFeeDivisor = self.depositTreasuryFeeDivisor;
1770
+ depositTxMaxFee = self.depositTxMaxFee;
1771
+ treasury = self.treasury;
1772
+ txProofDifficultyFactor = self.txProofDifficultyFactor;
2121
1773
  }
2122
1774
 
2123
1775
  /// @notice Indicates if the vault with the given address is trusted or not.
@@ -2145,4 +1797,14 @@ contract Bridge is Ownable, EcdsaWalletOwner {
2145
1797
  // TODO: rename to getDeposit?
2146
1798
  return self.deposits[depositKey];
2147
1799
  }
1800
+
1801
+ /// @notice Collection of main UTXOs that are honestly spent indexed by
1802
+ /// keccak256(fundingTxHash | fundingOutputIndex). The fundingTxHash
1803
+ /// is bytes32 (ordered as in Bitcoin internally) and
1804
+ /// fundingOutputIndex an uint32. A main UTXO is considered honestly
1805
+ /// spent if it was used as an input of a transaction that have been
1806
+ /// proven in the Bridge.
1807
+ function spentMainUTXOs(uint256 utxoKey) external view returns (bool) {
1808
+ return self.spentMainUTXOs[utxoKey];
1809
+ }
2148
1810
  }