Conversation
# Conflicts: # src/abi/v3/index.ts
| // target from it. | ||
|
|
||
| const maxAjustableTokenAmount = amountsIn[adjustableTokenIndex]; | ||
|
|
There was a problem hiding this comment.
This is where the core solution starts. I tried adding comments explaining how each step works, but I'll try to give more details here (and later write a more complete doc in Notion).
The whole goal is to find a bptAmount that will result in a maxAdjustableAmount close enough to the one provided by the user. Ideally approximating from below, so we favor leaving some dust instead of risking a revert transaction by requiring amounts bigger than what the user provided.
- start with an initial reference amount to calculate a proportional BPT. This initial guess is 50% of the maxAdjustableAmountIn provided by the user. The assumption is that ReClamms behave as a 50/50 Weighted pool and in those cases, the proportionalBptAmount calculated with 50% of the maxAdjustableAmountIn should result in a bptAmount pretty close to adding single-sided 100% of the maxAdustableAmountIn provided by the user.
1.1 the problem is that ReClamms only behave as 50/50 Weigthed when centeredness = 1. The further the current price is from the center, the bigger the error we get on the approximation above. That error has to be compensated somehow. One important thing to note is that the calculated bptAmount will result in a smaller maxAdjustableAmountIn when centeredness decreases. - we then query the add unbalanced with that bptAmount as input and get a maxAdjustableAmount as a result from it.
- we can then apply the ratio between maxAdjustableAmount and maxAdjustableAmountIn (in the opposite direction) to approximate the desired bptAmount (this is what I call correction factor)
- this actually overcorrects, resulting in a bptAmount that corresponds to a maxAdjustableAmount slightly greater that what the user provided
- we can then repeat the process to overcorrect in the opposite direction, resulting in a bptAmount that corresponds to a maxAdjustableAmount slightly smaller than what the user provided and that is much closer to the actual amount because the error decreases significantly on each step
- the existing tests already result in 0.004% error for an amountIn that is 60% of the pool balance, so 2 iterations seem to be fine (we'll need more tests to validate this)
I hope this makes it clear, but I'll spend more time polishing this next week ;)
# Conflicts: # src/abi/v3/index.ts # src/abi/v3/unbalancedAddViaSwapRouter.ts
| if (calculatedVsProvidedRatio > WAD) { | ||
| throw new SDKError( | ||
| 'Error', | ||
| 'Add Liquidity Unbalanced Via Swap', | ||
| 'Exact BPT out calculation failed. Please add a smaller or less unbalanced liquidity amount.', | ||
| ); | ||
| } |
There was a problem hiding this comment.
This extra check should prevent scenarios where the approximation fails. I was able to force it to fail by adding liquidity above 60% the pool balance. I'd say it's safer to throw in such cases and ask the user to provide a smaller/less unbalanced amount in.
# Conflicts: # test/v3/createPool/stable/stable.integration.test.ts
test/v3/addLiquidityUnbalancedViaSwap/addLiquidityUnbalancedViaSwap.integration.test.ts
Show resolved
Hide resolved
| const WETH = TOKENS[chainId].WETH; | ||
|
|
||
| // Toggle to control whether test results should be logged to files | ||
| const ENABLE_LOGGING = false; |
There was a problem hiding this comment.
Not sure if this logging is useful to keep?
There was a problem hiding this comment.
It was super useful for debugging, but I agree it might to be useful long term. I'll remove that 👍
| addLiquidityUnbalancedViaSwap.buildCall(buildCallInput); | ||
|
|
||
| expect(result).toHaveProperty('callData'); | ||
| expect(result).toHaveProperty('to'); |
There was a problem hiding this comment.
Should be able to test this is known contract address? Possible same with some other values?
There was a problem hiding this comment.
I refactored a bit this part of the test. Properties were already implicitly verified on all other tests, so I removed this section.
Contract address was indeed incorrect btw 👀
Apparently the attribute to from queryOutput is not used anywhere 😅
I think it's useful to keep it anyway, since it's relevant information to whoever is consuming that functionality from the SDK.
johngrantuk
left a comment
There was a problem hiding this comment.
Few small comments otherwise looking good.
test/v3/addLiquidityUnbalancedViaSwap/addLiquidityUnbalancedViaSwap.integration.test.ts
Show resolved
Hide resolved
| @@ -0,0 +1,212 @@ | |||
| import { encodeFunctionData, maxUint256, parseEther, zeroAddress } from 'viem'; | |||
There was a problem hiding this comment.
Noticed theres quite a few unused imports that don't seem to be picked up.
| const output: AddLiquidityUnbalancedViaSwapQueryOutput = { | ||
| pool: poolState.address, | ||
| bptOut, | ||
| exactAmountIn: TokenAmount.fromRawAmount( |
There was a problem hiding this comment.
Does this need to be returned? Might be confusing to user. Instead we can just hardcode 0 amount in buildCall?
There was a problem hiding this comment.
Yeah, the problem here is that buildCall requires exactToken address information and I don't have it available otherwise because buildCall doesn't expect poolState as input.
I believe the best way to provide that info is by returning exactTokenAmount in the queryOutput.
Let me know if you think there's a better approach.
| balanceDeltas[expectedAdjustableToken.index === 0 ? 1 : 0]; | ||
| const bptDelta = balanceDeltas[2]; | ||
|
|
||
| // TODO: check value is within range for adjustableAmountIn provided |
There was a problem hiding this comment.
It was, but I just pushed the fix - thanks 🙏
johngrantuk
left a comment
There was a problem hiding this comment.
Couple of small nits but otherwise LGTM. Thanks for getting it over the line!
Closes #796
This PR proposes a solution for adding liquidity single sided to 2 token pools using the AddLiquidityUnbalancedViaSwapRouter.
The main problem to solve is that we want to allow users to simply input amounts in, but the router operation also expects an
exactBptAmountOut.In order to solve that, we'll need to calculate/estimate the exactBptAmountOut corresponding to the provided amounts in.
The implemented approach relies on an initial guess which is then adjusted/corrected iteratively based on how far they are from on-chain query results. A full description of each step can be found documented both in the codebase and also in this Notion doc (with illustrative example).
Integration tests have shown the approach is able to calculate/estimate BPT amounts that approximate the amounts in provided within a tolerance of 0.01% even for add liquidity amounts up to 60% of the balance of the pool.