@keep-network/tbtc-v2 0.1.1-dev.33 → 0.1.1-dev.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/artifacts/TBTC.json +3 -3
- package/artifacts/TBTCToken.json +3 -3
- package/artifacts/VendingMachine.json +10 -10
- package/artifacts/solcInputs/{7e4974a220c8c12bc2c31c8235131416.json → 4718d6e944ad9d1fc247efda870cf51a.json} +12 -6
- 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 +89 -113
- 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 +4 -0
- package/build/contracts/bridge/{Bridge.sol → IRelay.sol}/IRelay.json +1 -1
- package/build/contracts/bridge/Sweep.sol/Sweep.dbg.json +4 -0
- package/build/contracts/bridge/Sweep.sol/Sweep.json +48 -0
- 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 +220 -558
- package/contracts/bridge/BridgeState.sol +50 -0
- package/contracts/bridge/IRelay.sol +28 -0
- package/contracts/bridge/Sweep.sol +510 -0
- package/package.json +1 -1
- package/build/contracts/bridge/Bridge.sol/IRelay.dbg.json +0 -4
|
@@ -15,10 +15,28 @@
|
|
|
15
15
|
|
|
16
16
|
pragma solidity ^0.8.9;
|
|
17
17
|
|
|
18
|
+
import "./IRelay.sol";
|
|
18
19
|
import "./Deposit.sol";
|
|
19
20
|
|
|
21
|
+
import "../bank/Bank.sol";
|
|
22
|
+
|
|
20
23
|
library BridgeState {
|
|
21
24
|
struct Storage {
|
|
25
|
+
/// @notice The number of confirmations on the Bitcoin chain required to
|
|
26
|
+
/// successfully evaluate an SPV proof.
|
|
27
|
+
uint256 txProofDifficultyFactor;
|
|
28
|
+
/// TODO: Revisit whether it should be governable or not.
|
|
29
|
+
/// @notice Address of the Bank this Bridge belongs to.
|
|
30
|
+
Bank bank;
|
|
31
|
+
/// TODO: Make it governable.
|
|
32
|
+
/// @notice Bitcoin relay providing the current Bitcoin network
|
|
33
|
+
/// difficulty.
|
|
34
|
+
IRelay relay;
|
|
35
|
+
/// TODO: Revisit whether it should be governable or not.
|
|
36
|
+
/// @notice Address where the deposit and redemption treasury fees will
|
|
37
|
+
/// be sent to. Treasury takes part in the operators rewarding
|
|
38
|
+
/// process.
|
|
39
|
+
address treasury;
|
|
22
40
|
/// TODO: Make it governable.
|
|
23
41
|
/// @notice The minimal amount that can be requested to deposit.
|
|
24
42
|
/// Value of this parameter must take into account the value of
|
|
@@ -35,6 +53,13 @@ library BridgeState {
|
|
|
35
53
|
/// the `depositTreasuryFeeDivisor` should be set to `50`
|
|
36
54
|
/// because `1/50 = 0.02 = 2%`.
|
|
37
55
|
uint64 depositTreasuryFeeDivisor;
|
|
56
|
+
/// TODO: Make it governable.
|
|
57
|
+
/// @notice Maximum amount of BTC transaction fee that can be incurred by
|
|
58
|
+
/// each swept deposit being part of the given sweep
|
|
59
|
+
/// transaction. If the maximum BTC transaction fee is exceeded,
|
|
60
|
+
/// such transaction is considered a fraud.
|
|
61
|
+
/// @dev This is a per-deposit input max fee for the sweep transaction.
|
|
62
|
+
uint64 depositTxMaxFee;
|
|
38
63
|
/// @notice Collection of all revealed deposits indexed by
|
|
39
64
|
/// keccak256(fundingTxHash | fundingOutputIndex).
|
|
40
65
|
/// The fundingTxHash is bytes32 (ordered as in Bitcoin internally)
|
|
@@ -50,5 +75,30 @@ library BridgeState {
|
|
|
50
75
|
/// responsibility - anyone can approve their Bank balance to any
|
|
51
76
|
/// address.
|
|
52
77
|
mapping(address => bool) isVaultTrusted;
|
|
78
|
+
/// @notice Collection of main UTXOs that are honestly spent indexed by
|
|
79
|
+
/// keccak256(fundingTxHash | fundingOutputIndex). The fundingTxHash
|
|
80
|
+
/// is bytes32 (ordered as in Bitcoin internally) and
|
|
81
|
+
/// fundingOutputIndex an uint32. A main UTXO is considered honestly
|
|
82
|
+
/// spent if it was used as an input of a transaction that have been
|
|
83
|
+
/// proven in the Bridge.
|
|
84
|
+
mapping(uint256 => bool) spentMainUTXOs;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// TODO: Is it the right place for this function? Should we move it to Bridge?
|
|
88
|
+
/// @notice Determines the current Bitcoin SPV proof difficulty context.
|
|
89
|
+
/// @return proofDifficulty Bitcoin proof difficulty context.
|
|
90
|
+
function proofDifficultyContext(Storage storage self)
|
|
91
|
+
internal
|
|
92
|
+
view
|
|
93
|
+
returns (BitcoinTx.ProofDifficulty memory proofDifficulty)
|
|
94
|
+
{
|
|
95
|
+
IRelay relay = self.relay;
|
|
96
|
+
proofDifficulty.currentEpochDifficulty = relay
|
|
97
|
+
.getCurrentEpochDifficulty();
|
|
98
|
+
proofDifficulty.previousEpochDifficulty = relay
|
|
99
|
+
.getPrevEpochDifficulty();
|
|
100
|
+
proofDifficulty.difficultyFactor = self.txProofDifficultyFactor;
|
|
101
|
+
|
|
102
|
+
return proofDifficulty;
|
|
53
103
|
}
|
|
54
104
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
/// @title Interface for the Bitcoin relay
|
|
19
|
+
/// @notice Contains only the methods needed by tBTC v2. The Bitcoin relay
|
|
20
|
+
/// provides the difficulty of the previous and current epoch. One
|
|
21
|
+
/// difficulty epoch spans 2016 blocks.
|
|
22
|
+
interface IRelay {
|
|
23
|
+
/// @notice Returns the difficulty of the current epoch.
|
|
24
|
+
function getCurrentEpochDifficulty() external view returns (uint256);
|
|
25
|
+
|
|
26
|
+
/// @notice Returns the difficulty of the previous epoch.
|
|
27
|
+
function getPrevEpochDifficulty() external view returns (uint256);
|
|
28
|
+
}
|
|
@@ -0,0 +1,510 @@
|
|
|
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
|
+
|
|
20
|
+
import "./BitcoinTx.sol";
|
|
21
|
+
import "./BridgeState.sol";
|
|
22
|
+
import "./Wallets.sol";
|
|
23
|
+
|
|
24
|
+
import "../bank/Bank.sol";
|
|
25
|
+
|
|
26
|
+
library Sweep {
|
|
27
|
+
using BridgeState for BridgeState.Storage;
|
|
28
|
+
|
|
29
|
+
using BTCUtils for bytes;
|
|
30
|
+
|
|
31
|
+
/// @notice Represents an outcome of the sweep Bitcoin transaction
|
|
32
|
+
/// inputs processing.
|
|
33
|
+
struct SweepTxInputsInfo {
|
|
34
|
+
// Sum of all inputs values i.e. all deposits and main UTXO value,
|
|
35
|
+
// if present.
|
|
36
|
+
uint256 inputsTotalValue;
|
|
37
|
+
// Addresses of depositors who performed processed deposits. Ordered in
|
|
38
|
+
// the same order as deposits inputs in the input vector. Size of this
|
|
39
|
+
// array is either equal to the number of inputs (main UTXO doesn't
|
|
40
|
+
// exist) or less by one (main UTXO exists and is pointed by one of
|
|
41
|
+
// the inputs).
|
|
42
|
+
address[] depositors;
|
|
43
|
+
// Amounts of deposits corresponding to processed deposits. Ordered in
|
|
44
|
+
// the same order as deposits inputs in the input vector. Size of this
|
|
45
|
+
// array is either equal to the number of inputs (main UTXO doesn't
|
|
46
|
+
// exist) or less by one (main UTXO exists and is pointed by one of
|
|
47
|
+
// the inputs).
|
|
48
|
+
uint256[] depositedAmounts;
|
|
49
|
+
// Values of the treasury fee corresponding to processed deposits.
|
|
50
|
+
// Ordered in the same order as deposits inputs in the input vector.
|
|
51
|
+
// Size of this array is either equal to the number of inputs (main
|
|
52
|
+
// UTXO doesn't exist) or less by one (main UTXO exists and is pointed
|
|
53
|
+
// by one of the inputs).
|
|
54
|
+
uint256[] treasuryFees;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
event DepositsSwept(bytes20 walletPubKeyHash, bytes32 sweepTxHash);
|
|
58
|
+
|
|
59
|
+
/// @notice Used by the wallet to prove the BTC deposit sweep transaction
|
|
60
|
+
/// and to update Bank balances accordingly. Sweep is only accepted
|
|
61
|
+
/// if it satisfies SPV proof.
|
|
62
|
+
///
|
|
63
|
+
/// The function is performing Bank balance updates by first
|
|
64
|
+
/// computing the Bitcoin fee for the sweep transaction. The fee is
|
|
65
|
+
/// divided evenly between all swept deposits. Each depositor
|
|
66
|
+
/// receives a balance in the bank equal to the amount inferred
|
|
67
|
+
/// during the reveal transaction, minus their fee share.
|
|
68
|
+
///
|
|
69
|
+
/// It is possible to prove the given sweep only one time.
|
|
70
|
+
/// @param sweepTx Bitcoin sweep transaction data
|
|
71
|
+
/// @param sweepProof Bitcoin sweep proof data
|
|
72
|
+
/// @param mainUtxo Data of the wallet's main UTXO, as currently known on
|
|
73
|
+
/// the Ethereum chain. If no main UTXO exists for the given wallet,
|
|
74
|
+
/// this parameter is ignored
|
|
75
|
+
/// @dev Requirements:
|
|
76
|
+
/// - `sweepTx` components must match the expected structure. See
|
|
77
|
+
/// `BitcoinTx.Info` docs for reference. Their values must exactly
|
|
78
|
+
/// correspond to appropriate Bitcoin transaction fields to produce
|
|
79
|
+
/// a provable transaction hash.
|
|
80
|
+
/// - The `sweepTx` should represent a Bitcoin transaction with 1..n
|
|
81
|
+
/// inputs. If the wallet has no main UTXO, all n inputs should
|
|
82
|
+
/// correspond to P2(W)SH revealed deposits UTXOs. If the wallet has
|
|
83
|
+
/// an existing main UTXO, one of the n inputs must point to that
|
|
84
|
+
/// main UTXO and remaining n-1 inputs should correspond to P2(W)SH
|
|
85
|
+
/// revealed deposits UTXOs. That transaction must have only
|
|
86
|
+
/// one P2(W)PKH output locking funds on the 20-byte wallet public
|
|
87
|
+
/// key hash.
|
|
88
|
+
/// - `sweepProof` components must match the expected structure. See
|
|
89
|
+
/// `BitcoinTx.Proof` docs for reference. The `bitcoinHeaders`
|
|
90
|
+
/// field must contain a valid number of block headers, not less
|
|
91
|
+
/// than the `txProofDifficultyFactor` contract constant.
|
|
92
|
+
/// - `mainUtxo` components must point to the recent main UTXO
|
|
93
|
+
/// of the given wallet, as currently known on the Ethereum chain.
|
|
94
|
+
/// If there is no main UTXO, this parameter is ignored.
|
|
95
|
+
function submitSweepProof(
|
|
96
|
+
BridgeState.Storage storage self,
|
|
97
|
+
Wallets.Data storage wallets,
|
|
98
|
+
BitcoinTx.Info calldata sweepTx,
|
|
99
|
+
BitcoinTx.Proof calldata sweepProof,
|
|
100
|
+
BitcoinTx.UTXO calldata mainUtxo
|
|
101
|
+
) external {
|
|
102
|
+
// TODO: Fail early if the function call gets frontrunned. See discussion:
|
|
103
|
+
// https://github.com/keep-network/tbtc-v2/pull/106#discussion_r801745204
|
|
104
|
+
|
|
105
|
+
// The actual transaction proof is performed here. After that point, we
|
|
106
|
+
// can assume the transaction happened on Bitcoin chain and has
|
|
107
|
+
// a sufficient number of confirmations as determined by
|
|
108
|
+
// `txProofDifficultyFactor` constant.
|
|
109
|
+
bytes32 sweepTxHash = BitcoinTx.validateProof(
|
|
110
|
+
sweepTx,
|
|
111
|
+
sweepProof,
|
|
112
|
+
self.proofDifficultyContext()
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
// Process sweep transaction output and extract its target wallet
|
|
116
|
+
// public key hash and value.
|
|
117
|
+
(
|
|
118
|
+
bytes20 walletPubKeyHash,
|
|
119
|
+
uint64 sweepTxOutputValue
|
|
120
|
+
) = processSweepTxOutput(sweepTx.outputVector);
|
|
121
|
+
|
|
122
|
+
(
|
|
123
|
+
Wallets.Wallet storage wallet,
|
|
124
|
+
BitcoinTx.UTXO memory resolvedMainUtxo
|
|
125
|
+
) = resolveSweepingWallet(wallets, walletPubKeyHash, mainUtxo);
|
|
126
|
+
|
|
127
|
+
// Process sweep transaction inputs and extract all information needed
|
|
128
|
+
// to perform deposit bookkeeping.
|
|
129
|
+
SweepTxInputsInfo memory inputsInfo = processSweepTxInputs(
|
|
130
|
+
self,
|
|
131
|
+
sweepTx.inputVector,
|
|
132
|
+
resolvedMainUtxo
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// Helper variable that will hold the sum of treasury fees paid by
|
|
136
|
+
// all deposits.
|
|
137
|
+
uint256 totalTreasuryFee = 0;
|
|
138
|
+
|
|
139
|
+
// Determine the transaction fee that should be incurred by each deposit
|
|
140
|
+
// and the indivisible remainder that should be additionally incurred
|
|
141
|
+
// by the last deposit.
|
|
142
|
+
(
|
|
143
|
+
uint256 depositTxFee,
|
|
144
|
+
uint256 depositTxFeeRemainder
|
|
145
|
+
) = sweepTxFeeDistribution(
|
|
146
|
+
inputsInfo.inputsTotalValue,
|
|
147
|
+
sweepTxOutputValue,
|
|
148
|
+
inputsInfo.depositedAmounts.length
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// Make sure the highest value of the deposit transaction fee does not
|
|
152
|
+
// exceed the maximum value limited by the governable parameter.
|
|
153
|
+
require(
|
|
154
|
+
depositTxFee + depositTxFeeRemainder <= self.depositTxMaxFee,
|
|
155
|
+
"Transaction fee is too high"
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
// Reduce each deposit amount by treasury fee and transaction fee.
|
|
159
|
+
for (uint256 i = 0; i < inputsInfo.depositedAmounts.length; i++) {
|
|
160
|
+
// The last deposit should incur the deposit transaction fee
|
|
161
|
+
// remainder.
|
|
162
|
+
uint256 depositTxFeeIncurred = i ==
|
|
163
|
+
inputsInfo.depositedAmounts.length - 1
|
|
164
|
+
? depositTxFee + depositTxFeeRemainder
|
|
165
|
+
: depositTxFee;
|
|
166
|
+
|
|
167
|
+
// There is no need to check whether
|
|
168
|
+
// `inputsInfo.depositedAmounts[i] - inputsInfo.treasuryFees[i] - txFee > 0`
|
|
169
|
+
// since the `depositDustThreshold` should force that condition
|
|
170
|
+
// to be always true.
|
|
171
|
+
inputsInfo.depositedAmounts[i] =
|
|
172
|
+
inputsInfo.depositedAmounts[i] -
|
|
173
|
+
inputsInfo.treasuryFees[i] -
|
|
174
|
+
depositTxFeeIncurred;
|
|
175
|
+
totalTreasuryFee += inputsInfo.treasuryFees[i];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Record this sweep data and assign them to the wallet public key hash
|
|
179
|
+
// as new main UTXO. Transaction output index is always 0 as sweep
|
|
180
|
+
// transaction always contains only one output.
|
|
181
|
+
wallet.mainUtxoHash = keccak256(
|
|
182
|
+
abi.encodePacked(sweepTxHash, uint32(0), sweepTxOutputValue)
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
emit DepositsSwept(walletPubKeyHash, sweepTxHash);
|
|
186
|
+
|
|
187
|
+
// Update depositors balances in the Bank.
|
|
188
|
+
self.bank.increaseBalances(
|
|
189
|
+
inputsInfo.depositors,
|
|
190
|
+
inputsInfo.depositedAmounts
|
|
191
|
+
);
|
|
192
|
+
// Pass the treasury fee to the treasury address.
|
|
193
|
+
self.bank.increaseBalance(self.treasury, totalTreasuryFee);
|
|
194
|
+
|
|
195
|
+
// TODO: Handle deposits having `vault` set.
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/// @notice Resolves sweeping wallet based on the provided wallet public key
|
|
199
|
+
/// hash. Validates the wallet state and current main UTXO, as
|
|
200
|
+
/// currently known on the Ethereum chain.
|
|
201
|
+
/// @param walletPubKeyHash public key hash of the wallet proving the sweep
|
|
202
|
+
/// Bitcoin transaction.
|
|
203
|
+
/// @param mainUtxo Data of the wallet's main UTXO, as currently known on
|
|
204
|
+
/// the Ethereum chain. If no main UTXO exists for the given wallet,
|
|
205
|
+
/// this parameter is ignored
|
|
206
|
+
/// @dev Requirements:
|
|
207
|
+
/// - Sweeping wallet must be either in Live or MovingFunds state.
|
|
208
|
+
/// - If the main UTXO of the sweeping wallet exists in the storage,
|
|
209
|
+
/// the passed `mainUTXO` parameter must be equal to the stored one.
|
|
210
|
+
function resolveSweepingWallet(
|
|
211
|
+
Wallets.Data storage wallets,
|
|
212
|
+
bytes20 walletPubKeyHash,
|
|
213
|
+
BitcoinTx.UTXO calldata mainUtxo
|
|
214
|
+
)
|
|
215
|
+
internal
|
|
216
|
+
returns (
|
|
217
|
+
Wallets.Wallet storage wallet,
|
|
218
|
+
BitcoinTx.UTXO memory resolvedMainUtxo
|
|
219
|
+
)
|
|
220
|
+
{
|
|
221
|
+
wallet = wallets.registeredWallets[walletPubKeyHash];
|
|
222
|
+
|
|
223
|
+
Wallets.WalletState walletState = wallet.state;
|
|
224
|
+
require(
|
|
225
|
+
walletState == Wallets.WalletState.Live ||
|
|
226
|
+
walletState == Wallets.WalletState.MovingFunds,
|
|
227
|
+
"Wallet must be in Live or MovingFunds state"
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
// Check if the main UTXO for given wallet exists. If so, validate
|
|
231
|
+
// passed main UTXO data against the stored hash and use them for
|
|
232
|
+
// further processing. If no main UTXO exists, use empty data.
|
|
233
|
+
resolvedMainUtxo = BitcoinTx.UTXO(bytes32(0), 0, 0);
|
|
234
|
+
bytes32 mainUtxoHash = wallet.mainUtxoHash;
|
|
235
|
+
if (mainUtxoHash != bytes32(0)) {
|
|
236
|
+
require(
|
|
237
|
+
keccak256(
|
|
238
|
+
abi.encodePacked(
|
|
239
|
+
mainUtxo.txHash,
|
|
240
|
+
mainUtxo.txOutputIndex,
|
|
241
|
+
mainUtxo.txOutputValue
|
|
242
|
+
)
|
|
243
|
+
) == mainUtxoHash,
|
|
244
|
+
"Invalid main UTXO data"
|
|
245
|
+
);
|
|
246
|
+
resolvedMainUtxo = mainUtxo;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/// @notice Processes the Bitcoin sweep transaction output vector by
|
|
251
|
+
/// extracting the single output and using it to gain additional
|
|
252
|
+
/// information required for further processing (e.g. value and
|
|
253
|
+
/// wallet public key hash).
|
|
254
|
+
/// @param sweepTxOutputVector Bitcoin sweep transaction output vector.
|
|
255
|
+
/// This function assumes vector's structure is valid so it must be
|
|
256
|
+
/// validated using e.g. `BTCUtils.validateVout` function before
|
|
257
|
+
/// it is passed here
|
|
258
|
+
/// @return walletPubKeyHash 20-byte wallet public key hash.
|
|
259
|
+
/// @return value 8-byte sweep transaction output value.
|
|
260
|
+
function processSweepTxOutput(bytes memory sweepTxOutputVector)
|
|
261
|
+
internal
|
|
262
|
+
pure
|
|
263
|
+
returns (bytes20 walletPubKeyHash, uint64 value)
|
|
264
|
+
{
|
|
265
|
+
// To determine the total number of sweep transaction outputs, we need to
|
|
266
|
+
// parse the compactSize uint (VarInt) the output vector is prepended by.
|
|
267
|
+
// That compactSize uint encodes the number of vector elements using the
|
|
268
|
+
// format presented in:
|
|
269
|
+
// https://developer.bitcoin.org/reference/transactions.html#compactsize-unsigned-integers
|
|
270
|
+
// We don't need asserting the compactSize uint is parseable since it
|
|
271
|
+
// was already checked during `validateVout` validation.
|
|
272
|
+
// See `BitcoinTx.outputVector` docs for more details.
|
|
273
|
+
(, uint256 outputsCount) = sweepTxOutputVector.parseVarInt();
|
|
274
|
+
require(
|
|
275
|
+
outputsCount == 1,
|
|
276
|
+
"Sweep transaction must have a single output"
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
bytes memory output = sweepTxOutputVector.extractOutputAtIndex(0);
|
|
280
|
+
value = output.extractValue();
|
|
281
|
+
bytes memory walletPubKeyHashBytes = output.extractHash();
|
|
282
|
+
// The sweep transaction output should always be P2PKH or P2WPKH.
|
|
283
|
+
// In both cases, the wallet public key hash should be 20 bytes length.
|
|
284
|
+
require(
|
|
285
|
+
walletPubKeyHashBytes.length == 20,
|
|
286
|
+
"Wallet public key hash should have 20 bytes"
|
|
287
|
+
);
|
|
288
|
+
/* solhint-disable-next-line no-inline-assembly */
|
|
289
|
+
assembly {
|
|
290
|
+
walletPubKeyHash := mload(add(walletPubKeyHashBytes, 32))
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return (walletPubKeyHash, value);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/// @notice Processes the Bitcoin sweep transaction input vector. It
|
|
297
|
+
/// extracts each input and tries to obtain associated deposit or
|
|
298
|
+
/// main UTXO data, depending on the input type. Reverts
|
|
299
|
+
/// if one of the inputs cannot be recognized as a pointer to a
|
|
300
|
+
/// revealed deposit or expected main UTXO.
|
|
301
|
+
/// This function also marks each processed deposit as swept.
|
|
302
|
+
/// @param sweepTxInputVector Bitcoin sweep transaction input vector.
|
|
303
|
+
/// This function assumes vector's structure is valid so it must be
|
|
304
|
+
/// validated using e.g. `BTCUtils.validateVin` function before
|
|
305
|
+
/// it is passed here
|
|
306
|
+
/// @param mainUtxo Data of the wallet's main UTXO. If no main UTXO
|
|
307
|
+
/// exists for the given the wallet, this parameter's fields should
|
|
308
|
+
/// be zeroed to bypass the main UTXO validation
|
|
309
|
+
/// @return info Outcomes of the processing.
|
|
310
|
+
function processSweepTxInputs(
|
|
311
|
+
BridgeState.Storage storage self,
|
|
312
|
+
bytes memory sweepTxInputVector,
|
|
313
|
+
BitcoinTx.UTXO memory mainUtxo
|
|
314
|
+
) internal returns (SweepTxInputsInfo memory info) {
|
|
315
|
+
// If the passed `mainUtxo` parameter's values are zeroed, the main UTXO
|
|
316
|
+
// for the given wallet doesn't exist and it is not expected to be
|
|
317
|
+
// included in the sweep transaction input vector.
|
|
318
|
+
bool mainUtxoExpected = mainUtxo.txHash != bytes32(0);
|
|
319
|
+
bool mainUtxoFound = false;
|
|
320
|
+
|
|
321
|
+
// Determining the total number of sweep transaction inputs in the same
|
|
322
|
+
// way as for number of outputs. See `BitcoinTx.inputVector` docs for
|
|
323
|
+
// more details.
|
|
324
|
+
(
|
|
325
|
+
uint256 inputsCompactSizeUintLength,
|
|
326
|
+
uint256 inputsCount
|
|
327
|
+
) = sweepTxInputVector.parseVarInt();
|
|
328
|
+
|
|
329
|
+
// To determine the first input starting index, we must jump over
|
|
330
|
+
// the compactSize uint which prepends the input vector. One byte
|
|
331
|
+
// must be added because `BtcUtils.parseVarInt` does not include
|
|
332
|
+
// compactSize uint tag in the returned length.
|
|
333
|
+
//
|
|
334
|
+
// For >= 0 && <= 252, `BTCUtils.determineVarIntDataLengthAt`
|
|
335
|
+
// returns `0`, so we jump over one byte of compactSize uint.
|
|
336
|
+
//
|
|
337
|
+
// For >= 253 && <= 0xffff there is `0xfd` tag,
|
|
338
|
+
// `BTCUtils.determineVarIntDataLengthAt` returns `2` (no
|
|
339
|
+
// tag byte included) so we need to jump over 1+2 bytes of
|
|
340
|
+
// compactSize uint.
|
|
341
|
+
//
|
|
342
|
+
// Please refer `BTCUtils` library and compactSize uint
|
|
343
|
+
// docs in `BitcoinTx` library for more details.
|
|
344
|
+
uint256 inputStartingIndex = 1 + inputsCompactSizeUintLength;
|
|
345
|
+
|
|
346
|
+
// Determine the swept deposits count. If main UTXO is NOT expected,
|
|
347
|
+
// all inputs should be deposits. If main UTXO is expected, one input
|
|
348
|
+
// should point to that main UTXO.
|
|
349
|
+
info.depositors = new address[](
|
|
350
|
+
!mainUtxoExpected ? inputsCount : inputsCount - 1
|
|
351
|
+
);
|
|
352
|
+
info.depositedAmounts = new uint256[](info.depositors.length);
|
|
353
|
+
info.treasuryFees = new uint256[](info.depositors.length);
|
|
354
|
+
|
|
355
|
+
// Initialize helper variables.
|
|
356
|
+
uint256 processedDepositsCount = 0;
|
|
357
|
+
|
|
358
|
+
// Inputs processing loop.
|
|
359
|
+
for (uint256 i = 0; i < inputsCount; i++) {
|
|
360
|
+
(
|
|
361
|
+
bytes32 outpointTxHash,
|
|
362
|
+
uint32 outpointIndex,
|
|
363
|
+
uint256 inputLength
|
|
364
|
+
) = parseTxInputAt(sweepTxInputVector, inputStartingIndex);
|
|
365
|
+
|
|
366
|
+
Deposit.Request storage deposit = self.deposits[
|
|
367
|
+
uint256(
|
|
368
|
+
keccak256(abi.encodePacked(outpointTxHash, outpointIndex))
|
|
369
|
+
)
|
|
370
|
+
];
|
|
371
|
+
|
|
372
|
+
if (deposit.revealedAt != 0) {
|
|
373
|
+
// If we entered here, that means the input was identified as
|
|
374
|
+
// a revealed deposit.
|
|
375
|
+
require(deposit.sweptAt == 0, "Deposit already swept");
|
|
376
|
+
|
|
377
|
+
if (processedDepositsCount == info.depositors.length) {
|
|
378
|
+
// If this condition is true, that means a deposit input
|
|
379
|
+
// took place of an expected main UTXO input.
|
|
380
|
+
// In other words, there is no expected main UTXO
|
|
381
|
+
// input and all inputs come from valid, revealed deposits.
|
|
382
|
+
revert(
|
|
383
|
+
"Expected main UTXO not present in sweep transaction inputs"
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/* solhint-disable-next-line not-rely-on-time */
|
|
388
|
+
deposit.sweptAt = uint32(block.timestamp);
|
|
389
|
+
|
|
390
|
+
info.depositors[processedDepositsCount] = deposit.depositor;
|
|
391
|
+
info.depositedAmounts[processedDepositsCount] = deposit.amount;
|
|
392
|
+
info.inputsTotalValue += info.depositedAmounts[
|
|
393
|
+
processedDepositsCount
|
|
394
|
+
];
|
|
395
|
+
info.treasuryFees[processedDepositsCount] = deposit.treasuryFee;
|
|
396
|
+
|
|
397
|
+
processedDepositsCount++;
|
|
398
|
+
} else if (
|
|
399
|
+
mainUtxoExpected != mainUtxoFound &&
|
|
400
|
+
mainUtxo.txHash == outpointTxHash
|
|
401
|
+
) {
|
|
402
|
+
// If we entered here, that means the input was identified as
|
|
403
|
+
// the expected main UTXO.
|
|
404
|
+
info.inputsTotalValue += mainUtxo.txOutputValue;
|
|
405
|
+
mainUtxoFound = true;
|
|
406
|
+
|
|
407
|
+
// Main UTXO used as an input, mark it as spent.
|
|
408
|
+
self.spentMainUTXOs[
|
|
409
|
+
uint256(
|
|
410
|
+
keccak256(
|
|
411
|
+
abi.encodePacked(outpointTxHash, outpointIndex)
|
|
412
|
+
)
|
|
413
|
+
)
|
|
414
|
+
] = true;
|
|
415
|
+
} else {
|
|
416
|
+
revert("Unknown input type");
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Make the `inputStartingIndex` pointing to the next input by
|
|
420
|
+
// increasing it by current input's length.
|
|
421
|
+
inputStartingIndex += inputLength;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Construction of the input processing loop guarantees that:
|
|
425
|
+
// `processedDepositsCount == info.depositors.length == info.depositedAmounts.length`
|
|
426
|
+
// is always true at this point. We just use the first variable
|
|
427
|
+
// to assert the total count of swept deposit is bigger than zero.
|
|
428
|
+
require(
|
|
429
|
+
processedDepositsCount > 0,
|
|
430
|
+
"Sweep transaction must process at least one deposit"
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
// Assert the main UTXO was used as one of current sweep's inputs if
|
|
434
|
+
// it was actually expected.
|
|
435
|
+
require(
|
|
436
|
+
mainUtxoExpected == mainUtxoFound,
|
|
437
|
+
"Expected main UTXO not present in sweep transaction inputs"
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
return info;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/// @notice Parses a Bitcoin transaction input starting at the given index.
|
|
444
|
+
/// @param inputVector Bitcoin transaction input vector
|
|
445
|
+
/// @param inputStartingIndex Index the given input starts at
|
|
446
|
+
/// @return outpointTxHash 32-byte hash of the Bitcoin transaction which is
|
|
447
|
+
/// pointed in the given input's outpoint.
|
|
448
|
+
/// @return outpointIndex 4-byte index of the Bitcoin transaction output
|
|
449
|
+
/// which is pointed in the given input's outpoint.
|
|
450
|
+
/// @return inputLength Byte length of the given input.
|
|
451
|
+
/// @dev This function assumes vector's structure is valid so it must be
|
|
452
|
+
/// validated using e.g. `BTCUtils.validateVin` function before it
|
|
453
|
+
/// is passed here.
|
|
454
|
+
function parseTxInputAt(
|
|
455
|
+
bytes memory inputVector,
|
|
456
|
+
uint256 inputStartingIndex
|
|
457
|
+
)
|
|
458
|
+
internal
|
|
459
|
+
pure
|
|
460
|
+
returns (
|
|
461
|
+
bytes32 outpointTxHash,
|
|
462
|
+
uint32 outpointIndex,
|
|
463
|
+
uint256 inputLength
|
|
464
|
+
)
|
|
465
|
+
{
|
|
466
|
+
outpointTxHash = inputVector.extractInputTxIdLeAt(inputStartingIndex);
|
|
467
|
+
|
|
468
|
+
outpointIndex = BTCUtils.reverseUint32(
|
|
469
|
+
uint32(inputVector.extractTxIndexLeAt(inputStartingIndex))
|
|
470
|
+
);
|
|
471
|
+
|
|
472
|
+
inputLength = inputVector.determineInputLengthAt(inputStartingIndex);
|
|
473
|
+
|
|
474
|
+
return (outpointTxHash, outpointIndex, inputLength);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/// @notice Determines the distribution of the sweep transaction fee
|
|
478
|
+
/// over swept deposits.
|
|
479
|
+
/// @param sweepTxInputsTotalValue Total value of all sweep transaction inputs.
|
|
480
|
+
/// @param sweepTxOutputValue Value of the sweep transaction output.
|
|
481
|
+
/// @param depositsCount Count of the deposits swept by the sweep transaction.
|
|
482
|
+
/// @return depositTxFee Transaction fee per deposit determined by evenly
|
|
483
|
+
/// spreading the divisible part of the sweep transaction fee
|
|
484
|
+
/// over all deposits.
|
|
485
|
+
/// @return depositTxFeeRemainder The indivisible part of the sweep
|
|
486
|
+
/// transaction fee than cannot be distributed over all deposits.
|
|
487
|
+
/// @dev It is up to the caller to decide how the remainder should be
|
|
488
|
+
/// counted in. This function only computes its value.
|
|
489
|
+
function sweepTxFeeDistribution(
|
|
490
|
+
uint256 sweepTxInputsTotalValue,
|
|
491
|
+
uint256 sweepTxOutputValue,
|
|
492
|
+
uint256 depositsCount
|
|
493
|
+
)
|
|
494
|
+
internal
|
|
495
|
+
pure
|
|
496
|
+
returns (uint256 depositTxFee, uint256 depositTxFeeRemainder)
|
|
497
|
+
{
|
|
498
|
+
// The sweep transaction fee is just the difference between inputs
|
|
499
|
+
// amounts sum and the output amount.
|
|
500
|
+
uint256 sweepTxFee = sweepTxInputsTotalValue - sweepTxOutputValue;
|
|
501
|
+
// Compute the indivisible remainder that remains after dividing the
|
|
502
|
+
// sweep transaction fee over all deposits evenly.
|
|
503
|
+
depositTxFeeRemainder = sweepTxFee % depositsCount;
|
|
504
|
+
// Compute the transaction fee per deposit by dividing the sweep
|
|
505
|
+
// transaction fee (reduced by the remainder) by the number of deposits.
|
|
506
|
+
depositTxFee = (sweepTxFee - depositTxFeeRemainder) / depositsCount;
|
|
507
|
+
|
|
508
|
+
return (depositTxFee, depositTxFeeRemainder);
|
|
509
|
+
}
|
|
510
|
+
}
|
package/package.json
CHANGED