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

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 (38) 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/{4718d6e944ad9d1fc247efda870cf51a.json → 518efc6faeb6612766a5b3fef24e13ad.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 +3 -89
  9. package/build/contracts/bridge/Bridge.sol/Bridge.dbg.json +1 -1
  10. package/build/contracts/bridge/Bridge.sol/Bridge.json +165 -157
  11. package/build/contracts/bridge/BridgeState.sol/BridgeState.dbg.json +1 -1
  12. package/build/contracts/bridge/BridgeState.sol/BridgeState.json +2 -2
  13. package/build/contracts/bridge/Deposit.sol/Deposit.dbg.json +1 -1
  14. package/build/contracts/bridge/Deposit.sol/Deposit.json +2 -2
  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/IRelay.sol/IRelay.dbg.json +1 -1
  19. package/build/contracts/bridge/MovingFunds.sol/MovingFunds.dbg.json +4 -0
  20. package/build/contracts/bridge/MovingFunds.sol/MovingFunds.json +48 -0
  21. package/build/contracts/bridge/Redeem.sol/OutboundTx.dbg.json +4 -0
  22. package/build/contracts/bridge/Redeem.sol/OutboundTx.json +10 -0
  23. package/build/contracts/bridge/Redeem.sol/Redeem.dbg.json +4 -0
  24. package/build/contracts/bridge/Redeem.sol/Redeem.json +110 -0
  25. package/build/contracts/bridge/Sweep.sol/Sweep.dbg.json +1 -1
  26. package/build/contracts/bridge/Sweep.sol/Sweep.json +4 -22
  27. package/build/contracts/bridge/VendingMachine.sol/VendingMachine.dbg.json +1 -1
  28. package/build/contracts/bridge/Wallets.sol/Wallets.dbg.json +1 -1
  29. package/build/contracts/bridge/Wallets.sol/Wallets.json +2 -2
  30. package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
  31. package/build/contracts/vault/IVault.sol/IVault.dbg.json +1 -1
  32. package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +1 -1
  33. package/contracts/bridge/BitcoinTx.sol +3 -3
  34. package/contracts/bridge/Bridge.sol +131 -910
  35. package/contracts/bridge/BridgeState.sol +68 -0
  36. package/contracts/bridge/MovingFunds.sol +280 -0
  37. package/contracts/bridge/Redeem.sol +849 -0
  38. package/package.json +1 -1
@@ -17,6 +17,7 @@ pragma solidity ^0.8.9;
17
17
 
18
18
  import "./IRelay.sol";
19
19
  import "./Deposit.sol";
20
+ import "./Redeem.sol";
20
21
 
21
22
  import "../bank/Bank.sol";
22
23
 
@@ -75,6 +76,73 @@ library BridgeState {
75
76
  /// responsibility - anyone can approve their Bank balance to any
76
77
  /// address.
77
78
  mapping(address => bool) isVaultTrusted;
79
+ /// TODO: Make it governable.
80
+ /// @notice Maximum amount of the total BTC transaction fee that is
81
+ /// acceptable in a single moving funds transaction.
82
+ /// @dev This is a TOTAL max fee for the moving funds transaction. Note
83
+ /// that `depositTxMaxFee` is per single deposit and `redemptionTxMaxFee`
84
+ /// if per single redemption. `movingFundsTxMaxTotalFee` is a total
85
+ /// fee for the entire transaction.
86
+ uint64 movingFundsTxMaxTotalFee;
87
+ /// TODO: Make it governable.
88
+ /// @notice The minimal amount that can be requested for redemption.
89
+ /// Value of this parameter must take into account the value of
90
+ /// `redemptionTreasuryFeeDivisor` and `redemptionTxMaxFee`
91
+ /// parameters in order to make requests that can incur the
92
+ /// treasury and transaction fee and still satisfy the redeemer.
93
+ uint64 redemptionDustThreshold;
94
+ /// TODO: Make it governable.
95
+ /// @notice Divisor used to compute the treasury fee taken from each
96
+ /// redemption request and transferred to the treasury upon
97
+ /// successful request finalization. That fee is computed as follows:
98
+ /// `treasuryFee = requestedAmount / redemptionTreasuryFeeDivisor`
99
+ /// For example, if the treasury fee needs to be 2% of each
100
+ /// redemption request, the `redemptionTreasuryFeeDivisor` should
101
+ /// be set to `50` because `1/50 = 0.02 = 2%`.
102
+ uint64 redemptionTreasuryFeeDivisor;
103
+ /// TODO: Make it governable.
104
+ /// @notice Maximum amount of BTC transaction fee that can be incurred by
105
+ /// each redemption request being part of the given redemption
106
+ /// transaction. If the maximum BTC transaction fee is exceeded, such
107
+ /// transaction is considered a fraud.
108
+ /// @dev This is a per-redemption output max fee for the redemption transaction.
109
+ uint64 redemptionTxMaxFee;
110
+ /// TODO: Make it governable.
111
+ /// @notice Time after which the redemption request can be reported as
112
+ /// timed out. It is counted from the moment when the redemption
113
+ /// request was created via `requestRedemption` call. Reported
114
+ /// timed out requests are cancelled and locked TBTC is returned
115
+ /// to the redeemer in full amount.
116
+ uint256 redemptionTimeout;
117
+ /// @notice Collection of all pending redemption requests indexed by
118
+ /// redemption key built as
119
+ /// keccak256(walletPubKeyHash | redeemerOutputScript). The
120
+ /// walletPubKeyHash is the 20-byte wallet's public key hash
121
+ /// (computed using Bitcoin HASH160 over the compressed ECDSA
122
+ /// public key) and redeemerOutputScript is a Bitcoin script
123
+ /// (P2PKH, P2WPKH, P2SH or P2WSH) that will be used to lock
124
+ /// redeemed BTC as requested by the redeemer. Requests are added
125
+ /// to this mapping by the `requestRedemption` method (duplicates
126
+ /// not allowed) and are removed by one of the following methods:
127
+ /// - `submitRedemptionProof` in case the request was handled
128
+ /// successfully
129
+ /// - `notifyRedemptionTimeout` in case the request was reported
130
+ /// to be timed out
131
+ mapping(uint256 => Redeem.RedemptionRequest) pendingRedemptions;
132
+ /// @notice Collection of all timed out redemptions requests indexed by
133
+ /// redemption key built as
134
+ /// keccak256(walletPubKeyHash | redeemerOutputScript). The
135
+ /// walletPubKeyHash is the 20-byte wallet's public key hash
136
+ /// (computed using Bitcoin HASH160 over the compressed ECDSA
137
+ /// public key) and redeemerOutputScript is the Bitcoin script
138
+ /// (P2PKH, P2WPKH, P2SH or P2WSH) that is involved in the timed
139
+ /// out request. Timed out requests are stored in this mapping to
140
+ /// avoid slashing the wallets multiple times for the same timeout.
141
+ /// Only one method can add to this mapping:
142
+ /// - `notifyRedemptionTimeout` which puts the redemption key
143
+ /// to this mapping basing on a timed out request stored
144
+ /// previously in `pendingRedemptions` mapping.
145
+ mapping(uint256 => Redeem.RedemptionRequest) timedOutRedemptions;
78
146
  /// @notice Collection of main UTXOs that are honestly spent indexed by
79
147
  /// keccak256(fundingTxHash | fundingOutputIndex). The fundingTxHash
80
148
  /// is bytes32 (ordered as in Bitcoin internally) and
@@ -0,0 +1,280 @@
1
+ // SPDX-License-Identifier: MIT
2
+
3
+ // ██████████████ ▐████▌ ██████████████
4
+ // ██████████████ ▐████▌ ██████████████
5
+ // ▐████▌ ▐████▌
6
+ // ▐████▌ ▐████▌
7
+ // ██████████████ ▐████▌ ██████████████
8
+ // ██████████████ ▐████▌ ██████████████
9
+ // ▐████▌ ▐████▌
10
+ // ▐████▌ ▐████▌
11
+ // ▐████▌ ▐████▌
12
+ // ▐████▌ ▐████▌
13
+ // ▐████▌ ▐████▌
14
+ // ▐████▌ ▐████▌
15
+
16
+ pragma solidity ^0.8.9;
17
+
18
+ import {BTCUtils} from "@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol";
19
+ import {BytesLib} from "@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol";
20
+
21
+ import "./BitcoinTx.sol";
22
+ import "./BridgeState.sol";
23
+ import "./Redeem.sol";
24
+
25
+ library MovingFunds {
26
+ using BridgeState for BridgeState.Storage;
27
+ using Wallets for Wallets.Data;
28
+
29
+ using BTCUtils for bytes;
30
+ using BytesLib for bytes;
31
+
32
+ event MovingFundsCompleted(
33
+ bytes20 walletPubKeyHash,
34
+ bytes32 movingFundsTxHash
35
+ );
36
+
37
+ /// @notice Used by the wallet to prove the BTC moving funds transaction
38
+ /// and to make the necessary state changes. Moving funds is only
39
+ /// accepted if it satisfies SPV proof.
40
+ ///
41
+ /// The function validates the moving funds transaction structure
42
+ /// by checking if it actually spends the main UTXO of the declared
43
+ /// wallet and locks the value on the pre-committed target wallets
44
+ /// using a reasonable transaction fee. If all preconditions are
45
+ /// met, this functions closes the source wallet.
46
+ ///
47
+ /// It is possible to prove the given moving funds transaction only
48
+ /// one time.
49
+ /// @param movingFundsTx Bitcoin moving funds transaction data
50
+ /// @param movingFundsProof Bitcoin moving funds proof data
51
+ /// @param mainUtxo Data of the wallet's main UTXO, as currently known on
52
+ /// the Ethereum chain
53
+ /// @param walletPubKeyHash 20-byte public key hash (computed using Bitcoin
54
+ /// HASH160 over the compressed ECDSA public key) of the wallet
55
+ /// which performed the moving funds transaction
56
+ /// @dev Requirements:
57
+ /// - `movingFundsTx` components must match the expected structure. See
58
+ /// `BitcoinTx.Info` docs for reference. Their values must exactly
59
+ /// correspond to appropriate Bitcoin transaction fields to produce
60
+ /// a provable transaction hash.
61
+ /// - The `movingFundsTx` should represent a Bitcoin transaction with
62
+ /// exactly 1 input that refers to the wallet's main UTXO. That
63
+ /// transaction should have 1..n outputs corresponding to the
64
+ /// pre-committed target wallets. Outputs must be ordered in the
65
+ /// same way as their corresponding target wallets are ordered
66
+ /// within the target wallets commitment.
67
+ /// - `movingFundsProof` components must match the expected structure.
68
+ /// See `BitcoinTx.Proof` docs for reference. The `bitcoinHeaders`
69
+ /// field must contain a valid number of block headers, not less
70
+ /// than the `txProofDifficultyFactor` contract constant.
71
+ /// - `mainUtxo` components must point to the recent main UTXO
72
+ /// of the given wallet, as currently known on the Ethereum chain.
73
+ /// Additionally, the recent main UTXO on Ethereum must be set.
74
+ /// - `walletPubKeyHash` must be connected with the main UTXO used
75
+ /// as transaction single input.
76
+ /// - The wallet that `walletPubKeyHash` points to must be in the
77
+ /// MovingFunds state.
78
+ /// - The target wallets commitment must be submitted by the wallet
79
+ /// that `walletPubKeyHash` points to.
80
+ /// - The total Bitcoin transaction fee must be lesser or equal
81
+ /// to `movingFundsTxMaxTotalFee` governable parameter.
82
+ function submitMovingFundsProof(
83
+ BridgeState.Storage storage self,
84
+ Wallets.Data storage wallets,
85
+ BitcoinTx.Info calldata movingFundsTx,
86
+ BitcoinTx.Proof calldata movingFundsProof,
87
+ BitcoinTx.UTXO calldata mainUtxo,
88
+ bytes20 walletPubKeyHash
89
+ ) external {
90
+ // The actual transaction proof is performed here. After that point, we
91
+ // can assume the transaction happened on Bitcoin chain and has
92
+ // a sufficient number of confirmations as determined by
93
+ // `txProofDifficultyFactor` constant.
94
+ bytes32 movingFundsTxHash = BitcoinTx.validateProof(
95
+ movingFundsTx,
96
+ movingFundsProof,
97
+ self.proofDifficultyContext()
98
+ );
99
+
100
+ // Process the moving funds transaction input. Specifically, check if
101
+ // it refers to the expected wallet's main UTXO.
102
+ OutboundTx.processWalletOutboundTxInput(
103
+ self,
104
+ wallets,
105
+ movingFundsTx.inputVector,
106
+ mainUtxo,
107
+ walletPubKeyHash
108
+ );
109
+
110
+ (
111
+ bytes32 targetWalletsHash,
112
+ uint256 outputsTotalValue
113
+ ) = processMovingFundsTxOutputs(movingFundsTx.outputVector);
114
+
115
+ require(
116
+ mainUtxo.txOutputValue - outputsTotalValue <=
117
+ self.movingFundsTxMaxTotalFee,
118
+ "Transaction fee is too high"
119
+ );
120
+
121
+ wallets.notifyFundsMoved(walletPubKeyHash, targetWalletsHash);
122
+
123
+ emit MovingFundsCompleted(walletPubKeyHash, movingFundsTxHash);
124
+ }
125
+
126
+ /// @notice Processes the moving funds Bitcoin transaction output vector
127
+ /// and extracts information required for further processing.
128
+ /// @param movingFundsTxOutputVector Bitcoin moving funds transaction output
129
+ /// vector. This function assumes vector's structure is valid so it
130
+ /// must be validated using e.g. `BTCUtils.validateVout` function
131
+ /// before it is passed here
132
+ /// @return targetWalletsHash keccak256 hash over the list of actual
133
+ /// target wallets used in the transaction.
134
+ /// @return outputsTotalValue Sum of all outputs values.
135
+ /// @dev Requirements:
136
+ /// - The `movingFundsTxOutputVector` must be parseable, i.e. must
137
+ /// be validated by the caller as stated in their parameter doc.
138
+ /// - Each output must refer to a 20-byte public key hash.
139
+ /// - The total outputs value must be evenly divided over all outputs.
140
+ function processMovingFundsTxOutputs(bytes memory movingFundsTxOutputVector)
141
+ internal
142
+ view
143
+ returns (bytes32 targetWalletsHash, uint256 outputsTotalValue)
144
+ {
145
+ // Determining the total number of Bitcoin transaction outputs in
146
+ // the same way as for number of inputs. See `BitcoinTx.outputVector`
147
+ // docs for more details.
148
+ (
149
+ uint256 outputsCompactSizeUintLength,
150
+ uint256 outputsCount
151
+ ) = movingFundsTxOutputVector.parseVarInt();
152
+
153
+ // To determine the first output starting index, we must jump over
154
+ // the compactSize uint which prepends the output vector. One byte
155
+ // must be added because `BtcUtils.parseVarInt` does not include
156
+ // compactSize uint tag in the returned length.
157
+ //
158
+ // For >= 0 && <= 252, `BTCUtils.determineVarIntDataLengthAt`
159
+ // returns `0`, so we jump over one byte of compactSize uint.
160
+ //
161
+ // For >= 253 && <= 0xffff there is `0xfd` tag,
162
+ // `BTCUtils.determineVarIntDataLengthAt` returns `2` (no
163
+ // tag byte included) so we need to jump over 1+2 bytes of
164
+ // compactSize uint.
165
+ //
166
+ // Please refer `BTCUtils` library and compactSize uint
167
+ // docs in `BitcoinTx` library for more details.
168
+ uint256 outputStartingIndex = 1 + outputsCompactSizeUintLength;
169
+
170
+ bytes20[] memory targetWallets = new bytes20[](outputsCount);
171
+ uint64[] memory outputsValues = new uint64[](outputsCount);
172
+
173
+ // Outputs processing loop.
174
+ for (uint256 i = 0; i < outputsCount; i++) {
175
+ uint256 outputLength = movingFundsTxOutputVector
176
+ .determineOutputLengthAt(outputStartingIndex);
177
+
178
+ bytes memory output = movingFundsTxOutputVector.slice(
179
+ outputStartingIndex,
180
+ outputLength
181
+ );
182
+
183
+ // Extract the output script payload.
184
+ bytes memory targetWalletPubKeyHashBytes = output.extractHash();
185
+ // Output script payload must refer to a known wallet public key
186
+ // hash which is always 20-byte.
187
+ require(
188
+ targetWalletPubKeyHashBytes.length == 20,
189
+ "Target wallet public key hash must have 20 bytes"
190
+ );
191
+
192
+ bytes20 targetWalletPubKeyHash = targetWalletPubKeyHashBytes
193
+ .slice20(0);
194
+
195
+ // The next step is making sure that the 20-byte public key hash
196
+ // is actually used in the right context of a P2PKH or P2WPKH
197
+ // output. To do so, we must extract the full script from the output
198
+ // and compare with the expected P2PKH and P2WPKH scripts
199
+ // referring to that 20-byte public key hash. The output consists
200
+ // of an 8-byte value and a variable length script. To extract the
201
+ // script we slice the output starting from 9th byte until the end.
202
+ bytes32 outputScriptKeccak = keccak256(
203
+ output.slice(8, output.length - 8)
204
+ );
205
+ // Build the expected P2PKH script which has the following byte
206
+ // format: <0x1976a914> <20-byte PKH> <0x88ac>. According to
207
+ // https://en.bitcoin.it/wiki/Script#Opcodes this translates to:
208
+ // - 0x19: Byte length of the entire script
209
+ // - 0x76: OP_DUP
210
+ // - 0xa9: OP_HASH160
211
+ // - 0x14: Byte length of the public key hash
212
+ // - 0x88: OP_EQUALVERIFY
213
+ // - 0xac: OP_CHECKSIG
214
+ // which matches the P2PKH structure as per:
215
+ // https://en.bitcoin.it/wiki/Transaction#Pay-to-PubkeyHash
216
+ bytes32 targetWalletP2PKHScriptKeccak = keccak256(
217
+ abi.encodePacked(
218
+ hex"1976a914",
219
+ targetWalletPubKeyHash,
220
+ hex"88ac"
221
+ )
222
+ );
223
+ // Build the expected P2WPKH script which has the following format:
224
+ // <0x160014> <20-byte PKH>. According to
225
+ // https://en.bitcoin.it/wiki/Script#Opcodes this translates to:
226
+ // - 0x16: Byte length of the entire script
227
+ // - 0x00: OP_0
228
+ // - 0x14: Byte length of the public key hash
229
+ // which matches the P2WPKH structure as per:
230
+ // https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#P2WPKH
231
+ bytes32 targetWalletP2WPKHScriptKeccak = keccak256(
232
+ abi.encodePacked(hex"160014", targetWalletPubKeyHash)
233
+ );
234
+ // Make sure the actual output script matches either the P2PKH
235
+ // or P2WPKH format.
236
+ require(
237
+ outputScriptKeccak == targetWalletP2PKHScriptKeccak ||
238
+ outputScriptKeccak == targetWalletP2WPKHScriptKeccak,
239
+ "Output must be P2PKH or P2WPKH"
240
+ );
241
+
242
+ // Add the wallet public key hash to the list that will be used
243
+ // to build the result list hash. There is no need to check if
244
+ // given output is a change here because the actual target wallet
245
+ // list must be exactly the same as the pre-committed target wallet
246
+ // list which is guaranteed to be valid.
247
+ targetWallets[i] = targetWalletPubKeyHash;
248
+
249
+ // Extract the value from given output.
250
+ outputsValues[i] = output.extractValue();
251
+ outputsTotalValue += outputsValues[i];
252
+
253
+ // Make the `outputStartingIndex` pointing to the next output by
254
+ // increasing it by current output's length.
255
+ outputStartingIndex += outputLength;
256
+ }
257
+
258
+ // Compute the indivisible remainder that remains after dividing the
259
+ // outputs total value over all outputs evenly.
260
+ uint256 outputsTotalValueRemainder = outputsTotalValue % outputsCount;
261
+ // Compute the minimum allowed output value by dividing the outputs
262
+ // total value (reduced by the remainder) by the number of outputs.
263
+ uint256 minOutputValue = (outputsTotalValue -
264
+ outputsTotalValueRemainder) / outputsCount;
265
+ // Maximum possible value is the minimum value with the remainder included.
266
+ uint256 maxOutputValue = minOutputValue + outputsTotalValueRemainder;
267
+
268
+ for (uint256 i = 0; i < outputsCount; i++) {
269
+ require(
270
+ minOutputValue <= outputsValues[i] &&
271
+ outputsValues[i] <= maxOutputValue,
272
+ "Transaction amount is not distributed evenly"
273
+ );
274
+ }
275
+
276
+ targetWalletsHash = keccak256(abi.encodePacked(targetWallets));
277
+
278
+ return (targetWalletsHash, outputsTotalValue);
279
+ }
280
+ }