@keep-network/tbtc-v2 0.1.1-dev.1 → 0.1.1-dev.10

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.
@@ -0,0 +1,104 @@
1
+ // SPDX-License-Identifier: MIT
2
+
3
+ // ██████████████ ▐████▌ ██████████████
4
+ // ██████████████ ▐████▌ ██████████████
5
+ // ▐████▌ ▐████▌
6
+ // ▐████▌ ▐████▌
7
+ // ██████████████ ▐████▌ ██████████████
8
+ // ██████████████ ▐████▌ ██████████████
9
+ // ▐████▌ ▐████▌
10
+ // ▐████▌ ▐████▌
11
+ // ▐████▌ ▐████▌
12
+ // ▐████▌ ▐████▌
13
+ // ▐████▌ ▐████▌
14
+ // ▐████▌ ▐████▌
15
+
16
+ pragma solidity 0.8.4;
17
+
18
+ /// @title Bitcoin transaction
19
+ /// @notice Allows to reference Bitcoin raw transaction in Solidity.
20
+ /// @dev See https://developer.bitcoin.org/reference/transactions.html#raw-transaction-format
21
+ ///
22
+ /// Raw Bitcon transaction data:
23
+ ///
24
+ /// | Bytes | Name | BTC type | Description |
25
+ /// |--------|--------------|------------------------|---------------------------|
26
+ /// | 4 | version | int32_t (LE) | TX version number |
27
+ /// | varies | tx_in_count | compactSize uint (LE) | Number of TX inputs |
28
+ /// | varies | tx_in | txIn[] | TX inputs |
29
+ /// | varies | tx_out count | compactSize uint (LE) | Number of TX outputs |
30
+ /// | varies | tx_out | txOut[] | TX outputs |
31
+ /// | 4 | lock_time | uint32_t (LE) | Unix time or block number |
32
+ ///
33
+ //
34
+ /// Non-coinbase transaction input (txIn):
35
+ ///
36
+ /// | Bytes | Name | BTC type | Description |
37
+ /// |--------|------------------|------------------------|---------------------------------------------|
38
+ /// | 36 | previous_output | outpoint | The previous outpoint being spent |
39
+ /// | varies | script bytes | compactSize uint (LE) | The number of bytes in the signature script |
40
+ /// | varies | signature script | char[] | The signature script, empty for P2WSH |
41
+ /// | 4 | sequence | uint32_t (LE) | Sequence number |
42
+ ///
43
+ ///
44
+ /// The reference to transaction being spent (outpoint):
45
+ ///
46
+ /// | Bytes | Name | BTC type | Description |
47
+ /// |-------|-------|---------------|------------------------------------------|
48
+ /// | 32 | hash | char[32] | Hash of the transaction to spend |
49
+ /// | 4 | index | uint32_t (LE) | Index of the specific output from the TX |
50
+ ///
51
+ ///
52
+ /// Transaction output (txOut):
53
+ ///
54
+ /// | Bytes | Name | BTC type | Description |
55
+ /// |--------|-----------------|-----------------------|--------------------------------------|
56
+ /// | 8 | value | int64_t (LE) | Number of satoshis to spend |
57
+ /// | 1+ | pk_script_bytes | compactSize uint (LE) | Number of bytes in the pubkey script |
58
+ /// | varies | pk_script | char[] | Pubkey script |
59
+ ///
60
+ /// compactSize uint format:
61
+ ///
62
+ /// | Value | Bytes | Format |
63
+ /// |-----------------------------------------|-------|----------------------------------------------|
64
+ /// | >= 0 && <= 252 | 1 | uint8_t |
65
+ /// | >= 253 && <= 0xffff | 3 | 0xfd followed by the number as uint16_t (LE) |
66
+ /// | >= 0x10000 && <= 0xffffffff | 5 | 0xfe followed by the number as uint32_t (LE) |
67
+ /// | >= 0x100000000 && <= 0xffffffffffffffff | 9 | 0xff followed by the number as uint64_t (LE) |
68
+ ///
69
+ /// (*) compactSize uint is often references as VarInt)
70
+ ///
71
+ library BitcoinTx {
72
+ /// @notice Represents Bitcoin transaction data for funding BTC deposit
73
+ /// P2(W)SH transaction.
74
+ struct Info {
75
+ /// @notice Bitcoin transaction version
76
+ /// @dev `version` from raw Bitcon transaction data.
77
+ /// Encoded as 4-bytes signed integer, little endian.
78
+ bytes4 version;
79
+ /// @notice All Bitcoin transaction inputs, prepended by the number of
80
+ /// transaction inputs.
81
+ /// @dev `tx_in_count | tx_in` from raw Bitcon transaction data.
82
+ ///
83
+ /// The number of transaction inputs encoded as compactSize
84
+ /// unsigned integer, little-endian.
85
+ ///
86
+ /// Note that some popular block explorers reverse the order of
87
+ /// bytes from `outpoint`'s `hash` and display it as big-endian.
88
+ /// Solidity code of Bridge expects hashes in little-endian, just
89
+ /// like they are represented in a raw Bitcoin transaction.
90
+ bytes inputVector;
91
+ /// @notice All Bitcoin transaction outputs prepended by the number of
92
+ /// transaction outputs.
93
+ /// @dev `tx_out_count | tx_out` from raw Bitcoin transaction data.
94
+ ///
95
+ /// The number of transaction outputs encoded as a compactSize
96
+ /// unsigned integer, little-endian.
97
+ bytes outputVector;
98
+ /// @notice Bitcoin transaction locktime.
99
+ ///
100
+ /// @dev `lock_time` from raw Bitcoin transaction data.
101
+ /// Encoded as 4-bytes unsigned integer, little endian.
102
+ bytes4 locktime;
103
+ }
104
+ }
@@ -0,0 +1,346 @@
1
+ // SPDX-License-Identifier: MIT
2
+
3
+ // ██████████████ ▐████▌ ██████████████
4
+ // ██████████████ ▐████▌ ██████████████
5
+ // ▐████▌ ▐████▌
6
+ // ▐████▌ ▐████▌
7
+ // ██████████████ ▐████▌ ██████████████
8
+ // ██████████████ ▐████▌ ██████████████
9
+ // ▐████▌ ▐████▌
10
+ // ▐████▌ ▐████▌
11
+ // ▐████▌ ▐████▌
12
+ // ▐████▌ ▐████▌
13
+ // ▐████▌ ▐████▌
14
+ // ▐████▌ ▐████▌
15
+
16
+ pragma solidity 0.8.4;
17
+
18
+ import "@openzeppelin/contracts/access/Ownable.sol";
19
+
20
+ import {BTCUtils} from "@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol";
21
+ import {BytesLib} from "@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol";
22
+
23
+ import "./BitcoinTx.sol";
24
+
25
+ /// @title Bitcoin Bridge
26
+ /// @notice Bridge manages BTC deposit and redemption flow and is increasing and
27
+ /// decreasing balances in the Bank as a result of BTC deposit and
28
+ /// redemption operations performed by depositors and redeemers.
29
+ ///
30
+ /// Depositors send BTC funds to the most recently created off-chain
31
+ /// ECDSA wallet of the bridge using pay-to-script-hash (P2SH) or
32
+ /// pay-to-witness-script-hash (P2WSH) containing hashed information
33
+ /// about the depositor’s Ethereum address. Then, the depositor reveals
34
+ /// their Ethereum address along with their deposit blinding factor,
35
+ /// refund public key hash and refund locktime to the Bridge on Ethereum
36
+ /// chain. The off-chain ECDSA wallet listens for these sorts of
37
+ /// messages and when it gets one, it checks the Bitcoin network to make
38
+ /// sure the deposit lines up. If it does, the off-chain ECDSA wallet
39
+ /// may decide to pick the deposit transaction for sweeping, and when
40
+ /// the sweep operation is confirmed on the Bitcoin network, the ECDSA
41
+ /// wallet informs the Bridge about the sweep increasing appropriate
42
+ /// balances in the Bank.
43
+ /// @dev Bridge is an upgradeable component of the Bank.
44
+ contract Bridge is Ownable {
45
+ using BTCUtils for bytes;
46
+ using BytesLib for bytes;
47
+
48
+ /// @notice Represents data which must be revealed by the depositor during
49
+ /// deposit reveal.
50
+ struct RevealInfo {
51
+ // Index of the funding output belonging to the funding transaction.
52
+ uint8 fundingOutputIndex;
53
+ // Ethereum depositor address.
54
+ address depositor;
55
+ // The blinding factor as 8 bytes. Byte endianness doesn't matter
56
+ // as this factor is not interpreted as uint.
57
+ bytes8 blindingFactor;
58
+ // The compressed Bitcoin public key (33 bytes and 02 or 03 prefix)
59
+ // of the deposit's wallet hashed in the HASH160 Bitcoin opcode style.
60
+ bytes20 walletPubKeyHash;
61
+ // The compressed Bitcoin public key (33 bytes and 02 or 03 prefix)
62
+ // that can be used to make the deposit refund after the refund
63
+ // locktime passes. Hashed in the HASH160 Bitcoin opcode style.
64
+ bytes20 refundPubKeyHash;
65
+ // The refund locktime (4-byte LE). Interpreted according to locktime
66
+ // parsing rules described in:
67
+ // https://developer.bitcoin.org/devguide/transactions.html#locktime-and-sequence-number
68
+ // and used with OP_CHECKLOCKTIMEVERIFY opcode as described in:
69
+ // https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki
70
+ bytes4 refundLocktime;
71
+ // Address of the Bank vault to which the deposit is routed to.
72
+ // Optional, can be 0x0. The vault must be trusted by the Bridge.
73
+ address vault;
74
+ }
75
+
76
+ /// @notice Represents tBTC deposit data.
77
+ struct DepositInfo {
78
+ // Ethereum depositor address.
79
+ address depositor;
80
+ // Deposit amount in satoshi (8-byte LE). For example:
81
+ // 0.0001 BTC = 10000 satoshi = 0x1027000000000000
82
+ bytes8 amount;
83
+ // UNIX timestamp the deposit was revealed at.
84
+ uint32 revealedAt;
85
+ // Address of the Bank vault the deposit is routed to.
86
+ // Optional, can be 0x0.
87
+ address vault;
88
+ }
89
+
90
+ /// @notice Indicates if the vault with the given address is trusted or not.
91
+ /// Depositors can route their revealed deposits only to trusted
92
+ /// vaults and have trusted vaults notified about new deposits as
93
+ /// soon as these deposits get swept. Vaults not trusted by the
94
+ /// Bridge can still be used by Bank balance owners on their own
95
+ /// responsibility - anyone can approve their Bank balance to any
96
+ /// address.
97
+ mapping(address => bool) public isVaultTrusted;
98
+
99
+ /// @notice Collection of all unswept deposits indexed by
100
+ /// keccak256(fundingTxHash | fundingOutputIndex).
101
+ /// The fundingTxHash is LE bytes32 and fundingOutputIndex an uint8.
102
+ /// This mapping may contain valid and invalid deposits and the
103
+ /// wallet is responsible for validating them before attempting to
104
+ /// execute a sweep.
105
+ ///
106
+ /// TODO: Explore the possibility of storing just a hash of DepositInfo.
107
+ mapping(uint256 => DepositInfo) public unswept;
108
+
109
+ event DepositRevealed(
110
+ bytes32 fundingTxHash,
111
+ uint8 fundingOutputIndex,
112
+ address depositor,
113
+ bytes8 blindingFactor,
114
+ bytes20 walletPubKeyHash,
115
+ bytes20 refundPubKeyHash,
116
+ bytes4 refundLocktime,
117
+ address vault
118
+ );
119
+
120
+ event VaultStatusUpdated(address indexed vault, bool isTrusted);
121
+
122
+ /// @notice Allows the Governance to mark the given vault address as trusted
123
+ /// or no longer trusted. Vaults are not trusted by default.
124
+ /// Trusted vault must meet the following criteria:
125
+ /// - `IVault.receiveBalanceIncrease` must have a known, low gas
126
+ /// cost.
127
+ /// - `IVault.receiveBalanceIncrease` must never revert.
128
+ /// @dev Without restricting reveal only to trusted vaults, malicious
129
+ /// vaults not meeting the criteria would be able to nuke sweep proof
130
+ /// transactions executed by ECDSA wallet with deposits routed to
131
+ /// them.
132
+ /// @param vault The address of the vault
133
+ /// @param isTrusted flag indicating whether the vault is trusted or not
134
+ /// @dev Can only be called by the Governance.
135
+ function setVaultStatus(address vault, bool isTrusted) external onlyOwner {
136
+ isVaultTrusted[vault] = isTrusted;
137
+ emit VaultStatusUpdated(vault, isTrusted);
138
+ }
139
+
140
+ /// @notice Used by the depositor to reveal information about their P2(W)SH
141
+ /// Bitcoin deposit to the Bridge on Ethereum chain. The off-chain
142
+ /// wallet listens for revealed deposit events and may decide to
143
+ /// include the revealed deposit in the next executed sweep.
144
+ /// Information about the Bitcoin deposit can be revealed before or
145
+ /// after the Bitcoin transaction with P2(W)SH deposit is mined on
146
+ /// the Bitcoin chain. Worth noting, the gas cost of this function
147
+ /// scales with the number of P2(W)SH transaction inputs and
148
+ /// outputs. The deposit may be routed to one of the trusted vaults.
149
+ /// When a deposit is routed to a vault, vault gets notified when
150
+ /// the deposit gets swept and it may execute the appropriate action.
151
+ /// @param fundingTx Bitcoin funding transaction data, see `BitcoinTx.Info`
152
+ /// @param reveal Deposit reveal data, see `RevealInfo struct
153
+ /// @dev Requirements:
154
+ /// - `reveal.vault` must be 0x0 or point to a trusted vault
155
+ /// - `reveal.fundingOutputIndex` must point to the actual P2(W)SH
156
+ /// output of the BTC deposit transaction
157
+ /// - `reveal.depositor` must be the Ethereum address used in the
158
+ /// P2(W)SH BTC deposit transaction,
159
+ /// - `reveal.blindingFactor` must be the blinding factor used in the
160
+ /// P2(W)SH BTC deposit transaction,
161
+ /// - `reveal.walletPubKeyHash` must be the wallet pub key hash used in
162
+ /// the P2(W)SH BTC deposit transaction,
163
+ /// - `reveal.refundPubKeyHash` must be the refund pub key hash used in
164
+ /// the P2(W)SH BTC deposit transaction,
165
+ /// - `reveal.refundLocktime` must be the refund locktime used in the
166
+ /// P2(W)SH BTC deposit transaction,
167
+ /// - BTC deposit for the given `fundingTxHash`, `fundingOutputIndex`
168
+ /// can be revealed only one time.
169
+ ///
170
+ /// If any of these requirements is not met, the wallet _must_ refuse
171
+ /// to sweep the deposit and the depositor has to wait until the
172
+ /// deposit script unlocks to receive their BTC back.
173
+ function revealDeposit(
174
+ BitcoinTx.Info calldata fundingTx,
175
+ RevealInfo calldata reveal
176
+ ) external {
177
+ require(
178
+ reveal.vault == address(0) || isVaultTrusted[reveal.vault],
179
+ "Vault is not trusted"
180
+ );
181
+
182
+ bytes memory expectedScript =
183
+ abi.encodePacked(
184
+ hex"14", // Byte length of depositor Ethereum address.
185
+ reveal.depositor,
186
+ hex"75", // OP_DROP
187
+ hex"08", // Byte length of blinding factor value.
188
+ reveal.blindingFactor,
189
+ hex"75", // OP_DROP
190
+ hex"76", // OP_DUP
191
+ hex"a9", // OP_HASH160
192
+ hex"14", // Byte length of a compressed Bitcoin public key hash.
193
+ reveal.walletPubKeyHash,
194
+ hex"87", // OP_EQUAL
195
+ hex"63", // OP_IF
196
+ hex"ac", // OP_CHECKSIG
197
+ hex"67", // OP_ELSE
198
+ hex"76", // OP_DUP
199
+ hex"a9", // OP_HASH160
200
+ hex"14", // Byte length of a compressed Bitcoin public key hash.
201
+ reveal.refundPubKeyHash,
202
+ hex"88", // OP_EQUALVERIFY
203
+ hex"04", // Byte length of refund locktime value.
204
+ reveal.refundLocktime,
205
+ hex"b1", // OP_CHECKLOCKTIMEVERIFY
206
+ hex"75", // OP_DROP
207
+ hex"ac", // OP_CHECKSIG
208
+ hex"68" // OP_ENDIF
209
+ );
210
+
211
+ bytes memory fundingOutput =
212
+ fundingTx.outputVector.extractOutputAtIndex(
213
+ reveal.fundingOutputIndex
214
+ );
215
+ bytes memory fundingOutputHash = fundingOutput.extractHash();
216
+
217
+ if (fundingOutputHash.length == 20) {
218
+ // A 20-byte output hash is used by P2SH. That hash is constructed
219
+ // by applying OP_HASH160 on the locking script. A 20-byte output
220
+ // hash is used as well by P2PKH and P2WPKH (OP_HASH160 on the
221
+ // public key). However, since we compare the actual output hash
222
+ // with an expected locking script hash, this check will succeed only
223
+ // for P2SH transaction type with expected script hash value. For
224
+ // P2PKH and P2WPKH, it will fail on the output hash comparison with
225
+ // the expected locking script hash.
226
+ require(
227
+ keccak256(fundingOutputHash) ==
228
+ keccak256(expectedScript.hash160()),
229
+ "Wrong 20-byte script hash"
230
+ );
231
+ } else if (fundingOutputHash.length == 32) {
232
+ // A 32-byte output hash is used by P2WSH. That hash is constructed
233
+ // by applying OP_SHA256 on the locking script.
234
+ require(
235
+ fundingOutputHash.toBytes32() == sha256(expectedScript),
236
+ "Wrong 32-byte script hash"
237
+ );
238
+ } else {
239
+ revert("Wrong script hash length");
240
+ }
241
+
242
+ // Resulting TX hash is in native Bitcoin little-endian format.
243
+ bytes32 fundingTxHash =
244
+ abi
245
+ .encodePacked(
246
+ fundingTx
247
+ .version,
248
+ fundingTx
249
+ .inputVector,
250
+ fundingTx
251
+ .outputVector,
252
+ fundingTx
253
+ .locktime
254
+ )
255
+ .hash256();
256
+
257
+ DepositInfo storage deposit =
258
+ unswept[
259
+ uint256(
260
+ keccak256(
261
+ abi.encodePacked(
262
+ fundingTxHash,
263
+ reveal.fundingOutputIndex
264
+ )
265
+ )
266
+ )
267
+ ];
268
+ require(deposit.revealedAt == 0, "Deposit already revealed");
269
+
270
+ bytes8 fundingOutputAmount;
271
+ /* solhint-disable-next-line no-inline-assembly */
272
+ assembly {
273
+ // First 8 bytes (little-endian) of the funding output represents
274
+ // its value. To take the value, we need to jump over the first
275
+ // word determining the array length, load the array, and trim it
276
+ // by putting it to a bytes8.
277
+ fundingOutputAmount := mload(add(fundingOutput, 32))
278
+ }
279
+
280
+ deposit.amount = fundingOutputAmount;
281
+ deposit.depositor = reveal.depositor;
282
+ /* solhint-disable-next-line not-rely-on-time */
283
+ deposit.revealedAt = uint32(block.timestamp);
284
+ deposit.vault = reveal.vault;
285
+
286
+ emit DepositRevealed(
287
+ fundingTxHash,
288
+ reveal.fundingOutputIndex,
289
+ reveal.depositor,
290
+ reveal.blindingFactor,
291
+ reveal.walletPubKeyHash,
292
+ reveal.refundPubKeyHash,
293
+ reveal.refundLocktime,
294
+ reveal.vault
295
+ );
296
+ }
297
+
298
+ /// @notice Used by the wallet to prove the BTC deposit sweep transaction
299
+ /// and to update Bank balances accordingly. Sweep is only accepted
300
+ /// if it satisfies SPV proof.
301
+ ///
302
+ /// The function is performing Bank balance updates by first
303
+ /// computing the Bitcoin fee for the sweep transaction. The fee is
304
+ /// divided evenly between all swept deposits. Each depositor
305
+ /// receives a balance in the bank equal to the amount inferred
306
+ /// during the reveal transaction, minus their fee share.
307
+ ///
308
+ /// It is possible to prove the given sweep only one time.
309
+ /// @param sweepTx Bitcoin sweep transaction data.
310
+ /// @param merkleProof The merkle proof of transaction inclusion in a block.
311
+ /// @param txIndexInBlock Transaction index in the block (0-indexed).
312
+ /// @param bitcoinHeaders Single bytestring of 80-byte bitcoin headers,
313
+ /// lowest height first.
314
+ function sweep(
315
+ BitcoinTx.Info calldata sweepTx,
316
+ bytes memory merkleProof,
317
+ uint256 txIndexInBlock,
318
+ bytes memory bitcoinHeaders
319
+ ) external {
320
+ // TODO We need to read `fundingTxHash`, `fundingOutputIndex` from
321
+ // `sweepTx.inputVector`. We then hash them to obtain deposit
322
+ // identifier and read DepositInfo. From DepositInfo we know what
323
+ // amount was inferred during deposit reveal transaction and we
324
+ // use that amount to update their Bank balance, minus fee.
325
+ //
326
+ // TODO We need to validate if the sum in the output minus the
327
+ // amount from the previous wallet balance input minus fees is
328
+ // equal to the amount by which Bank balances were increased.
329
+ //
330
+ // TODO We need to validate `sweepTx.outputVector` to see if the balance
331
+ // was not transferred away from the wallet before increasing
332
+ // balances in the bank.
333
+ //
334
+ // TODO Delete deposit from unswept mapping or mark it as swept
335
+ // depending on the gas costs. Alternatively, do not allow to
336
+ // use the same TX input vector twice. Sweep should be provable
337
+ // only one time.
338
+ }
339
+
340
+ // TODO It is possible a malicious wallet can sweep deposits that can not
341
+ // be later proved on Ethereum. For example, a deposit with
342
+ // an incorrect amount revealed. We need to provide a function for honest
343
+ // depositors, next to sweep, to prove their swept balances on Ethereum
344
+ // selectively, based on deposits they have earlier received.
345
+ // (UPDATE PR #90: Is it still the case since amounts are inferred?)
346
+ }
@@ -0,0 +1,60 @@
1
+ // SPDX-License-Identifier: MIT
2
+
3
+ // ██████████████ ▐████▌ ██████████████
4
+ // ██████████████ ▐████▌ ██████████████
5
+ // ▐████▌ ▐████▌
6
+ // ▐████▌ ▐████▌
7
+ // ██████████████ ▐████▌ ██████████████
8
+ // ██████████████ ▐████▌ ██████████████
9
+ // ▐████▌ ▐████▌
10
+ // ▐████▌ ▐████▌
11
+ // ▐████▌ ▐████▌
12
+ // ▐████▌ ▐████▌
13
+ // ▐████▌ ▐████▌
14
+ // ▐████▌ ▐████▌
15
+
16
+ pragma solidity 0.8.4;
17
+
18
+ /// @title Bank Vault interface
19
+ /// @notice `IVault` is an interface for a smart contract consuming Bank
20
+ /// balances of other contracts or externally owned accounts (EOA).
21
+ interface IVault {
22
+ /// @notice Called by the Bank in `approveBalanceAndCall` function after
23
+ /// the balance `owner` approved `amount` of their balance in the
24
+ /// Bank for the vault. This way, the depositor can approve balance
25
+ /// and call the vault to use the approved balance in a single
26
+ /// transaction.
27
+ /// @param owner Address of the Bank balance owner who approved their
28
+ /// balance to be used by the vault
29
+ /// @param amount The amount of the Bank balance approved by the owner
30
+ /// to be used by the vault
31
+ // @dev The implementation must ensure this function can only be called
32
+ /// by the Bank. The Bank does _not_ guarantee that the `amount`
33
+ /// approved by the `owner` currently exists on their balance. That is,
34
+ /// the `owner` could approve more balance than they currently have.
35
+ /// This works the same as `Bank.approve` function. The vault must
36
+ /// ensure the actual balance is checked before performing any action
37
+ /// based on it.
38
+ function receiveBalanceApproval(address owner, uint256 amount) external;
39
+
40
+ /// @notice Called by the Bank in `increaseBalanceAndCall` function after
41
+ /// increasing the balance in the Bank for the vault. It happens in
42
+ /// the same transaction in which deposits were swept by the Bridge.
43
+ /// This allows the depositor to route their deposit revealed to the
44
+ /// Bridge to the particular smart contract (vault) in the same
45
+ /// transaction in which the deposit is revealed. This way, the
46
+ /// depositor does not have to execute additional transaction after
47
+ /// the deposit gets swept by the Bridge to approve and transfer
48
+ /// their balance to the vault.
49
+ /// @param depositors Addresses of depositors whose deposits have been swept
50
+ /// @param depositedAmounts Amounts deposited by individual depositors and
51
+ /// swept
52
+ /// @dev The implementation must ensure this function can only be called
53
+ /// by the Bank. The Bank guarantees that the vault's balance was
54
+ /// increased by the sum of all deposited amounts before this function
55
+ /// is called, in the same transaction.
56
+ function receiveBalanceIncrease(
57
+ address[] calldata depositors,
58
+ uint256[] calldata depositedAmounts
59
+ ) external;
60
+ }
@@ -0,0 +1,146 @@
1
+ // SPDX-License-Identifier: MIT
2
+
3
+ // ██████████████ ▐████▌ ██████████████
4
+ // ██████████████ ▐████▌ ██████████████
5
+ // ▐████▌ ▐████▌
6
+ // ▐████▌ ▐████▌
7
+ // ██████████████ ▐████▌ ██████████████
8
+ // ██████████████ ▐████▌ ██████████████
9
+ // ▐████▌ ▐████▌
10
+ // ▐████▌ ▐████▌
11
+ // ▐████▌ ▐████▌
12
+ // ▐████▌ ▐████▌
13
+ // ▐████▌ ▐████▌
14
+ // ▐████▌ ▐████▌
15
+
16
+ pragma solidity 0.8.4;
17
+
18
+ import "./IVault.sol";
19
+ import "../bank/Bank.sol";
20
+ import "../token/TBTC.sol";
21
+
22
+ /// @title TBTC application vault
23
+ /// @notice TBTC is a fully Bitcoin-backed ERC-20 token pegged to the price of
24
+ /// Bitcoin. It facilitates Bitcoin holders to act on the Ethereum
25
+ /// blockchain and access the decentralized finance (DeFi) ecosystem.
26
+ /// TBTC Vault mints and redeems TBTC based on Bitcoin balances in the
27
+ /// Bank.
28
+ /// @dev TBTC Vault is the owner of TBTC token contract and is the only contract
29
+ /// minting the token.
30
+ contract TBTCVault is IVault {
31
+ Bank public bank;
32
+ TBTC public tbtcToken;
33
+
34
+ event Minted(address indexed to, uint256 amount);
35
+
36
+ event Redeemed(address indexed from, uint256 amount);
37
+
38
+ modifier onlyBank() {
39
+ require(msg.sender == address(bank), "Caller is not the Bank");
40
+ _;
41
+ }
42
+
43
+ constructor(Bank _bank, TBTC _tbtcToken) {
44
+ require(
45
+ address(_bank) != address(0),
46
+ "Bank can not be the zero address"
47
+ );
48
+
49
+ require(
50
+ address(_tbtcToken) != address(0),
51
+ "TBTC token can not be the zero address"
52
+ );
53
+
54
+ bank = _bank;
55
+ tbtcToken = _tbtcToken;
56
+ }
57
+
58
+ /// @notice Transfers the given `amount` of the Bank balance from caller
59
+ /// to TBTC Vault, and mints `amount` of TBTC to the caller.
60
+ /// @dev TBTC Vault must have an allowance for caller's balance in the Bank
61
+ /// for at least `amount`.
62
+ /// @param amount Amount of TBTC to mint
63
+ function mint(uint256 amount) external {
64
+ address minter = msg.sender;
65
+ require(
66
+ bank.balanceOf(minter) >= amount,
67
+ "Amount exceeds balance in the bank"
68
+ );
69
+ _mint(minter, amount);
70
+ bank.transferBalanceFrom(minter, address(this), amount);
71
+ }
72
+
73
+ /// @notice Transfers the given `amount` of the Bank balance from the caller
74
+ /// to TBTC Vault and mints `amount` of TBTC to the caller.
75
+ /// @dev Can only be called by the Bank via `approveBalanceAndCall`.
76
+ /// @param owner The owner who approved their Bank balance
77
+ /// @param amount Amount of TBTC to mint
78
+ function receiveBalanceApproval(address owner, uint256 amount)
79
+ external
80
+ override
81
+ onlyBank
82
+ {
83
+ require(
84
+ bank.balanceOf(owner) >= amount,
85
+ "Amount exceeds balance in the bank"
86
+ );
87
+ _mint(owner, amount);
88
+ bank.transferBalanceFrom(owner, address(this), amount);
89
+ }
90
+
91
+ /// @notice Mints the same amount of TBTC as the deposited amount for each
92
+ /// depositor in the array. Can only be called by the Bank after the
93
+ /// Bridge swept deposits and Bank increased balance for the
94
+ /// vault.
95
+ /// @dev Fails if `depositors` array is empty. Expects the length of
96
+ /// `depositors` and `depositedAmounts` is the same.
97
+ function receiveBalanceIncrease(
98
+ address[] calldata depositors,
99
+ uint256[] calldata depositedAmounts
100
+ ) external override onlyBank {
101
+ require(depositors.length != 0, "No depositors specified");
102
+ for (uint256 i = 0; i < depositors.length; i++) {
103
+ _mint(depositors[i], depositedAmounts[i]);
104
+ }
105
+ }
106
+
107
+ /// @notice Burns `amount` of TBTC from the caller's account and transfers
108
+ /// `amount` back to the caller's balance in the Bank.
109
+ /// @dev Caller must have at least `amount` of TBTC approved to
110
+ /// TBTC Vault.
111
+ /// @param amount Amount of TBTC to redeem
112
+ function redeem(uint256 amount) external {
113
+ _redeem(msg.sender, amount);
114
+ }
115
+
116
+ /// @notice Burns `amount` of TBTC from the caller's account and transfers
117
+ /// `amount` back to the caller's balance in the Bank.
118
+ /// @dev This function is doing the same as `redeem` but it allows to
119
+ /// execute redemption without an additional approval transaction.
120
+ /// The function can be called only via `approveAndCall` of TBTC token.
121
+ /// @param from TBTC token holder executing redemption
122
+ /// @param amount Amount of TBTC to redeem
123
+ /// @param token TBTC token address
124
+ function receiveApproval(
125
+ address from,
126
+ uint256 amount,
127
+ address token,
128
+ bytes calldata
129
+ ) external {
130
+ require(token == address(tbtcToken), "Token is not TBTC");
131
+ require(msg.sender == token, "Only TBTC caller allowed");
132
+ _redeem(from, amount);
133
+ }
134
+
135
+ // slither-disable-next-line calls-loop
136
+ function _mint(address minter, uint256 amount) internal {
137
+ emit Minted(minter, amount);
138
+ tbtcToken.mint(minter, amount);
139
+ }
140
+
141
+ function _redeem(address redeemer, uint256 amount) internal {
142
+ emit Redeemed(redeemer, amount);
143
+ tbtcToken.burnFrom(redeemer, amount);
144
+ bank.transferBalance(redeemer, amount);
145
+ }
146
+ }