@keep-network/tbtc-v2 0.1.1-dev.37 → 0.1.1-dev.38
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/{e22260bedca047fea8676977de0ef137.json → fbc4b7a5a9816c01bbe8daca114c1439.json} +6 -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 +60 -68
- package/build/contracts/bridge/BridgeState.sol/BridgeState.dbg.json +1 -1
- package/build/contracts/bridge/BridgeState.sol/BridgeState.json +2 -2
- package/build/contracts/bridge/Deposit.sol/Deposit.dbg.json +1 -1
- package/build/contracts/bridge/Deposit.sol/Deposit.json +2 -2
- 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/IRelay.sol/IRelay.dbg.json +1 -1
- package/build/contracts/bridge/MovingFunds.sol/MovingFunds.dbg.json +4 -0
- package/build/contracts/bridge/MovingFunds.sol/MovingFunds.json +64 -0
- package/build/contracts/bridge/Redeem.sol/OutboundTx.dbg.json +1 -1
- package/build/contracts/bridge/Redeem.sol/OutboundTx.json +2 -2
- package/build/contracts/bridge/Redeem.sol/Redeem.dbg.json +1 -1
- package/build/contracts/bridge/Redeem.sol/Redeem.json +6 -6
- package/build/contracts/bridge/Sweep.sol/Sweep.dbg.json +1 -1
- package/build/contracts/bridge/Sweep.sol/Sweep.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/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 +20 -194
- package/contracts/bridge/BridgeState.sol +8 -0
- package/contracts/bridge/MovingFunds.sol +280 -0
- package/package.json +1 -1
|
@@ -31,6 +31,7 @@ import "./BitcoinTx.sol";
|
|
|
31
31
|
import "./EcdsaLib.sol";
|
|
32
32
|
import "./Wallets.sol";
|
|
33
33
|
import "./Frauds.sol";
|
|
34
|
+
import "./MovingFunds.sol";
|
|
34
35
|
|
|
35
36
|
import "../bank/Bank.sol";
|
|
36
37
|
|
|
@@ -63,6 +64,7 @@ contract Bridge is Ownable, EcdsaWalletOwner {
|
|
|
63
64
|
using Deposit for BridgeState.Storage;
|
|
64
65
|
using Sweep for BridgeState.Storage;
|
|
65
66
|
using Redeem for BridgeState.Storage;
|
|
67
|
+
using MovingFunds for BridgeState.Storage;
|
|
66
68
|
using Frauds for Frauds.Data;
|
|
67
69
|
using Wallets for Wallets.Data;
|
|
68
70
|
|
|
@@ -72,15 +74,6 @@ contract Bridge is Ownable, EcdsaWalletOwner {
|
|
|
72
74
|
|
|
73
75
|
BridgeState.Storage internal self;
|
|
74
76
|
|
|
75
|
-
/// TODO: Make it governable.
|
|
76
|
-
/// @notice Maximum amount of the total BTC transaction fee that is
|
|
77
|
-
/// acceptable in a single moving funds transaction.
|
|
78
|
-
/// @dev This is a TOTAL max fee for the moving funds transaction. Note that
|
|
79
|
-
/// `depositTxMaxFee` is per single deposit and `redemptionTxMaxFee`
|
|
80
|
-
/// if per single redemption. `movingFundsTxMaxTotalFee` is a total fee
|
|
81
|
-
/// for the entire transaction.
|
|
82
|
-
uint64 public movingFundsTxMaxTotalFee;
|
|
83
|
-
|
|
84
77
|
/// @notice Contains parameters related to frauds and the collection of all
|
|
85
78
|
/// submitted fraud challenges.
|
|
86
79
|
Frauds.Data internal frauds;
|
|
@@ -214,7 +207,7 @@ contract Bridge is Ownable, EcdsaWalletOwner {
|
|
|
214
207
|
self.redemptionTreasuryFeeDivisor = 2000; // 1/2000 == 5bps == 0.05% == 0.0005
|
|
215
208
|
self.redemptionTxMaxFee = 10000; // 10000 satoshi
|
|
216
209
|
self.redemptionTimeout = 172800; // 48 hours
|
|
217
|
-
movingFundsTxMaxTotalFee = 10000; // 10000 satoshi
|
|
210
|
+
self.movingFundsTxMaxTotalFee = 10000; // 10000 satoshi
|
|
218
211
|
|
|
219
212
|
// TODO: Revisit initial values.
|
|
220
213
|
frauds.setSlashingAmount(10000 * 1e18); // 10000 T
|
|
@@ -839,195 +832,13 @@ contract Bridge is Ownable, EcdsaWalletOwner {
|
|
|
839
832
|
BitcoinTx.UTXO calldata mainUtxo,
|
|
840
833
|
bytes20 walletPubKeyHash
|
|
841
834
|
) external {
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
// a sufficient number of confirmations as determined by
|
|
845
|
-
// `txProofDifficultyFactor` constant.
|
|
846
|
-
bytes32 movingFundsTxHash = BitcoinTx.validateProof(
|
|
835
|
+
self.submitMovingFundsProof(
|
|
836
|
+
wallets,
|
|
847
837
|
movingFundsTx,
|
|
848
838
|
movingFundsProof,
|
|
849
|
-
self.proofDifficultyContext()
|
|
850
|
-
);
|
|
851
|
-
|
|
852
|
-
// Process the moving funds transaction input. Specifically, check if
|
|
853
|
-
// it refers to the expected wallet's main UTXO.
|
|
854
|
-
OutboundTx.processWalletOutboundTxInput(
|
|
855
|
-
self,
|
|
856
|
-
wallets,
|
|
857
|
-
movingFundsTx.inputVector,
|
|
858
839
|
mainUtxo,
|
|
859
840
|
walletPubKeyHash
|
|
860
841
|
);
|
|
861
|
-
|
|
862
|
-
(
|
|
863
|
-
bytes32 targetWalletsHash,
|
|
864
|
-
uint256 outputsTotalValue
|
|
865
|
-
) = processMovingFundsTxOutputs(movingFundsTx.outputVector);
|
|
866
|
-
|
|
867
|
-
require(
|
|
868
|
-
mainUtxo.txOutputValue - outputsTotalValue <=
|
|
869
|
-
movingFundsTxMaxTotalFee,
|
|
870
|
-
"Transaction fee is too high"
|
|
871
|
-
);
|
|
872
|
-
|
|
873
|
-
wallets.notifyFundsMoved(walletPubKeyHash, targetWalletsHash);
|
|
874
|
-
|
|
875
|
-
emit MovingFundsCompleted(walletPubKeyHash, movingFundsTxHash);
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
/// @notice Processes the moving funds Bitcoin transaction output vector
|
|
879
|
-
/// and extracts information required for further processing.
|
|
880
|
-
/// @param movingFundsTxOutputVector Bitcoin moving funds transaction output
|
|
881
|
-
/// vector. This function assumes vector's structure is valid so it
|
|
882
|
-
/// must be validated using e.g. `BTCUtils.validateVout` function
|
|
883
|
-
/// before it is passed here
|
|
884
|
-
/// @return targetWalletsHash keccak256 hash over the list of actual
|
|
885
|
-
/// target wallets used in the transaction.
|
|
886
|
-
/// @return outputsTotalValue Sum of all outputs values.
|
|
887
|
-
/// @dev Requirements:
|
|
888
|
-
/// - The `movingFundsTxOutputVector` must be parseable, i.e. must
|
|
889
|
-
/// be validated by the caller as stated in their parameter doc.
|
|
890
|
-
/// - Each output must refer to a 20-byte public key hash.
|
|
891
|
-
/// - The total outputs value must be evenly divided over all outputs.
|
|
892
|
-
function processMovingFundsTxOutputs(bytes memory movingFundsTxOutputVector)
|
|
893
|
-
internal
|
|
894
|
-
view
|
|
895
|
-
returns (bytes32 targetWalletsHash, uint256 outputsTotalValue)
|
|
896
|
-
{
|
|
897
|
-
// Determining the total number of Bitcoin transaction outputs in
|
|
898
|
-
// the same way as for number of inputs. See `BitcoinTx.outputVector`
|
|
899
|
-
// docs for more details.
|
|
900
|
-
(
|
|
901
|
-
uint256 outputsCompactSizeUintLength,
|
|
902
|
-
uint256 outputsCount
|
|
903
|
-
) = movingFundsTxOutputVector.parseVarInt();
|
|
904
|
-
|
|
905
|
-
// To determine the first output starting index, we must jump over
|
|
906
|
-
// the compactSize uint which prepends the output vector. One byte
|
|
907
|
-
// must be added because `BtcUtils.parseVarInt` does not include
|
|
908
|
-
// compactSize uint tag in the returned length.
|
|
909
|
-
//
|
|
910
|
-
// For >= 0 && <= 252, `BTCUtils.determineVarIntDataLengthAt`
|
|
911
|
-
// returns `0`, so we jump over one byte of compactSize uint.
|
|
912
|
-
//
|
|
913
|
-
// For >= 253 && <= 0xffff there is `0xfd` tag,
|
|
914
|
-
// `BTCUtils.determineVarIntDataLengthAt` returns `2` (no
|
|
915
|
-
// tag byte included) so we need to jump over 1+2 bytes of
|
|
916
|
-
// compactSize uint.
|
|
917
|
-
//
|
|
918
|
-
// Please refer `BTCUtils` library and compactSize uint
|
|
919
|
-
// docs in `BitcoinTx` library for more details.
|
|
920
|
-
uint256 outputStartingIndex = 1 + outputsCompactSizeUintLength;
|
|
921
|
-
|
|
922
|
-
bytes20[] memory targetWallets = new bytes20[](outputsCount);
|
|
923
|
-
uint64[] memory outputsValues = new uint64[](outputsCount);
|
|
924
|
-
|
|
925
|
-
// Outputs processing loop.
|
|
926
|
-
for (uint256 i = 0; i < outputsCount; i++) {
|
|
927
|
-
uint256 outputLength = movingFundsTxOutputVector
|
|
928
|
-
.determineOutputLengthAt(outputStartingIndex);
|
|
929
|
-
|
|
930
|
-
bytes memory output = movingFundsTxOutputVector.slice(
|
|
931
|
-
outputStartingIndex,
|
|
932
|
-
outputLength
|
|
933
|
-
);
|
|
934
|
-
|
|
935
|
-
// Extract the output script payload.
|
|
936
|
-
bytes memory targetWalletPubKeyHashBytes = output.extractHash();
|
|
937
|
-
// Output script payload must refer to a known wallet public key
|
|
938
|
-
// hash which is always 20-byte.
|
|
939
|
-
require(
|
|
940
|
-
targetWalletPubKeyHashBytes.length == 20,
|
|
941
|
-
"Target wallet public key hash must have 20 bytes"
|
|
942
|
-
);
|
|
943
|
-
|
|
944
|
-
bytes20 targetWalletPubKeyHash = targetWalletPubKeyHashBytes
|
|
945
|
-
.slice20(0);
|
|
946
|
-
|
|
947
|
-
// The next step is making sure that the 20-byte public key hash
|
|
948
|
-
// is actually used in the right context of a P2PKH or P2WPKH
|
|
949
|
-
// output. To do so, we must extract the full script from the output
|
|
950
|
-
// and compare with the expected P2PKH and P2WPKH scripts
|
|
951
|
-
// referring to that 20-byte public key hash. The output consists
|
|
952
|
-
// of an 8-byte value and a variable length script. To extract the
|
|
953
|
-
// script we slice the output starting from 9th byte until the end.
|
|
954
|
-
bytes32 outputScriptKeccak = keccak256(
|
|
955
|
-
output.slice(8, output.length - 8)
|
|
956
|
-
);
|
|
957
|
-
// Build the expected P2PKH script which has the following byte
|
|
958
|
-
// format: <0x1976a914> <20-byte PKH> <0x88ac>. According to
|
|
959
|
-
// https://en.bitcoin.it/wiki/Script#Opcodes this translates to:
|
|
960
|
-
// - 0x19: Byte length of the entire script
|
|
961
|
-
// - 0x76: OP_DUP
|
|
962
|
-
// - 0xa9: OP_HASH160
|
|
963
|
-
// - 0x14: Byte length of the public key hash
|
|
964
|
-
// - 0x88: OP_EQUALVERIFY
|
|
965
|
-
// - 0xac: OP_CHECKSIG
|
|
966
|
-
// which matches the P2PKH structure as per:
|
|
967
|
-
// https://en.bitcoin.it/wiki/Transaction#Pay-to-PubkeyHash
|
|
968
|
-
bytes32 targetWalletP2PKHScriptKeccak = keccak256(
|
|
969
|
-
abi.encodePacked(
|
|
970
|
-
hex"1976a914",
|
|
971
|
-
targetWalletPubKeyHash,
|
|
972
|
-
hex"88ac"
|
|
973
|
-
)
|
|
974
|
-
);
|
|
975
|
-
// Build the expected P2WPKH script which has the following format:
|
|
976
|
-
// <0x160014> <20-byte PKH>. According to
|
|
977
|
-
// https://en.bitcoin.it/wiki/Script#Opcodes this translates to:
|
|
978
|
-
// - 0x16: Byte length of the entire script
|
|
979
|
-
// - 0x00: OP_0
|
|
980
|
-
// - 0x14: Byte length of the public key hash
|
|
981
|
-
// which matches the P2WPKH structure as per:
|
|
982
|
-
// https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#P2WPKH
|
|
983
|
-
bytes32 targetWalletP2WPKHScriptKeccak = keccak256(
|
|
984
|
-
abi.encodePacked(hex"160014", targetWalletPubKeyHash)
|
|
985
|
-
);
|
|
986
|
-
// Make sure the actual output script matches either the P2PKH
|
|
987
|
-
// or P2WPKH format.
|
|
988
|
-
require(
|
|
989
|
-
outputScriptKeccak == targetWalletP2PKHScriptKeccak ||
|
|
990
|
-
outputScriptKeccak == targetWalletP2WPKHScriptKeccak,
|
|
991
|
-
"Output must be P2PKH or P2WPKH"
|
|
992
|
-
);
|
|
993
|
-
|
|
994
|
-
// Add the wallet public key hash to the list that will be used
|
|
995
|
-
// to build the result list hash. There is no need to check if
|
|
996
|
-
// given output is a change here because the actual target wallet
|
|
997
|
-
// list must be exactly the same as the pre-committed target wallet
|
|
998
|
-
// list which is guaranteed to be valid.
|
|
999
|
-
targetWallets[i] = targetWalletPubKeyHash;
|
|
1000
|
-
|
|
1001
|
-
// Extract the value from given output.
|
|
1002
|
-
outputsValues[i] = output.extractValue();
|
|
1003
|
-
outputsTotalValue += outputsValues[i];
|
|
1004
|
-
|
|
1005
|
-
// Make the `outputStartingIndex` pointing to the next output by
|
|
1006
|
-
// increasing it by current output's length.
|
|
1007
|
-
outputStartingIndex += outputLength;
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
// Compute the indivisible remainder that remains after dividing the
|
|
1011
|
-
// outputs total value over all outputs evenly.
|
|
1012
|
-
uint256 outputsTotalValueRemainder = outputsTotalValue % outputsCount;
|
|
1013
|
-
// Compute the minimum allowed output value by dividing the outputs
|
|
1014
|
-
// total value (reduced by the remainder) by the number of outputs.
|
|
1015
|
-
uint256 minOutputValue = (outputsTotalValue -
|
|
1016
|
-
outputsTotalValueRemainder) / outputsCount;
|
|
1017
|
-
// Maximum possible value is the minimum value with the remainder included.
|
|
1018
|
-
uint256 maxOutputValue = minOutputValue + outputsTotalValueRemainder;
|
|
1019
|
-
|
|
1020
|
-
for (uint256 i = 0; i < outputsCount; i++) {
|
|
1021
|
-
require(
|
|
1022
|
-
minOutputValue <= outputsValues[i] &&
|
|
1023
|
-
outputsValues[i] <= maxOutputValue,
|
|
1024
|
-
"Transaction amount is not distributed evenly"
|
|
1025
|
-
);
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
targetWalletsHash = keccak256(abi.encodePacked(targetWallets));
|
|
1029
|
-
|
|
1030
|
-
return (targetWalletsHash, outputsTotalValue);
|
|
1031
842
|
}
|
|
1032
843
|
|
|
1033
844
|
/// @notice Returns the addresses of contracts Bridge is interacting with.
|
|
@@ -1125,6 +936,21 @@ contract Bridge is Ownable, EcdsaWalletOwner {
|
|
|
1125
936
|
txProofDifficultyFactor = self.txProofDifficultyFactor;
|
|
1126
937
|
}
|
|
1127
938
|
|
|
939
|
+
/// @notice Returns the current values of Bridge moving funds between
|
|
940
|
+
/// wallets parameters.
|
|
941
|
+
/// @return movingFundsTxMaxTotalFee Maximum amount of the total BTC
|
|
942
|
+
/// transaction fee that is acceptable in a single moving funds
|
|
943
|
+
/// transaction. This is a _total_ max fee for the entire moving
|
|
944
|
+
/// funds transaction.
|
|
945
|
+
function movingFundsParameters()
|
|
946
|
+
external
|
|
947
|
+
view
|
|
948
|
+
returns (uint64 movingFundsTxMaxTotalFee)
|
|
949
|
+
{
|
|
950
|
+
// TODO: we will have more parameters here, for example moving funds timeout
|
|
951
|
+
movingFundsTxMaxTotalFee = self.movingFundsTxMaxTotalFee;
|
|
952
|
+
}
|
|
953
|
+
|
|
1128
954
|
/// @notice Indicates if the vault with the given address is trusted or not.
|
|
1129
955
|
/// Depositors can route their revealed deposits only to trusted
|
|
1130
956
|
/// vaults and have trusted vaults notified about new deposits as
|
|
@@ -77,6 +77,14 @@ library BridgeState {
|
|
|
77
77
|
/// address.
|
|
78
78
|
mapping(address => bool) isVaultTrusted;
|
|
79
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.
|
|
80
88
|
/// @notice The minimal amount that can be requested for redemption.
|
|
81
89
|
/// Value of this parameter must take into account the value of
|
|
82
90
|
/// `redemptionTreasuryFeeDivisor` and `redemptionTxMaxFee`
|
|
@@ -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
|
+
}
|