Skip to content
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

Track pending stake requests balance #191

Closed
wants to merge 1 commit into from

Conversation

nkuba
Copy link
Member

@nkuba nkuba commented Jan 22, 2024

Depends on: #91

Acre contract has a max deposit limit as a cap for new deposits. tBTC Depositor contract is a contract that performs stakes asynchronously, requiring tBTC Bridge to mint tBTC for deposited Bitcoin.
We cannot rely solely on the Acre.maxDeposit function for the maximum amount of stake users can make, as the limit may be reached while initiating the stake request and finalizing tBTC minting.

In the tBTC Depositor contract, we define a maxStake function that will consider the Acre.maxDeposit and the balance of pending stakes requested by the tBTC Depositor contract. The dApp should use this function to check the maximum allowed stake limit.

Deposit details revealed in the stake request may be invalid, and a Bitcoin funding transaction was not made. In such a case, we don't want the amount for this request to be included in the pending stakes balance forever. We introduce a timeout, after which the requested amount will be deducted from the balance of the pending stake.

When the stake request is finalized, it will be released from the pending stakes balance, as it will be deposited and included in the Acre.maxDeposit amount.

When the stake request is recalled and tBTC is withdrawn by the staker, the pending stakes balance will also be reduced.

Acre contract has a max deposit limit as a cap for new deposits.
tBTC Depositor contract is a contract that performs stakes
asynchronously, requiring tBTC Bridge to mint tBTC for deposited
Bitcoin.
We cannot rely solely on the `Acre.maxDeposit` function for the
maximum amount of stake users can make, as the limit may be
reached while initiating the stake request and finalizing tBTC minting.

In the tBTC Depositor contract, we define a `maxStake` function
that will consider the `Acre.maxDeposit` and the balance of pending
stakes requested by the tBTC Depositor contract. The dApp should
use this function to check the maximum allowed stake limit.

Deposit details revealed in the stake request may be invalid,
and a Bitcoin funding transaction was not made. In such a case,
we don't want the amount for this request to be included in the
pending stakes balance forever. We introduce a timeout, after
which the requested amount will be deducted from the balance of
the pending stake.

When the stake request is finalized, it will be released from
the pending stakes balance, as it will be deposited and included
in the `Acre.maxDeposit` amount.

When the stake request is recalled and tBTC is withdrawn by the
staker, the pending stakes balance will also be reduced.
@nkuba
Copy link
Member Author

nkuba commented Feb 29, 2024

Closing in favor of #253.

@nkuba nkuba closed this Feb 29, 2024
dimpar added a commit that referenced this pull request Mar 6, 2024
The Acre Bitcoin 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 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 process
- `finalizeStake` - 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 of
completion 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:

<details>
<summary>
Fee changes simulation
</summary>

**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%` and `max_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 actual `tx_fee` remains as debt
after the deposit is swept and we know it is lower than `max_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 compute `tbtc_net_amount = 100 BTC - 5 BTC - 2.85 BTC
- 0.01 BTC = 92.14 TBTC`. Note that `92.15 - 92.14 = 0.01 TBTC` is in
reserve to cover the `tx_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 value

- If `opt_minting_fee` increases, let's say to 5%, the `tbtc_net_amount
= 100 BTC - 5 BTC - 4.75 BTC - 0.01 BTC = 90.24 TBTC`. That means `92.15
- 90.24 = 1.91 TBTC` stays in reserve

- If `opt_minting_fee` decreases, let's say to 1%, the `tbtc_net_amount
= 100 BTC - 5 BTC - 0.95 BTC - 0.01 BTC = 94.04 TBTC`. The loss is
`92.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 to `0.05 BTC`, the `tbtc_net_amount = 100
BTC - 5 BTC - 2.85 BTC - 0.05 BTC = 92.10 BTC`. That means `92.15 -
90.10 = 0.05 TBTC` stays in reserve. We know that actual `tx_fee`
accrued as debt will be lower so we are covered. The corner case here is
`max_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 of `max_tx_fee`

- If `max_tx_fee` decreases to `0.002 BTC`, the `tbtc_net_amount = 100
BTC - 5 BTC - 2.85 BTC - 0.002 BTC = 92.148 BTC`. That means `92.15 -
92.148 = 0.002 TBTC` stays in reserve. We know that actual `tx_fee`
accrued as debt will be lower so we are covered. The corner case here is
`max_tx_fee` decreases AFTER deposit finalization. However, this is not
a problem as the reserve will still be greater than the debt (we used
old `max_tx_fee` to cut the reserve while the debt is below the new
`max_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 finalization
- `max_tx_fee` increases after deposit finalization

The 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, the `max_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%` and `max_tx_fee = 0.01 BTC` but the `actual tx_fee = 0.005 BTC`.
The actually minted amount will be `100 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 compute `tbtc_net_amount
= 100 BTC - 5 BTC - 2.85 BTC - 0.01 BTC = 92.14 TBTC`. Note that `94.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%, the `tbtc_net_amount
= 100 BTC - 5 BTC - 4.75 BTC - 0.01 BTC = 90.24 TBTC`. That means
`94.995 - 90.24 = 4.755 TBTC` stays in reserve

- If `opt_minting_fee` decreases, let's say to 1%, the `tbtc_net_amount
= 100 BTC - 5 BTC - 0.95 BTC - 0.01 BTC = 94.04 TBTC`. That means
`94.995 - 94.04 = 0.955 TBTC` stays in reserve

- If `max_tx_fee` increases to `0.05 BTC`, the `tbtc_net_amount = 100
BTC - 5 BTC - 2.85 BTC - 0.05 BTC = 92.10 BTC`. That means `94.995 -
92.10 = 2.895 TBTC` stays in reserve.

- If max_tx_fee decreases to `0.002 BTC`, the `tbtc_net_amount = 100 BTC
- 5 BTC - 2.85 BTC - 0.002 BTC = 92.148 BTC`. That means `94.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.

</details>

### 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant