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

BAL Hookathon - ReBalancer Hook #113

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

utkarshdagoat
Copy link

@utkarshdagoat utkarshdagoat commented Oct 20, 2024

ReBalancer

ReBalancer is a balancer v3 hook that dynamically rebalances lp positions and fees based on real-time events and market implied volatility.

The Motivation

Volatility spikes are common during key real-world asset (RWA) events like central bank interest rate decisions, inflation reports (e.g., CPI), corporate earnings releases, bond coupon payments, and dividend announcements. Additionally, off-market hours in traditional finance and major geopolitical developments can drive price fluctuations.
RWAs that generate income, when these predictable price changes occur (like a bond's coupon payment), the value lost due to this change is permanent. That’s because the LP has effectively lost part of the asset’s value as it was transferred in the form of a coupon or dividend to the holder.
As the value of RWA tokens is expecteed to cross $16 Trillion by 2030 said by bcg. Solving such a problem with balancer hooks can attaract LPs that provide such assets.

The Problem

Arbitrageurs capture all expected price and volatility changes at the expense of LPs. These predictable arbitrages harm liquidity, lead to MEV leaks, and deter swappers due to poor liquidity

Solution

A hook that dynamically optimizes LP fees and positions by leveraging forward-looking volatility for flexible fee adjustments, redirecting value from arbitrageurs to LPs, and using anticipated price movements to rebalance LP positions in advance.

ReBalancer solves the problems by

  • Positions itself smartly in case of future events that leads to voltality of these assets.

  • Improve LP returns by dynamic rebalancing and fees.

  • Minimizes losses to arbitrages

  • Feedback
    The documentation is quite lacking in various areas. I generally tried to see the code more than read documentation about what going on!. Because I feel like making a hook or something like this can only be done hands on!

Check out the Readme for more details on implmentation and the maths behind Rebalancer!!

Copy link

vercel bot commented Oct 20, 2024

@utkarshdagoat is attempting to deploy a commit to the Matt Pereira's projects Team on Vercel.

A member of the Team first needs to authorize it.

@utkarshdagoat
Copy link
Author

Hey So my project had a little bit of math which did not rendered correctly on github for some reason. Here is the same stuff with a picture
image

///@dev prevent race conditions when rebalancing
bool private isRebalancing;

modifier nonReentrantRebalance() {

Choose a reason for hiding this comment

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

Might be able to use our ReentrancyGuardTransient for this part

wethIsEth,
""
);
uint256[] memory lpBptAmount = amountTokens[pool][msg.sender];

Choose a reason for hiding this comment

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

The name is confusing here; you're tracking the portion of this pool's token balances for this sender, not the BPT

Copy link
Author

Choose a reason for hiding this comment

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

Yup I got a little confused here. I just couldn't find a way to get active lp from the vault api and tried to do this myself last minute so got things a little mixed up will resolve this

TokenConfig[] memory,
LiquidityManagement calldata
) public override onlyVault returns (bool) {
///@dev removing for testing

Choose a reason for hiding this comment

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

The check would be that the factory is the WeightedPoolFactory - very important, or else it wouldn't work at all - and then that the pool is from that factory. Should be able to test with a real weighted pool

Copy link
Author

Choose a reason for hiding this comment

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

Yup so for the longest time I couldn't find a example for weighted pool test as for all the example it was done otherwise. So I don't know what I thought of when I tried to test for a normal pool (a little sleep deprived I guess) and commented this line of code out.

I finally looked at Weighted pool test itself and got how to set up weighted pool itself. So removing this line would work and I could also put in a test for wrong factory check

Setter Functions
***************************************************************************/
function setWeightedPoolFactoryAddress(address newWeightedPoolFactory) external onlyOwner {
///@dev I am a little confused that whether to check if the factory is disable or not would love

Choose a reason for hiding this comment

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

If the factory is disabled, no more pools can be created from it. Existing pools will continue to work (unless we're migrating because of an issue and they're all paused).

In production, either you'd have a single immutable factory - meaning you'd need to redeploy a new hook with a new factory if Balancer migrated it - or have a set of valid factories that you could add/remove from.

Copy link
Author

Choose a reason for hiding this comment

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

Got it thanks for the answering

function setRebalanceData(address pool, RebalanceData[] memory _rebalanceData) external {
PoolRoleAccounts memory roleAccounts = _vault.getPoolRoleAccounts(pool);

if (msg.sender != roleAccounts.poolCreator) {
Copy link

@EndymionJkb EndymionJkb Oct 22, 2024

Choose a reason for hiding this comment

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

This is mixing concerns a bit. The purpose of the pool creator is to set pool creator fees. You're using it for a different purpose here - and it prevents you from using it with standard WeightedPools, which can't have a pool creator (though this can be circumvented in test code with mocks).

Could use a custom factory to record the pool deployer.

Copy link
Author

Choose a reason for hiding this comment

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

Got it didn't know weighted pools had no creators I guess a custom factory would be the way to go as this part should only be done by the creator.

I was able to get the test passed with vm.mock's so yeah

(daiIdx, usdcIdx) = getSortedIndexes(address(dai), address(usdc));
}

// Overrides approval to include NFTRouter

Choose a reason for hiding this comment

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

Suggested change
// Overrides approval to include NFTRouter
// Overrides approval to include Rebalancer

data[i] = ReBalancerHook.RebalanceData({ minRatio: minRatios[i], rebalanceRequired: false });
}

vm.prank(address(0)); // Only pool creator can set rebalance data

Choose a reason for hiding this comment

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

Interesting trick :) Not sure what you would do in production, though. Maybe you would need a custom Weighted Pool Factory that deploys the regular WeightedPool, but keeps track of the deployer in the factory, so you could query not only whether a given pool came from that factory, but who the deployer was. (In practice, you could just check that the deployer is non-zero.)

Copy link
Author

Choose a reason for hiding this comment

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

Okay so when I was trying to find the interfaces for finding owner from the pool and only looked at code for weighted pool directly from the code of monorepo and did not have look at the factory code. I will definetly fix this

}

/// @inheritdoc BaseHooks
function onAfterRemoveLiquidity(

Choose a reason for hiding this comment

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

Could also be a before hook, to fail faster?

Copy link
Author

Choose a reason for hiding this comment

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

I mean if I do it before hook I would need to do a rebalance i.e removing and adding liquidity before the swap wouldn't that change the swap qoute for that swap?

bool remove,
bool wethIsEth
) internal {
if (!remove) {

Choose a reason for hiding this comment

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

Better to use positive logic here

Copy link
Author

Choose a reason for hiding this comment

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

Yeah got it

/***************************************************************************
Router Functions
***************************************************************************/
function addLiquidityProportional(

Choose a reason for hiding this comment

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

So if I understand this, you're making people add/remove/swap through this hook (it reverts if you use a standard router). When they add, the tokens go to the Vault, but the hook gets the BPT. When they remove, the hook burns the BPT and gives them the tokens from the Vault. It's tracking the LPs and their token balances inside the hook (whereas the Vault just has the total).

When they swap, it does the normal operation... then potentially redistributes tokens afterward by pulling tokens from LPs and sending back adjusted amounts (fuzzy on how this settles), which seems like it could be done more simply as a "counter-swap".

Copy link
Author

@utkarshdagoat utkarshdagoat Oct 22, 2024

Choose a reason for hiding this comment

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

Yeah so I guess the BPT tracking part got a little mixed up it was not my intention to take bpt but to only track the bpt amount to get the active LPs for the pool as I couldn't find the API/interface to do so!

So the problem I tried to solve could be understand as follows. Let's say we have stETH/eth pool:
So as we know when the ethereum network distributes staking rewards the stETH token rebases i.e each token is worth a little more than ETH

So let me take you through an example
The Initial State for the pool might look like:

  • 1 stETH = 1 ETH
  • LP Pool has 100 ETH and 100 stETH
  • Pool value = 200 ETH equivalent
  • Expected staking reward: 0.5%
  • Post-rebase: 1 stETH will = 1.005 ETH

Now a rebase happens daily so what an arbitrageur can do pre rebase is

  1. Borrow 100 ETH
  2. Sell ETH to pool for stETH at 1:1 ratio
    and after rebase as the stETH value increases
  • stETH in pool rebases to 1.005 ETH each
  • Arbitrageur buys back 100. 5 ETH using 100 stETH
  • Returns borrowed 100 ETH
  • Profit: 0.5 ETH

Therefore the vaule - 0.5 which was the LP's staking reward was lost to arbitrageur.

This would be the case for every dividend/yeild based RWAs pool

So what my plan is to have an oracle tell me that an event like rebase is coming for my pool which is given by the predicted price and when this price is significant my pool rebalncing the liquidity pool so in this case it would take out 0.5 stETH out and protect the LPs from losing out ont the profit

Copy link
Author

Choose a reason for hiding this comment

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

And the principal for rebalancing the pool is actually very simple just keep the value same in the pool. I worked out the math and in terms of token changes it would look something like this
image
Where Bt' is our new balance and rt is the ratio of new price and last price and K are the number of tokens

Copy link
Author

Choose a reason for hiding this comment

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

For the counter swap argument I think it can be done for a two token pool. But for multiple token I would have to implement some sort of algorithm that would give to swap which token for which as I can only swap two tokens at a time (I think).
So yeah counter swap is better but could get complex with increased number of tokens!

userData
);
address[] storage lps = liquidityProviders[pool];
lps.push(msg.sender);

Choose a reason for hiding this comment

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

Would create duplicates of the LP on every add. Do you really need all the token balances? Could there just be a mapping of LP -> BPT?

Copy link
Author

@utkarshdagoat utkarshdagoat Oct 22, 2024

Choose a reason for hiding this comment

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

Yeah it would. damn I did write some trash code! Sorry,

for making my case for the longest I did not had any idea the hookathon extended. Only got to know on Wednesday and started coding on friday night and sunday night was submission. So I know this is not an excuse to write bad code but hopefully you understand

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.

2 participants