-
Notifications
You must be signed in to change notification settings - Fork 45
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
base: main
Are you sure you want to change the base?
Conversation
@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. |
///@dev prevent race conditions when rebalancing | ||
bool private isRebalancing; | ||
|
||
modifier nonReentrantRebalance() { |
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.
Might be able to use our ReentrancyGuardTransient
for this part
wethIsEth, | ||
"" | ||
); | ||
uint256[] memory lpBptAmount = amountTokens[pool][msg.sender]; |
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.
The name is confusing here; you're tracking the portion of this pool's token balances for this sender, not the BPT
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 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 |
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.
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
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 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 |
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.
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.
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.
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) { |
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.
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.
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.
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 |
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.
// 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 |
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.
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.)
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.
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( |
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.
Could also be a before hook, to fail faster?
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.
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) { |
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.
Better to use positive logic here
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.
Yeah got it
/*************************************************************************** | ||
Router Functions | ||
***************************************************************************/ | ||
function addLiquidityProportional( |
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.
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".
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.
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
- Borrow 100 ETH
- 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
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.
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.
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); |
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.
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?
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.
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
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!!