-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
426 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,241 @@ | ||
// SPDX-License-Identifier: GPL-3.0-only | ||
pragma solidity ^0.8.21; | ||
|
||
import {BTCUtils} from "@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol"; | ||
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; | ||
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
|
||
import {Acre} from "../Acre.sol"; | ||
|
||
interface IBridge { | ||
struct BitcoinTxInfo { | ||
bytes4 version; | ||
bytes inputVector; | ||
bytes outputVector; | ||
bytes4 locktime; | ||
} | ||
|
||
struct DepositRevealInfo { | ||
uint32 fundingOutputIndex; | ||
bytes8 blindingFactor; | ||
bytes20 walletPubKeyHash; | ||
bytes20 refundPubKeyHash; | ||
bytes4 refundLocktime; | ||
address vault; | ||
} | ||
|
||
struct DepositRequest { | ||
address depositor; | ||
uint64 amount; | ||
uint32 revealedAt; | ||
address vault; | ||
uint64 treasuryFee; | ||
uint32 sweptAt; | ||
} | ||
|
||
function revealDepositWithExtraData( | ||
BitcoinTxInfo calldata fundingTx, | ||
DepositRevealInfo calldata reveal, | ||
bytes32 extraData | ||
) external; | ||
|
||
function deposits( | ||
uint256 depositKey | ||
) external view returns (DepositRequest memory); | ||
|
||
function depositParameters() | ||
external | ||
view | ||
returns ( | ||
uint64 depositDustThreshold, | ||
uint64 depositTreasuryFeeDivisor, | ||
uint64 depositTxMaxFee, | ||
uint32 depositRevealAheadPeriod | ||
); | ||
} | ||
|
||
interface ITBTCVault { | ||
struct OptimisticMintingRequest { | ||
// UNIX timestamp at which the optimistic minting was requested. | ||
uint64 requestedAt; | ||
// UNIX timestamp at which the optimistic minting was finalized. | ||
// 0 if not yet finalized. | ||
uint64 finalizedAt; | ||
} | ||
|
||
function optimisticMintingRequests( | ||
uint256 depositKey | ||
) external returns (OptimisticMintingRequest memory); | ||
|
||
function optimisticMintingFeeDivisor() external returns (uint32); | ||
} | ||
|
||
contract TbtcDepositor { | ||
using BTCUtils for bytes; | ||
using SafeERC20 for IERC20; | ||
|
||
struct DepositRequest { | ||
// Receiver of the stBTC token. | ||
address receiver; | ||
// Referral for the stake operation. | ||
uint16 referral; | ||
// UNIX timestamp at which the optimistic minting was requested. | ||
uint64 requestedAt; | ||
// UNIX timestamp at which the optimistic minting was finalized. | ||
// 0 if not yet finalized. | ||
uint64 finalizedAt; | ||
// Maximum Deposit Transaction Fee snapshotted from the Bridge contract | ||
// at the moment of deposit reveal. | ||
uint256 depositTxMaxFee; | ||
// Optimistic Minting Fee Divisor snapshotted from the TBTC Vault contract | ||
// at the moment of deposit reveal. | ||
uint32 optimisticMintingFeeDivisor; | ||
} | ||
|
||
IBridge public bridge; | ||
ITBTCVault public tbtcVault; | ||
Acre public acre; | ||
|
||
mapping(uint256 => DepositRequest) public depositRequests; | ||
|
||
constructor(IBridge _bridge, ITBTCVault _tbtcVault, Acre _acre) { | ||
bridge = _bridge; | ||
tbtcVault = _tbtcVault; | ||
acre = _acre; | ||
} | ||
|
||
// Extra Data 32 byte | ||
// receiver - address - 20 byte | ||
// referral - uint16 - 2 byte | ||
function initializeDeposit( | ||
IBridge.BitcoinTxInfo calldata fundingTx, | ||
IBridge.DepositRevealInfo calldata reveal, | ||
bytes32 extraData | ||
) external { | ||
bytes32 fundingTxHash = abi | ||
.encodePacked( | ||
fundingTx.version, | ||
fundingTx.inputVector, | ||
fundingTx.outputVector, | ||
fundingTx.locktime | ||
) | ||
.hash256View(); | ||
|
||
DepositRequest storage request = depositRequests[ | ||
calculateDepositKey(fundingTxHash, reveal.fundingOutputIndex) | ||
]; | ||
|
||
// TODO: Replace with custom errors | ||
require(request.requestedAt == 0, "deposit already initialized"); | ||
|
||
// solhint-disable-next-line not-rely-on-time | ||
request.requestedAt = uint64(block.timestamp); | ||
|
||
// First 20 bytes of extra data is receiver address. | ||
request.receiver = address(uint160(bytes20(extraData))); | ||
// Next 2 bytes of extra data is referral info. | ||
request.referral = uint16(bytes2(extraData << (8 * 20))); | ||
|
||
require(request.receiver != address(0), "receiver cannot be zero"); | ||
|
||
// Reveal the deposit to tBTC Bridge contract. | ||
bridge.revealDepositWithExtraData(fundingTx, reveal, extraData); | ||
|
||
// Store Deposit Transaction Max Fee. | ||
(, , uint64 depositTxMaxFee, ) = bridge.depositParameters(); | ||
request.depositTxMaxFee = depositTxMaxFee; | ||
|
||
// Store Optimistic Minting Fee Divisor. | ||
request.optimisticMintingFeeDivisor = tbtcVault | ||
.optimisticMintingFeeDivisor(); | ||
} | ||
|
||
function finalizeDeposit( | ||
bytes32 fundingTxHash, | ||
uint32 fundingOutputIndex | ||
) external { | ||
uint256 depositKey = calculateDepositKey( | ||
fundingTxHash, | ||
fundingOutputIndex | ||
); | ||
DepositRequest storage request = depositRequests[depositKey]; | ||
|
||
// TODO: Replace with custom errors | ||
require(request.requestedAt > 0, "deposit not initialized"); | ||
require(request.finalizedAt == 0, "deposit already finalized"); | ||
|
||
// Set finalization timestamp. | ||
// solhint-disable-next-line not-rely-on-time | ||
request.finalizedAt = uint64(block.timestamp); | ||
|
||
// Get deposit details from tBTC contracts. | ||
IBridge.DepositRequest memory bridgeDepositRequest = bridge.deposits( | ||
depositKey | ||
); | ||
ITBTCVault.OptimisticMintingRequest | ||
memory optimisticMintingRequest = tbtcVault | ||
.optimisticMintingRequests(depositKey); | ||
|
||
// tBTC amount calculation. | ||
// - for optimistically minted deposits: | ||
// amount = depositAmount - depositTreasuryFee - depositTxMaxFee - optimisticMintingFee | ||
// - for swept deposits. | ||
// amount = depositAmount - depositTreasuryFee - depositTxMaxFee | ||
// | ||
// NOTE: These calculation are simplified and can leave some positive | ||
// imbalance in this contract. | ||
// - depositTxMaxFee - this is a maximum transaction fee that can be deducted | ||
// on Bitcoin transaction sweeping, | ||
// - optimisticMintingFee - this is a optimistic minting fee snapshotted | ||
// at the moment of the reveal, minting finalization can be completed; | ||
// the final value deducted on optimistic minting finalization can change | ||
// in the meantime. | ||
// The imbalance should be donated to the Acre staking contract. | ||
|
||
// Extract initial value sent by the user. | ||
uint256 fundingTxAmount = bridgeDepositRequest.amount; | ||
|
||
uint256 amount = fundingTxAmount - | ||
bridgeDepositRequest.treasuryFee - | ||
request.depositTxMaxFee; | ||
|
||
// TODO: Revisit logic to find edge cases of mixed minting paths. | ||
// Check if deposit was optimistically minted. | ||
if (optimisticMintingRequest.finalizedAt > 0) { | ||
// TODO: Consider checking optimisticMintingFee once again and take | ||
// the max(optimisticMintingFeeOnReval, optimisticMintingFeeOnFinalize) | ||
// Subtract optimistic minting fee. | ||
uint256 optimisticMintingFeeDivisor = request | ||
.optimisticMintingFeeDivisor; | ||
|
||
uint256 optimisticMintingFee = optimisticMintingFeeDivisor > 0 | ||
? (fundingTxAmount / optimisticMintingFeeDivisor) | ||
: 0; | ||
|
||
amount -= optimisticMintingFee; | ||
// If not optimistically minted check if deposit was swept. | ||
} else { | ||
require( | ||
bridgeDepositRequest.sweptAt > 0, | ||
"tbtc bridge deposit not swept" | ||
); | ||
} | ||
|
||
// Stake tBTC in Acre. | ||
IERC20(acre.asset()).safeIncreaseAllowance(address(acre), amount); | ||
acre.stake(amount, request.receiver, request.referral); | ||
} | ||
|
||
/// @notice Calculates deposit key the same way as the Bridge contract. | ||
/// The deposit key is computed as | ||
/// `keccak256(fundingTxHash | fundingOutputIndex)`. | ||
function calculateDepositKey( | ||
bytes32 fundingTxHash, | ||
uint32 fundingOutputIndex | ||
) public pure returns (uint256) { | ||
return | ||
uint256( | ||
keccak256(abi.encodePacked(fundingTxHash, fundingOutputIndex)) | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
// SPDX-License-Identifier: GPL-3.0-only | ||
pragma solidity ^0.8.21; | ||
|
||
import {BTCUtils} from "@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol"; | ||
import {IBridge} from "../tbtc/TbtcDepositor.sol"; | ||
|
||
contract BridgeStub is IBridge { | ||
using BTCUtils for bytes; | ||
|
||
mapping(uint256 => DepositRequest) dep; | ||
|
||
uint64 depositTreasuryFeeDivisor = 2000; // 0.05% | ||
|
||
function revealDepositWithExtraData( | ||
BitcoinTxInfo calldata fundingTx, | ||
DepositRevealInfo calldata reveal, | ||
bytes32 extraData | ||
) external { | ||
bytes32 fundingTxHash = abi | ||
.encodePacked( | ||
fundingTx.version, | ||
fundingTx.inputVector, | ||
fundingTx.outputVector, | ||
fundingTx.locktime | ||
) | ||
.hash256View(); | ||
|
||
DepositRequest storage deposit = dep[ | ||
calculateDepositKey(fundingTxHash, reveal.fundingOutputIndex) | ||
]; | ||
|
||
require(deposit.revealedAt == 0, "Deposit already revealed"); | ||
|
||
bytes memory fundingOutput = fundingTx | ||
.outputVector | ||
.extractOutputAtIndex(reveal.fundingOutputIndex); | ||
|
||
uint64 fundingOutputAmount = fundingOutput.extractValue(); | ||
|
||
deposit.amount = fundingOutputAmount; | ||
deposit.depositor = msg.sender; | ||
/* solhint-disable-next-line not-rely-on-time */ | ||
deposit.revealedAt = uint32(block.timestamp); | ||
deposit.vault = reveal.vault; | ||
deposit.treasuryFee = depositTreasuryFeeDivisor > 0 | ||
? fundingOutputAmount / depositTreasuryFeeDivisor | ||
: 0; | ||
} | ||
|
||
function deposits( | ||
uint256 depositKey | ||
) external view returns (DepositRequest memory) { | ||
return dep[depositKey]; | ||
} | ||
|
||
function sweep(bytes32 fundingTxHash, uint32 fundingOutputIndex) public { | ||
DepositRequest storage deposit = dep[ | ||
calculateDepositKey(fundingTxHash, fundingOutputIndex) | ||
]; | ||
|
||
deposit.sweptAt = uint32(block.timestamp); | ||
|
||
// TODO: Mint TBTC | ||
} | ||
|
||
function calculateDepositKey( | ||
bytes32 fundingTxHash, | ||
uint32 fundingOutputIndex | ||
) public pure returns (uint256) { | ||
return | ||
uint256( | ||
keccak256(abi.encodePacked(fundingTxHash, fundingOutputIndex)) | ||
); | ||
} | ||
|
||
function depositParameters() | ||
external | ||
view | ||
returns ( | ||
uint64 depositDustThreshold, | ||
uint64 depositTreasuryFeeDivisor, | ||
uint64 depositTxMaxFee, | ||
uint32 depositRevealAheadPeriod | ||
) | ||
{ | ||
return ( | ||
1000000, // 1000000 satoshi = 0.01 BTC | ||
2000, // 1/2000 == 5bps == 0.05% == 0.0005 | ||
100000, // 100000 satoshi = 0.001 BTC | ||
15 days | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// SPDX-License-Identifier: GPL-3.0-only | ||
pragma solidity ^0.8.21; | ||
import {ITBTCVault} from "../tbtc/TbtcDepositor.sol"; | ||
|
||
contract TBTCVaultStub is ITBTCVault { | ||
uint32 public optimisticMintingFeeDivisor = 500; // 1/500 = 0.002 = 0.2% | ||
|
||
// request.optimisticMintFee = optimisticMintingFeeDivisor > 0 | ||
// ? (amountToMint / optimisticMintingFeeDivisor) | ||
// : 0; | ||
|
||
function optimisticMintingRequests( | ||
uint256 depositKey | ||
) external returns (OptimisticMintingRequest memory) { | ||
OptimisticMintingRequest memory result; | ||
return result; | ||
} | ||
} |
Oops, something went wrong.