-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Initial implementation of Bitcoin depositor (tBTC Depositor) #91
Conversation
We will integrate with tBTC Bridge and TBTCVault contracts. Here we define interfaces with the functions from external contracts that we will use. The interfaces are based on the changes proposed in keep-network/tbtc-v2#760.
The tBTC Depositor contract is an implementation of a depositor contract mentioned in [RFC-11](https://github.com/keep-network/tbtc-v2/blob/main/docs/rfc/rfc-11.adoc). This contract serves as an integrator of tBTC Bridge and Acre staking contract. Bitcoin deposits revealed via tBTC Depositor contract will be automatically staked in Acre after tBTC minting process is completed on the tBTC network side. The process consists of two steps: - initializeStake - this step reveals a deposit to tBTC Bridge to start minting process - finalizeStake - this step should be called after tBTC minting was completed to stake the tBTC tokens in Acre The functions are unprotected, it means that anyone can call them (e.g. bots). This solution will be used to enable gasless minting experience to the users, where only funding Bitcoin transaction will be required from them. The complexity comes with the actual minted tBTC amount calculation, as the fees are not unambiguously predictable. The stake finalization step calculates the amount to stake in Acre contract by deducting tBTC network minting fees from the initial funding transaction amount. The amount to stake is calculated depending on the process the tBTC was minted in: - for swept deposits: `amount = depositAmount - depositTreasuryFee - depositTxMaxFee` - for optimistically minted deposits: ``` amount = depositAmount - depositTreasuryFee - depositTxMaxFee - optimisticMintingFee ``` These calculation are simplified and can leave some positive imbalance in the Depositor 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 deposit reveal, there is a chance that the fee parameter is updated in the tBTC Vault contract before the optimistic minting is finalized. The imbalance should be donated to the Acre staking contract by the Governance.
The dependency is used in TbtcDepositor contract to calculate bitcoin transaction hash.
uint16 should be enough to track partners as the max value is 65535.
I reviewed the contracts high level. I'm not very familiar with tBTC integration things, so leaving the deeper review for Łukasz / Piotr. |
The contract is Ownable with governance as the owner.
Define a fee for tBTC Depositor contract that will be taken before staking tBTC in Acre contract. The fee should cover costs of running maintainer bots that will execute initializeStake and finalizeStake methods. The fee is calculated based on the divisor. Initial value for the fee is set to zero.
The treasury address will be used as a destination to transfer fees.
The value for minimum deposit amount in Acre contract should consider the amount that will be a result of TbtcDepositor stake. It means that it is dependend on tBTC minimum deposit (`depositDustThreshold`) and all the fees taken by tBTC minting process and TbtcDepositor fee.
The function estimates fees that will be taken for a deposit of a given amount by: - tBTC protocol minting - tBTC Depositor contract - Acre contract's deposit function
To match the contract API from #91.
✅ Deploy Preview for acre-dapp-testnet canceled.
|
|
||
await deployments.deploy("AcreBitcoinDepositor", { | ||
contract: | ||
process.env.HARDHAT_TEST === "true" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, I know what you're trying to achieve. My proposition was having two deployment files: 03_deploy_acre_bitcoin_depositor.ts
and 03_test_deploy_acre_bitcoin_depositor.ts
. In the first one we add skip if the network is hardhat. For the latter script we can add skip in case the network is other than hardhat. This way the logic from deployment script and package.json will go away. Anyway, this is just another option, non blocker and up to you.
To reduce confusion we put all the test contracts related to tBTC bridging into one file.
The contract defines `exposed_finalizeBridging` which is a naming pattern recommended by Foundry. We need to disable the rule for the file.
This PR enhances #91. Depends on keep-network/tbtc-v2#787 Depends on keep-network/tbtc-v2#791 ### Introduction In this PR we introduce a mechanism for the dApp to throttle stake request initialization. The staking flow for Bitcoin is asynchronous, consisting of a couple of stages between the user entering the staking form in the dApp and the tBTC being deposited in the stBTC vault. Since the stBTC vault introduced a limit for maximum total assets under management, there is a risk that the user will initialize staking that won't be able to finalize in stBTC, due to concurrent other users' stakes. We need to reduce such risk by throttling stake initialization flow in the dApp. ### Soft Cap and Hard Cap Limits #### Hard Cap stBTC contract defines a _maximum total assets limit_, that cannot be exceeded with new tBTC deposits. This is considered a hard limit, that when reached no new deposits can be made, and stake requests in the Bitcoin Depositor contract will be queued. These queued requests will have to wait until the limit is raised or other users withdraw their funds, making room for new users. #### Soft Cap Bitcoin Depositor Contract defines a _maximum total assets soft limit_, which is assumed to be much lower than the _hard cap_. The limit is used to throttle stakes and use a difference between _hard cap_ and _soft cap_ as a buffer for possible async stakes coming from the dApp. ### Stake Amount Limits #### Minimum Stake Limit The Bitcoin Depositor contract defines a _minimum stake limit_, that is used to define a minimum amount of a stake. The limit has to be higher than the _deposit dust threshold_ defined in the tBTC Bridge, which is validated on the deposit reveal. The _minimum stake limit_ has to take into account the _minimum deposit limit_ defined in the stBTC contract, and consider that the amount of tBTC deposited in the stBTC vault on stake finalization will be reduced by tBTC Bridge fees and Depositor fee. #### Maximum Stake Limit The Bitcoin Depositor contract defines a _maximum stake limit_, which is the maximum amount of a single stake request. This limit is used to introduce granularity to the stake amount and reduce the possibility of a big stake request using the whole soft limit shared across other concurrent stake requests. ### Usage in dApp The limits should be validated in the dApp staking flow to reduce the possibility of user stakes being stuck in the queue. The contract exposes two functions to be used in the dApp `minStakeInSatoshi` and `maxStakeInSatoshi`, for convenience the result is returned in the satoshi precision. ### Flow ![image](https://github.com/thesis/acre/assets/10741774/46f699d1-3607-4e27-b07a-de18b6078fbd) [source: Figjam](https://www.figma.com/file/5S8Wa5WudTQGbKMk7ETKq8/Bitcoin-Depositor-Staking-Limits?type=whiteboard&node-id=1-519&t=aCatffRBQpe4BCMn-4)
The Acre Bitcoin Depositor contract is an implementation of a depositor contract mentioned in RFC-11.
This contract serves as an integrator of tBTC Bridge and Acre stBTC staking contract.
Bitcoin deposits revealed via the tBTC Depositor contract will be automatically staked in Acre after the tBTC minting process is completed on the tBTC network side.
The contract is based on the
AbstractTBTCDepositor
(keep-network/tbtc-v2#778).The staking process consists of two steps:
initializeStake
- this step reveals a deposit to tBTC Bridge to start the minting processfinalizeStake
- this step should be called after tBTC minting on the tBTC Bridge side is completed, and tBTC token is transferred to the tBTC Depositor contract, this step calculates the approximate amount of minted tBTC tokens, and stakes them in stBTC contract.The functions are unprotected, meaning anyone can call them (e.g. bots). This solution will be used to enable a gasless minting experience for the users, where only a funding Bitcoin transaction will be required from them.
tBTC Minting Fees
The complexity comes with the actual minted tBTC amount calculation as the tBTC network fees are not unambiguously predictable.
The stake finalization step calculates the amount to stake in Acre by deducting approximate tBTC network minting fees from the initial funding transaction amount.
The calculation is performed in
AbstractTBTCDepositor._calculateTbtcAmount
function.The amount to stake is calculated d:
amount = depositAmount - depositTreasuryFee - optimisticMintingFee - depositTxMaxFee
These calculations are approximate and can leave some imbalance in the Depositor contract, as:
depositTreasuryFee
- this is a precise value, snapshotted at the moment of deposit reveal,optimisticMintingFee
- this is an optimistic minting fee calculated at the moment ofcompletion notification, there is a very low possibility that the fee was updated in the tBTC Vault contract between tBTC was minted and completion notification was submitted,
depositTxMaxFee
- this is the maximum transaction fee that can be deducted on Bitcoin transaction sweeping, in most cases it will be higher than the actual deducted amount, and will grow the reserve in the depositor contract.For the great majority of the deposits, such an algorithm will return a tbtcAmount slightly lesser than the actual amount of TBTC minted for the deposit. This will cause some TBTC to be left in the contract and ensure there is enough liquidity to finalize the deposit. However, in some rare cases, where the actual values of those fees change between the deposit minting and finalization, the tbtcAmount returned by this function may be greater than the actual amount of TBTC minted for the deposit.
If this happens and the reserve coming from previous deposits leftovers does not provide enough liquidity, the deposit will have to wait for finalization until the reserve is refilled by subsequent deposits or a manual top-up.
The Acre governance is responsible for handling such cases.
Fee changes simulation
Please see the simulation performed by @lukasz-zimnoch for fee changes analysis:
Fee changes simulation
Case 1 - Deposit is optimistically minted (currently 98.6% of mainnet deposits)
Let's say a deposit is 100 BTC,
treasury_fee = 5%
,opt_minting_fee = 3%
andmax_tx_fee = 0.01 BTC
. The actually minted amount will be(100 BTC * 0.95) * 0.97 = 92.15 TBTC
and this will be in direct control of the depositor contract. The actualtx_fee
remains as debt after the deposit is swept and we know it is lower thanmax_tx_fee = 0.01 BTC
The depositor contract does the finalization AFTER the deposit is optimistically minted. If it uses the
tbtc_net_amount = btc_gross_amount - treasury_fee - opt_minting_fee - max_tx_fee
equation, it will computetbtc_net_amount = 100 BTC - 5 BTC - 2.85 BTC - 0.01 BTC = 92.14 TBTC
. Note that92.15 - 92.14 = 0.01 TBTC
is in reserve to cover thetx_fee
debt which is more than enough.Now, consider fee changes. Fee changes matters only if they occur between deposit mint and deposit finalization. Let's suppose the depositor contract received the aforementioned
92.15 TBTC
. This is the outcome if particular fees change between deposit mint and finalization:If
treasury_fee
changes, it doesn't matter as we used the real snapshotted valueIf
opt_minting_fee
increases, let's say to 5%, thetbtc_net_amount = 100 BTC - 5 BTC - 4.75 BTC - 0.01 BTC = 90.24 TBTC
. That means92.15 - 90.24 = 1.91 TBTC
stays in reserveIf
opt_minting_fee
decreases, let's say to 1%, thetbtc_net_amount = 100 BTC - 5 BTC - 0.95 BTC - 0.01 BTC = 94.04 TBTC
. The loss is92.15 - 94.04 = -1.89 TBTC
. If there is a reserve, we take from there. If not, we can either pay the full balance 92.15 or wait for the gov or next deposits to fill the gap (new deposits won't have this error)If
max_tx_fee
increases to0.05 BTC
, thetbtc_net_amount = 100 BTC - 5 BTC - 2.85 BTC - 0.05 BTC = 92.10 BTC
. That means92.15 - 90.10 = 0.05 TBTC
stays in reserve. We know that actualtx_fee
accrued as debt will be lower so we are covered. The corner case here ismax_tx_fee
increases AFTER deposit finalization. This can make the real debt slightly uncovered but only if the actual tx_fee exceeds the old value ofmax_tx_fee
If
max_tx_fee
decreases to0.002 BTC
, thetbtc_net_amount = 100 BTC - 5 BTC - 2.85 BTC - 0.002 BTC = 92.148 BTC
. That means92.15 - 92.148 = 0.002 TBTC
stays in reserve. We know that actualtx_fee
accrued as debt will be lower so we are covered. The corner case here ismax_tx_fee
decreases AFTER deposit finalization. However, this is not a problem as the reserve will still be greater than the debt (we used oldmax_tx_fee
to cut the reserve while the debt is below the newmax_tx_fee
value)As you can see, almost all cases cause the positive imbalance and reserve increase. There are only two cases that cause negative imbalance:
opt_minting_fee
decreases between deposit minting and finalizationmax_tx_fee
increases after deposit finalizationThe first case requires a very unfortunate timing. It becomes a real problem only when the reserve is not enough to cover the loss. We can decrease the probability by keeping the delay between minting and finalization as short as possible.
The second case is similar. In practice,
max_tx_fee
never changed and the only reason to do so would be a global change of fee levels on Bitcoin. Moreover, themax_tx_fee
is just a cap and the actual fee is always lower as tBTC clients estimate it according to the network circumstances. Last but not least, this becomes a real problem only in case a deposit is not optimistically minted but swept instead (currently 1.4% of mainnet deposits went this way) so part of their minted amount is used to repaid the accrued debt. It also requires that the reserve is not enough to cover the loss.Case 2 - Deposit is not optimistically minted but swept (currently 1.4% of mainnet deposits)
Let's say a deposit is
100 BTC
,treasury_fee = 5%
,opt_minting_fee = 3%
andmax_tx_fee = 0.01 BTC
but theactual tx_fee = 0.005 BTC
. The actually minted amount will be100 BTC * 0.95 - 0.005 BTC = 94.995 TBTC
(opt_minting_fee
is not accrued here) and this will be in direct control of the depositor contract.The depositor contract does the finalization AFTER the deposit is swept. If it uses the
tbtc_net_amount = btc_gross_amount - treasury_fee - opt_minting_fee - max_tx_fee
equation, it will computetbtc_net_amount = 100 BTC - 5 BTC - 2.85 BTC - 0.01 BTC = 92.14 TBTC
. Note that94.995 - 92.14 = 2.855
TBTC stays in reserve.Now, consider fee changes. Fee changes matter only if they occur between deposit sweep and deposit finalization. Let's suppose the depositor contract received the aforementioned
94.995 TBTC
. This is the outcome if particular fees change between deposit sweep and finalization:If treasury_fee changes, it doesn't matter as we used the real snapshotted value
If
opt_minting_fee
increases, let's say to 5%, thetbtc_net_amount = 100 BTC - 5 BTC - 4.75 BTC - 0.01 BTC = 90.24 TBTC
. That means94.995 - 90.24 = 4.755 TBTC
stays in reserveIf
opt_minting_fee
decreases, let's say to 1%, thetbtc_net_amount = 100 BTC - 5 BTC - 0.95 BTC - 0.01 BTC = 94.04 TBTC
. That means94.995 - 94.04 = 0.955 TBTC
stays in reserveIf
max_tx_fee
increases to0.05 BTC
, thetbtc_net_amount = 100 BTC - 5 BTC - 2.85 BTC - 0.05 BTC = 92.10 BTC
. That means94.995 - 92.10 = 2.895 TBTC
stays in reserve.If max_tx_fee decreases to
0.002 BTC
, thetbtc_net_amount = 100 BTC - 5 BTC - 2.85 BTC - 0.002 BTC = 92.148 BTC
. That means94.995 - 92.148 = 2.847 TBTC
stays in reserve.As you can see, fee changes in this case do not cause a negative imbalance at all. The only risk is that this deposit is partially used to repay previous debts. However, we are balancing this unlikely case by always deducting the
opt_minting_fee
fee which commits to the reserve. The risk that the reserve won't be enough is low here.Maximum Stake Limit
The Acre contract has a limit for the maximum amount of deposits it can accept. Due to an asynchronous manner of staking where Bitcoin has to be first bridged to tBTC via tBTC Bridge, the limit for deposits may be reached in the meantime of stake request initialization and finalization. In such cases, the stake request has to wait until more deposits are accepted by the Acre contract (the maximum limit is increased, or funds are withdrawn from the Acre contract, making space for new deposits).
In this PR we added a path where stake requests that are unable to be finalized will be added to a queue and finalized later. If the user is not willing to wait anymore for the stake request to be finalized, they can recall the tBTC stake request and withdraw to their wallet the liquid tBTC that got minted.
This solution should be improved further to not allow the user to reduce the possibility of such a situation happening. Different possibilities are being explored as part of #191.
Refs: #60
Depends on: keep-network/tbtc-v2#760