@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.
- package/artifacts/TBTC.json +3 -3
- package/artifacts/TBTCToken.json +3 -3
- package/artifacts/VendingMachine.json +10 -10
- package/artifacts/solcInputs/{d9d0873fb324e55f80390065c7fe0dff.json → a67a2c11233ce411fb55a9f369ced323.json} +9 -3
- package/build/contracts/GovernanceUtils.sol/GovernanceUtils.dbg.json +1 -1
- package/build/contracts/bank/Bank.sol/Bank.dbg.json +1 -1
- package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.dbg.json +1 -1
- package/build/contracts/bridge/Bridge.sol/Bridge.dbg.json +1 -1
- package/build/contracts/bridge/Bridge.sol/Bridge.json +234 -82
- package/build/contracts/bridge/Bridge.sol/IRelay.dbg.json +1 -1
- package/build/contracts/bridge/BridgeState.sol/BridgeState.dbg.json +4 -0
- package/build/contracts/bridge/BridgeState.sol/BridgeState.json +10 -0
- package/build/contracts/bridge/Deposit.sol/Deposit.dbg.json +4 -0
- package/build/contracts/bridge/Deposit.sol/Deposit.json +72 -0
- package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.dbg.json +1 -1
- package/build/contracts/bridge/Frauds.sol/Frauds.dbg.json +1 -1
- package/build/contracts/bridge/Frauds.sol/Frauds.json +2 -2
- package/build/contracts/bridge/VendingMachine.sol/VendingMachine.dbg.json +1 -1
- package/build/contracts/bridge/Wallets.sol/Wallets.dbg.json +1 -1
- package/build/contracts/bridge/Wallets.sol/Wallets.json +2 -2
- package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
- package/build/contracts/vault/IVault.sol/IVault.dbg.json +1 -1
- package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +1 -1
- package/contracts/bridge/Bridge.sol +439 -278
- package/contracts/bridge/BridgeState.sol +54 -0
- package/contracts/bridge/Deposit.sol +247 -0
- package/contracts/bridge/Wallets.sol +89 -19
- 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
|
|
63
|
-
using
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
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
|
|
142
|
+
Bank public bank;
|
|
181
143
|
|
|
182
144
|
/// TODO: Make it governable.
|
|
183
145
|
/// @notice Handle to the Bitcoin relay.
|
|
184
|
-
IRelay public
|
|
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
|
|
151
|
+
address public treasury;
|
|
190
152
|
|
|
191
|
-
|
|
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
|
-
///
|
|
250
|
-
///
|
|
251
|
-
///
|
|
252
|
-
///
|
|
253
|
-
///
|
|
254
|
-
///
|
|
255
|
-
///
|
|
256
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
840
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
1599
|
-
//
|
|
1600
|
-
|
|
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
|
|
1650
|
-
///
|
|
1651
|
-
///
|
|
1652
|
-
///
|
|
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
|
|
1661
|
-
function
|
|
1662
|
-
bytes memory
|
|
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
|
|
1542
|
+
// Assert that the single outbound transaction input actually
|
|
1686
1543
|
// refers to the wallet's main UTXO.
|
|
1687
1544
|
(
|
|
1688
|
-
bytes32
|
|
1689
|
-
uint32
|
|
1690
|
-
) =
|
|
1545
|
+
bytes32 outpointTxHash,
|
|
1546
|
+
uint32 outpointIndex
|
|
1547
|
+
) = parseWalletOutboundTxInput(walletOutboundTxInputVector);
|
|
1691
1548
|
require(
|
|
1692
|
-
mainUtxo.txHash ==
|
|
1693
|
-
mainUtxo.txOutputIndex ==
|
|
1694
|
-
"
|
|
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
|
|
1708
|
-
///
|
|
1709
|
-
/// index from its outpoint.
|
|
1710
|
-
///
|
|
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
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
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) =
|
|
1589
|
+
(, uint256 inputsCount) = walletOutboundTxInputVector.parseVarInt();
|
|
1732
1590
|
require(
|
|
1733
1591
|
inputsCount == 1,
|
|
1734
|
-
"
|
|
1592
|
+
"Outbound transaction must have a single input"
|
|
1735
1593
|
);
|
|
1736
1594
|
|
|
1737
|
-
bytes memory input =
|
|
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
|
|
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
|
}
|