Skip to content

Negative Interest: Zombie Edition#721

Merged
jalextowle merged 7 commits intomainfrom
jalextowle/feature/negative-interest-zombie
Jan 12, 2024
Merged

Negative Interest: Zombie Edition#721
jalextowle merged 7 commits intomainfrom
jalextowle/feature/negative-interest-zombie

Conversation

@jalextowle
Copy link
Copy Markdown
Contributor

@jalextowle jalextowle commented Jan 10, 2024

Negative Interest: 🧟 Edition

When _applyCheckpoint is used to close positions at maturity, we apply the share proceeds of closing these positions to the share reserves. At this point, the reserves will reflect any negative interest that accrued during the life of these positions.

Whenever a new checkpoint is minted, the zombie interest mechanism will deduct positive interest that accrues on the matured positions. Thus, after applying the checkpoint, the base amount reserved for the zombie positions will be equal the total base proceeds required to pay out all of the zombie positions. This fact makes zombie positions sensitive to negative interest that accrues in the current checkpoint as the following example demonstrates.

Example

Suppose that Hyperdrive has no outstanding longs or shorts, $c = 1$, $z = 1,000$, and $z_{zombie} = 0$.

  1. Alice pays 95 base for a long of 100 bonds.
    • $z \mathrel{+}= 95 \implies z = 1,095$
  2. The term passes and some interest accrues. The share price is now $c = 1.05$.
  3. checkpoint is called. Alice's proceeds are deducted from the share reserves and placed into the zombie share reserves.
    • $z \mathrel{-}= \tfrac{100}{1.05} \implies z = 999.76$
    • $z_{zombie} \mathrel{+}= \tfrac{100}{1.05} \implies z_{zombie} = 95.24$
  4. A checkpoint passes and more interest accrues. The share price is now $c = 1.06$.
  5. checkpoint is called and the zombie interest is deducted from the zombie share reserves and placed into the share reserves.
    • $z \mathrel{+}= \tfrac{1.06 - 1.05}{1.06} \cdot 95.24 \implies z = 1,000.66$
    • $z_{zombie} -= \tfrac{1.06 - 1.05}{1.06} \cdot 95.24 \implies z_{zombie} = 94.34$
  6. A negative interest accrues. The share price is now $1.055$.
  7. Alice attempts to close her position. Since the starting share price of $1$ is less than the ending share price of $1.05$, negative interest isn't assessed. Her share proceeds are calculated as $94.76$ which is greater than the zombie share reserves. This will be handled gracefully by zeroing out the zombie share reserves; however, the share reserves plus the zombie share reserves will be greater than the total number of shares tracked by the pool.

Proposed Fix #1 (Doesn't Work)

Whenever zombie interest accrues, we record the maximum share price at which we collected zombie interest, $c_{max}$. Having this variable around allows us to detect when negative interest accrued on the zombie share reserves and also has the side benefit of letting us easily collect zombie interest across checkpoint gaps.

Using $c_{max}$, we apply the full negative interest haircut to zombie longs and shorts. In the example, Alice's share proceeds would be adjusted as:

$$ 94.76 + \tfrac{1.055 - 1.06}{1.055} \cdot 94.76 = 94.31 $$

This is smaller than the zombie share reserves. The discrepancy of $0.02$ is caused by rounding, and the true result would be much closer to the zombie share reserves.

Problem

The problem with this fix is that it doesn't track which zombie positions were effected by the negative interest. Consider the following example:

  1. The pool is initialized. $c = 1$.
  2. Alice opens a long of 1,000 bonds.
  3. The term passes and interest accrues. $c = 1.05$.
  4. A checkpoint is minted. Alice's proceeds of 1,000 base are moved into the zombie share reserves and $c_{max}$ is set to $1.05$. $z_{zombie} = 952.38$.
  5. Negative interest accrues and $c = 1.04$.
  6. Bob opens a long of 100.
  7. The term passes and no interest accrues. $c = 1.04$.
  8. A checkpoint is minted. Bob's proceeds of 100 base are moved into the zombie share reserves. $z_{zombie} \mathrel{+}= 96.15 \implies z_{zombie} = 1,048.53$.

If Alice and Bob both redeemed their matured positions, the negative interest haircut would be applied to both of them. This means that Alice would receive $1000 \cdot (1 + \tfrac{1.04 - 1.05}{1.04}) = 990.47$. Bob would receive $100 \cdot (1 + \tfrac{1.04 - 1.05}{1.04}) = 99.04$. Collectively, this is equal to $\tfrac{990.47 + 99.04}{1.04} = 1047.60$, which is less than the zombie share reserves.

To summarize the problem, this solution incorrectly socializes losses across the entire zombie share reserves, which leaves some of the zombie share reserves unspent if all of zombie positions are closed after negative interest accrues.

Proposed Fix #2 (Works)

Anytime we move matured positions into (or out of) the zombie share reserves, we add (or subtract) the share proceeds to the zombie share reserves, $z_{zombie}$ AND we add (or subtract) the base proceeds to the new field called "zombie base reserves," $x_{zombie}$.

When we collect zombie interest, all we do is take the difference of $c \cdot z_{zombie}$ and $x_{zombie}$, check to see the difference is greater than zero, and move that amount of base to the share reserves.

When a trader closes a matured position, we check to see if $c \cdot z_{zombie} < x_{zombie}$. If the inequality holds, then negative interest has accrued on the zombie share reserves and we discount the trader's base proceeds by $\tfrac{c \cdot z_{zombie}}{x_{zombie}}$. Otherwise, we just pay out the base proceeds normally.

Let's try the example from "Proposed Fix #1" and see if we get a different result:

  1. The pool is initialized. $c = 1$.
  2. Alice opens a long of 1,000 bonds.
  3. The term passes and interest accrues. $c = 1.05$.
  4. A checkpoint is minted. Alice's proceeds of 1,000 base are moved into the zombie share reserves and the zombie base reserves. $z_{zombie} = 952.38$ and $x_{zombie} = 1,000$.
  5. Negative interest accrues and $c = 1.04$.
  6. Bob opens a long of 100.
  7. The term passes and no interest accrues. $c = 1.04$.
  8. A checkpoint is minted. Bob's proceeds of 100 base are moved into the zombie share reserves. $z_{zombie} \mathrel{+}= 96.15 \implies z_{zombie} = 1,048.53$ and $x_{zombie} = 1,100$.

If Alice and Bob both redeemed their matured positions, we discount both of them by the ratio $\tfrac{1.04 \cdot 1,048.53}{1,100} = \tfrac{1090.47}{1,100}$. This means that Alice would receive $1,000 \cdot \tfrac{1090.47}{1,100} = 991.33$. Bob would receive $100 \cdot \tfrac{1090.47}{1,100} = 99.1$. Collectively, this is equal to $\tfrac{991.33 + 99.1}{1.04} = 1048.49$, which is approximately equal to the zombie share reserves (the small discrepancy is caused by rounding to 2 significant digits).

This solution isn't perfect considering that Bob still takes a hit due to negative interest; however, it does ensure that all of the zombie share reserves can be paid out. Doing the accounting perfectly (by properly attributing negative interest to each checkpoint) appears to be computationally infeasible on Ethereum (I'm happy to be proven wrong here, but I haven't thought of a way to do it). IMO this solution is good enough.

@jalextowle jalextowle force-pushed the jalextowle/feature/negative-interest-zombie branch from efdaae5 to 6713d9b Compare January 10, 2024 20:54
@jalextowle jalextowle requested a review from jrhea January 10, 2024 20:59
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jan 10, 2024

Hyperdrive Gas Benchmark

Benchmark suite Current: 60742ab Previous: c2e0f6c Deviation Status
addLiquidity: min 1600 gas 1600 gas 0% 🟰
addLiquidity: avg 65742 gas 64248 gas 2.3254% 🚨
addLiquidity: max 275068 gas 275046 gas 0.0080% 🚨
checkpoint: min 1150 gas 1172 gas -1.8771%
checkpoint: avg 47693 gas 47975 gas -0.5878%
checkpoint: max 191154 gas 202123 gas -5.4269%
closeLong: min 1558 gas 1580 gas -1.3924%
closeLong: avg 27677 gas 27908 gas -0.8277%
closeLong: max 150993 gas 148684 gas 1.5530% 🚨
closeShort: min 1549 gas 1549 gas 0% 🟰
closeShort: avg 29944 gas 29962 gas -0.0601%
closeShort: max 147229 gas 147189 gas 0.0272% 🚨
initialize: min 1538 gas 1538 gas 0% 🟰
initialize: avg 213117 gas 214974 gas -0.8638%
initialize: max 254775 gas 256760 gas -0.7731%
openLong: min 1487 gas 1509 gas -1.4579%
openLong: avg 50043 gas 50152 gas -0.2173%
openLong: max 184695 gas 185904 gas -0.6503%
openShort: min 1608 gas 1519 gas 5.8591% 🚨
openShort: avg 49285 gas 47423 gas 3.9264% 🚨
openShort: max 179656 gas 161536 gas 11.2173% 🚨
redeemWithdrawalShares: min 1575 gas 1575 gas 0% 🟰
redeemWithdrawalShares: avg 20026 gas 22540 gas -11.1535%
redeemWithdrawalShares: max 105972 gas 105958 gas 0.0132% 🚨
removeLiquidity: min 1639 gas 1661 gas -1.3245%
removeLiquidity: avg 147194 gas 149144 gas -1.3075%
removeLiquidity: max 323566 gas 323560 gas 0.0019% 🚨

This comment was automatically generated by workflow using github-action-benchmark.

@coveralls
Copy link
Copy Markdown
Collaborator

coveralls commented Jan 10, 2024

Coverage Status

coverage: 95.069% (-0.2%) from 95.257%
when pulling 60742ab on jalextowle/feature/negative-interest-zombie
into 0953627 on main.

Comment thread contracts/src/interfaces/IHyperdrive.sol
Comment thread contracts/src/internal/HyperdriveBase.sol Outdated
Comment thread contracts/src/internal/HyperdriveLong.sol Outdated
Comment thread contracts/src/internal/HyperdriveShort.sol Outdated
Comment thread contracts/src/internal/HyperdriveBase.sol
Comment thread contracts/src/internal/HyperdriveCheckpoint.sol
Copy link
Copy Markdown
Contributor

@jrhea jrhea left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fantastic! I'd be good if you pasted the spec text into the PR description. I'll wait to approve until you have time to make changes we discussed

Comment thread test/integrations/hyperdrive/ZombieInterestTest.t.sol Outdated
Comment thread test/units/hyperdrive/CloseShortTest.t.sol
@jalextowle jalextowle force-pushed the jalextowle/feature/negative-interest-zombie branch from a90309b to 60742ab Compare January 12, 2024 01:41
@jalextowle jalextowle enabled auto-merge (squash) January 12, 2024 01:41
@jalextowle jalextowle merged commit cb519ad into main Jan 12, 2024
@jalextowle jalextowle deleted the jalextowle/feature/negative-interest-zombie branch January 12, 2024 03:43
@MrToph
Copy link
Copy Markdown

MrToph commented Feb 12, 2024

If Alice and Bob both redeemed their matured positions, we discount both of them by the ratio 1.04⋅1,048.531,100=1090.471,100. This means that Alice would receive 1,000⋅1090.471,100=991.33. Bob would receive 100⋅1090.471,100=99.1. Collectively, this is equal to 991.33+99.11.04=1048.49, which is approximately equal to the zombie share reserves (the small discrepancy is caused by rounding to 2 significant digits).

Another way to express this is that both receive their fair share of the remaining zombie reserves: shareProceeds = (dy / x_zombie) * z_zombie. I.e., Alice receives 1000/1100 * z_zombie, and Bob 100/1100 * z_zombie. Which means the losses are socialized proportionally to their bonds or original base proceeds.

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.

4 participants