diff --git a/docs/build-a-custom-amm/build-an-amm/create-custom-amm-with-novel-invariant.md b/docs/build-a-custom-amm/build-an-amm/create-custom-amm-with-novel-invariant.md index 8c1e0af5..4bb43c56 100644 --- a/docs/build-a-custom-amm/build-an-amm/create-custom-amm-with-novel-invariant.md +++ b/docs/build-a-custom-amm/build-an-amm/create-custom-amm-with-novel-invariant.md @@ -15,13 +15,15 @@ _This section is for developers looking to build a new custom pool type with a n ## Build your custom AMM -At a high level, creating a custom AMM on Balancer protocol involves the implementation of five functions `onSwap`, `computeInvariant` and `computeBalance` as well as `getMaximumSwapFeePercentage` and `getMinimumSwapFeePercentage`. +At a high level, creating a custom AMM on Balancer protocol involves the implementation of five functions `onSwap`, `computeInvariant` and `computeBalance` as well as the `ISwapFeePercentageBounds` and `IUnbalancedLiquidityInvariantRatioBounds` interfaces (which define `getMinimumSwapFeePercentage` / `getMaximumSwapFeePercentage`, and `getMinimumInvariantRatio` / `getMaximumInvariantRatio`, respectively). To expedite the development process, Balancer provides two contracts to inherit from: - [IBasePool.sol](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/IBasePool.sol) - This interface defines the required functions that every Balancer pool must implement - [BalancerPoolToken.sol](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/BalancerPoolToken.sol) - This contract implements the [ERC20MultiToken](/concepts/vault/erc20-multi-token.html) standard that enables your pool contract to be ERC20 compliant while delegating BPT accounting to the vault. For more information, refer to [BalancerPoolToken](/concepts/core-concepts/balancer-pool-tokens.html). -Both `IBasePool` and `BalancerPoolToken` are used across all core Balancer pools, even those implemented by Balancer Labs (ie: [WeightedPool](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-weighted/contracts/WeightedPool.sol#L18)). +Both `IBasePool` and `BalancerPoolToken` are used across all core Balancer pools, even those implemented by Balancer Labs (ie: [WeightedPool](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-weighted/contracts/WeightedPool.sol#L36)). + +Standard Balancer pools also implement the optional `Version` interface (for easy on- and off-chain verification of the contract version), and `IPoolInfo`, which exposes Vault getters (e.g., `getTokens`, and `getTokenInfo`) through the pool itself as a convenience. On top of that, the standard pools define custom interfaces that return structs corresponding to the immutable and dynamic data fields for the pools. For instance, Weighted Pools return the weights. Note that dynamic pool values (e.g., live balances), exposed through either `IPoolInfo` or the custom interfaces, will only be valid on-chain if the Vault is locked (i.e., not in the middle of a transaction). Below, we present a naive implementation of a two token `ConstantProductPool` & `ConstantSumPool` utilizing (X * Y = K) & (X + Y = K) as a reference for walking through the required functions necessary to implement a custom AMM on Balancer protocol: @@ -39,7 +41,9 @@ contract ConstantProductPool is IBasePool, BalancerPoolToken { IVault vault, string memory name, string memory symbol) - BalancerPoolToken(vault, name, symbol) {} + BalancerPoolToken(vault, name, symbol) { + // solhint-disable-previous-line no-empty-blocks + } /** * @notice Execute a swap in the pool. @@ -57,28 +61,29 @@ contract ConstantProductPool is IBasePool, BalancerPoolToken { /** * @notice Computes and returns the pool's invariant. - * @dev This function computes the invariant based on current balances - * @param balancesLiveScaled18 Array of current pool balances for each token in the pool, scaled to 18 decimals + * @dev This function computes the invariant based on current balances. + * @param balancesLiveScaled18 Token balances after paying yield fees, applying decimal scaling and rates + * @param rounding Rounding direction to consider when computing the invariant * @return invariant The calculated invariant of the pool, represented as a uint256 */ - function computeInvariant(uint256[] memory balancesLiveScaled18) - public - pure - returns (uint256 invariant) - { - // expected to work with 2 tokens only + function computeInvariant( + uint256[] memory balancesLiveScaled18, + Rounding rounding + ) public view returns (uint256 invariant) { + // expected to work with 2 tokens only. invariant = FixedPoint.ONE; for (uint256 i = 0; i < balancesLiveScaled18.length; ++i) { - invariant = invariant.mulDown(balancesLiveScaled18[i]); + invariant = rounding == Rounding.ROUND_DOWN + ? invariant.mulDown(balancesLiveScaled18[i]) + : invariant.mulUp(balancesLiveScaled18[i]); } // scale the invariant to 1e18 invariant = Math.sqrt(invariant) * 1e9; - } /** - * @dev Computes the new balance of a token after an operation, given the invariant growth ratio and all other - * balances. + * @notice Computes the new balance of a token after an operation. + * @dev This takes into account the invariant growth ratio and all other balances. * @param balancesLiveScaled18 Current live balances (adjusted for decimals, rates, etc.) * @param tokenInIndex The index of the token we're computing the balance for, in token registration order * @param invariantRatio The ratio of the new invariant (after an operation) to the old @@ -91,20 +96,30 @@ contract ConstantProductPool is IBasePool, BalancerPoolToken { ) external pure returns (uint256 newBalance) { uint256 otherTokenIndex = tokenInIndex == 0 ? 1 : 0; - uint256 newInvariant = computeInvariant(balancesLiveScaled18).mulDown(invariantRatio); + uint256 newInvariant = computeInvariant(balancesLiveScaled18, Rounding.ROUND_DOWN).mulDown(invariantRatio); newBalance = (newInvariant * newInvariant / balancesLiveScaled18[otherTokenIndex]); } - /// @return minimumSwapFeePercentage The minimum swap fee percentage for a pool + /// @inheritdoc ISwapFeePercentageBounds function getMinimumSwapFeePercentage() external pure returns (uint256) { return _MIN_SWAP_FEE_PERCENTAGE; } - /// @return maximumSwapFeePercentage The maximum swap fee percentage for a pool + /// @inheritdoc ISwapFeePercentageBounds function getMaximumSwapFeePercentage() external pure returns (uint256) { return _MAX_SWAP_FEE_PERCENTAGE; } + + /// @inheritdoc IUnbalancedLiquidityInvariantRatioBounds + function getMinimumInvariantRatio() external pure returns (uint256) { + return WeightedMath._MIN_INVARIANT_RATIO; + } + + /// @inheritdoc IUnbalancedLiquidityInvariantRatioBounds + function getMaximumInvariantRatio() external pure returns (uint256) { + return WeightedMath._MAX_INVARIANT_RATIO; + } } ``` @@ -133,11 +148,17 @@ contract ConstantSumPool is IBasePool, BalancerPoolToken { /** * @notice Computes and returns the pool's invariant. - * @dev This function computes the invariant based on current balances - * @param balancesLiveScaled18 Array of current pool balances for each token in the pool, scaled to 18 decimals + * @dev This function computes the invariant based on current balances. There is no precision loss for addition, + * so we can ignore the rounding direction. + * + * @param balancesLiveScaled18 Token balances after paying yield fees, applying decimal scaling and rates + * @param rounding Rounding direction to consider when computing the invariant * @return invariant The calculated invariant of the pool, represented as a uint256 */ - function computeInvariant(uint256[] memory balancesLiveScaled18) external pure returns (uint256 invariant) { + function computeInvariant( + uint256[] memory balancesLiveScaled18, + Rounding + ) public pure returns (uint256 invariant) { invariant = balancesLiveScaled18[0] + balancesLiveScaled18[1]; } @@ -145,7 +166,7 @@ contract ConstantSumPool is IBasePool, BalancerPoolToken { * @dev Computes the new balance of a token after an operation, given the invariant growth ratio and all other * balances. * @param balancesLiveScaled18 Current live balances (adjusted for decimals, rates, etc.) - * @param tokenInIndex The index of the token we're computing the balance for (tokens will be sorted alphanumerically) + * @param tokenInIndex The index of the token we're computing the balance for (tokens are sorted alphanumerically) * @param invariantRatio The ratio of the new invariant (after an operation) to the old * @return newBalance The new balance of the selected token, after the operation */ @@ -154,7 +175,7 @@ contract ConstantSumPool is IBasePool, BalancerPoolToken { uint256 tokenInIndex, uint256 invariantRatio ) external pure returns (uint256 newBalance) { - uint256 invariant = computeInvariant(balancesLiveScaled18); + uint256 invariant = computeInvariant(balancesLiveScaled18, Rounding.ROUND_DOWN); newBalance = (balancesLiveScaled18[tokenInIndex] + invariant.mulDown(invariantRatio)) - invariant; } @@ -168,12 +189,21 @@ contract ConstantSumPool is IBasePool, BalancerPoolToken { function getMaximumSwapFeePercentage() external pure returns (uint256) { return _MAX_SWAP_FEE_PERCENTAGE; } + + /// @inheritdoc IUnbalancedLiquidityInvariantRatioBounds + function getMinimumInvariantRatio() external pure returns (uint256) { + return WeightedMath._MIN_INVARIANT_RATIO; + } + + /// @inheritdoc IUnbalancedLiquidityInvariantRatioBounds + function getMaximumInvariantRatio() external pure returns (uint256) { + return WeightedMath._MAX_INVARIANT_RATIO; + } } ``` ::: - ::: info What does Scaled18 mean? Internally, Balancer protocol scales all tokens to 18 decimals to minimize the potential for errors that can occur when comparing tokens with different decimals numbers (ie: WETH/USDC). `Scaled18` is a suffix used to signify values has already been scaled. @@ -194,50 +224,54 @@ By implementing `computeInvariant` and `computeBalance`, your custom AMM will im Custom AMMs built on Balancer protocol are defined primarily by their invariant. Broadly speaking, an invariant is a mathematical function that defines how the AMM exchanges one asset for another. A few widely known invariants include [Constant Product (X * Y = K)](https://docs.uniswap.org/contracts/v2/concepts/protocol-overview/how-uniswap-works) and [StableSwap](https://berkeley-defi.github.io/assets/material/StableSwap.pdf). -Our two-token `ConstantSumPool` uses the constant sum invariant, or `X + Y = K`. To implement `computeInvariant`, we simply add the balances of the two tokens. For the `ConstantProductPool` the invariant calculation is the square root of the product of balances. This ensures invariant growth proportional to liquidity growth. +Our two-token `ConstantSumPool` uses the constant sum invariant, or `X + Y = K`. To implement `computeInvariant`, we simply add the balances of the two tokens. For the `ConstantProductPool`, the invariant calculation is the square root of the product of balances. This ensures invariant growth proportional to liquidity growth. ::: code-tabs#shell @tab Constant Product Pool ```solidity -function computeInvariant(uint256[] memory balancesLiveScaled18) - public - pure - returns (uint256 invariant) -{ - // expected to work with 2 tokens only - invariant = FixedPoint.ONE; - for (uint256 i = 0; i < balancesLiveScaled18.length; ++i) { - invariant = invariant.mulDown(balancesLiveScaled18[i]); + function computeInvariant( + uint256[] memory balancesLiveScaled18, + Rounding rounding + ) public view returns (uint256 invariant) { + // expected to work with 2 tokens only. + invariant = FixedPoint.ONE; + for (uint256 i = 0; i < balancesLiveScaled18.length; ++i) { + invariant = rounding == Rounding.ROUND_DOWN + ? invariant.mulDown(balancesLiveScaled18[i]) + : invariant.mulUp(balancesLiveScaled18[i]); + } + // scale the invariant to 1e18 + invariant = Math.sqrt(invariant) * 1e9; } - // scale the invariant to 1e18 - invariant = _sqrt(invariant) * 1e9; - -} ``` @tab Constant Sum Pool ```solidity -function computeInvariant(uint256[] memory balancesLiveScaled18) external pure returns (uint256 invariant) { - invariant = balancesLiveScaled18[0] + balancesLiveScaled18[1]; -} + function computeInvariant( + uint256[] memory balancesLiveScaled18, + Rounding + ) public pure returns (uint256 invariant) { + invariant = balancesLiveScaled18[0] + balancesLiveScaled18[1]; + } ``` ::: -For additional references, refer to the [WeightedPool](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-weighted/contracts/WeightedPool.sol#L73-L75) and [Stable Pool](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-stable/contracts/StablePool.sol#L94-L99) implementations. +For additional references, refer to the [WeightedPool](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-weighted/contracts/WeightedPool.sol#L121-L128) and [Stable Pool](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-stable/contracts/StablePool.sol#L150-L154) implementations. ::: info application context on computeBalance In the context of `computeBalance` the invariant is used as a measure of liquidity. What you need to consider when implementing all possible liquidity operations on the pool is that: -- bptAmountOut for an unbalanced add liquidity operation should equal bptAmountOut for a proportional add liquidity in the case that `exactAmountsIn` for the unbalanced add are equal to the `amountsIn` for the same bptAmountOut for both addLiquidity scenarios. `AddLiquidityProportional` does not call into the custom pool it instead calculates BptAmountOut within the [BasePoolMath.sol](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/solidity-utils/contracts/math/BasePoolMath.sol#L33-L40) whereas `addLiquidityUnbalanced` calls the custom pool's `computeInvariant`. +- bptAmountOut for an unbalanced add liquidity operation should equal bptAmountOut for a proportional add liquidity in the case that `exactAmountsIn` for the unbalanced add are equal to the `amountsIn` for the same bptAmountOut for both addLiquidity scenarios. `AddLiquidityProportional` does not call into the custom pool it instead calculates BptAmountOut within the [BasePoolMath.sol](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/BasePoolMath.sol#L50-L71) whereas `addLiquidityUnbalanced` calls the custom pool's `computeInvariant`. - the amountIn for an exactBptAmountOut in an `addLiquiditySingleTokenExactOut` should equal the amountIn for an unbalanced addLiquidity when the bptAmountOut is expected to be the same for both operations. `addLiquiditySingleTokenExactOut` uses `computeBalance` whereas `addLiquidityUnbalanced` uses `computeInvariant`. -These are important consideration to ensure that LPs get the same share of the pool's liquidity when adding liquidity. In a Uniswap V2 Pair adding liquidity not in proportional amounts get's [penalized](https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Pair.sol#L123), which you can also implement in a custom pool, as long as you accurately handle the bullet points outlined above. + +These are important consideration to ensure that LPs get the same share of the pool's liquidity when adding liquidity. In a Uniswap V2 Pair adding liquidity not in proportional amounts gets [penalized](https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Pair.sol#L123), which you can also implement in a custom pool, as long as you accurately handle the bullet points outlined above. ::: ### Compute Balance `computeBalance` returns the new balance of a pool token necessary to achieve an invariant change. It is essentially the inverse of the pool's invariant. The `invariantRatio` is the ratio of the new invariant (after an operation) to the old. -`computeBalance` is used for liquidity operations where the token amount in/out is unknown, specifically [`AddLiquidityKind.SINGLE_TOKEN_EXACT_OUT`](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/Vault.sol#L582-L594) and [`RemoveLiquidityKind.SINGLE_TOKEN_EXACT_IN`](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/Vault.sol#L788-L800). +`computeBalance` is used for liquidity operations where the token amount in/out is unknown, specifically [`AddLiquidityKind.SINGLE_TOKEN_EXACT_OUT`](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/Vault.sol#L642-L657) and [`RemoveLiquidityKind.SINGLE_TOKEN_EXACT_IN`](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/Vault.sol#L881-L895). You can see the implementations of the `ConstantProductPool` and `ConstantSumPool` below: @@ -251,7 +285,7 @@ function computeBalance( ) external pure returns (uint256 newBalance) { uint256 otherTokenIndex = tokenInIndex == 0 ? 1 : 0; - uint256 newInvariant = computeInvariant(balancesLiveScaled18).mulDown(invariantRatio); + uint256 newInvariant = computeInvariant(balancesLiveScaled18, Rounding.ROUND_DOWN).mulDown(invariantRatio); newBalance = (newInvariant * newInvariant / balancesLiveScaled18[otherTokenIndex]); } @@ -264,7 +298,7 @@ function computeBalance( uint256 tokenInIndex, uint256 invariantRatio ) external pure returns (uint256 newBalance) { - uint256 invariant = computeInvariant(balancesLiveScaled18); + uint256 invariant = computeInvariant(balancesLiveScaled18, Rounding.ROUND_DOWN); newBalance = (balancesLiveScaled18[tokenInIndex] + invariant.mulDown(invariantRatio)) - invariant; } @@ -275,7 +309,7 @@ function computeBalance( The `invariantRatio` refers to the new BPT supply over the total BPT supply and is calculated within the `BasePoolMath.sol` via `newSupply.divUp(totalSupply)`. ::: -For additional references, refer to the [WeightedPool](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-weighted/contracts/WeightedPool.sol#L78-L89) and [StablePool](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-stable/contracts/StablePool.sol#L101-L116) implementations. +For additional references, refer to the [WeightedPool](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-weighted/contracts/WeightedPool.sol#L131-L142) and [StablePool](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-stable/contracts/StablePool.sol#L157-L171) implementations. ### On Swap @@ -287,7 +321,7 @@ Balancer protocol supports two types of swaps: - `EXACT_IN` - The user defines the exact amount of `tokenIn` they want to spend. - `EXACT_OUT` - The user defines the exact amount of `tokenOut` they want to receive. -The `minAmountOut` or `maxAmountIn` are enforced by the [vault](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/Vault.sol#L368-L381) . +The `minAmountOut` or `maxAmountIn` are enforced by the [vault](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/Vault.sol#L405) . When swapping tokens, our constant `K` must remain unchanged. Since our two-token `ConstantSumPool` uses the constant sum invariant (`X + Y = K`), the amount entering the pool will always equal the amount leaving the pool: @@ -314,9 +348,9 @@ function onSwap(PoolSwapParams calldata params) external pure returns (uint256 a ::: -The `PoolSwapParams` struct definition can be found [here](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/IBasePool.sol#L59-L67). +The `PoolSwapParams` struct definition can be found [here](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/VaultTypes.sol#L238-L246). -For additional references, refer to the [WeightedPool](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-weighted/contracts/WeightedPool.sol#L100-L126) and [StablePool](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-stable/contracts/StablePool.sol#L118-L146) implementations. +For additional references, refer to the [WeightedPool](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-weighted/contracts/WeightedPool.sol#L150-L176) and [StablePool](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-stable/contracts/StablePool.sol#L174-L201) implementations. ### Constructor arguments @@ -330,7 +364,7 @@ At a minimum, your constructor should have the required arguments to instantiate constructor(IVault vault, string name, string symbol) BalancerPoolToken(vault, name, symbol) {} ``` -The approach taken by Balancer Labs is to define a [NewPoolParams](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-weighted/contracts/WeightedPool.sol#L30-L35) struct to better organize the constructor arguments. +The approach taken by Balancer Labs is to define a [NewPoolParams](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-weighted/contracts/WeightedPool.sol#L38-L44) struct to better organize the constructor arguments. ## Swap fees @@ -338,7 +372,7 @@ The approach taken by Balancer Labs is to define a [NewPoolParams](https://githu The charging of swap fees is managed entirely by the Balancer vault. The pool is only responsible for declaring the `swapFeePercentage` for any given swap or unbalanced liquidity operation on registration as well as declaring an minimum and maximum swap fee percentage. For more information, see [Swap fees](https://docs-v3.balancer.fi/concepts/vault/swap-fee.html). ::: info Do I need to take swap fees into account when implementing onSwap? -No, swap fees are managed entirely by the Balancer vault. For an `EXACT_OUT` swap, the amount in (`params.amountGivenScaled18`) will already have the swap fee removed before `onSwap` is called. +No, swap fees are managed entirely by the Balancer vault. For an `EXACT_OUT` swap, the amount in (`params.amountGivenScaled18`) will already have the swap fee removed before `onSwap` is called. Fees are always taken on `tokenIn`. ::: Balancer supports two types of swap fees: @@ -368,22 +402,23 @@ contract ConstantSumPool is IBasePool, IPoolLiquidity, BalancerPoolToken { ### Add liquidity custom For your AMM to support add liquidity custom, it must: -- Implement `onAddLiquidityCustom`, as defined [here](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/IPoolLiquidity.sol#L22-L28) +- Implement `onAddLiquidityCustom`, as defined [here](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/IPoolLiquidity.sol#L19-L32) - Set `LiquidityManagement.supportsAddLiquidityCustom` to `true` on pool register. ### Remove liquidity custom For your AMM to support remove liquidity custom, it must: -- Implement `onRemoveLiquidityCustom`, as defined [here](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/IPoolLiquidity.sol#L41-L47) +- Implement `onRemoveLiquidityCustom`, as defined [here](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/IPoolLiquidity.sol#L46-L59) - Set `LiquidityManagement.supportsRemoveLiquidityCustom` to `true` on pool register. ### Remove support for built in liquidity operations There may be instances where your AMM should not support specific built-in liquidity operations. If certain operations should be enabled in your custom pool is defined in `LiquidityManagement`. You can choose to: -- disable unbalanced liquidity add and removals -- enable add liquidity custom -- enable remove liquidity custom +- disable add and remove liquidity unbalanced (i.e., non-proportional; enabled by default. These cannot be disabled independently.) +- enable add liquidity custom (disabled by default) +- enable remove liquidity custom (disabled by default) +- enable donation (disabled by default) To achieve this, the respective entry in the `LiquidityManagement` struct needs to be set. @@ -392,6 +427,7 @@ struct LiquidityManagement { bool disableUnbalancedLiquidity; bool enableAddLiquidityCustom; bool enableRemoveLiquidityCustom; + bool enableDonation; } ``` These settings get passed into the [pool registration](/developer-reference/contracts/vault-api.html#registerpool) flow. @@ -399,7 +435,7 @@ These settings get passed into the [pool registration](/developer-reference/cont ## Testing your pool -Depending on the combination of liquidity operations you allow for your pool you need to ensure the correct amount of BPT get's minted whenever a user adds/removes liquidity unbalanced (which calls into `computeInvariant`) and proportional adds/removes (which does not call into the pool and solely relies on [BasePoolMath.sol](https://github.com/balancer/balancer-v3-monorepo/blob/4864599800adc88d6a53f0a5b71c8352eac2f3a1/pkg/solidity-utils/contracts/math/BasePoolMath.sol#L7)). Let's say your pool has reserves of [100, 100] and an `addLiquidityProportional([50,50])` gets the user 100 BPT in return, if the user were to `addLiquidityUnbalanced([50,50])` you must ensure that the amount of BPT that gets minted is the same as in the `addLiquidityProportional([50,50])` operation. Consider also reading through [liquidity invariant approximation](/concepts/vault/liquidity-invariant-approximation.html) to get more context on various combination of pool operations. +Depending on the combination of liquidity operations you allow for your pool you need to ensure the correct amount of BPT get's minted whenever a user adds/removes liquidity unbalanced (which calls into `computeInvariant`) and proportional adds/removes (which does not call into the pool and solely relies on [BasePoolMath.sol](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/BasePoolMath.sol#L10)). Let's say your pool has reserves of [100, 100] and an `addLiquidityProportional([50,50])` gets the user 100 BPT in return, if the user were to `addLiquidityUnbalanced([50,50])` you must ensure that the amount of BPT that gets minted is the same as in the `addLiquidityProportional([50,50])` operation. Consider also reading through [liquidity invariant approximation](/concepts/vault/liquidity-invariant-approximation.html) to get more context on various combination of pool operations. ## Deploying your pool diff --git a/docs/tools/README.md b/docs/tools/README.md index 10f2f3b3..9a1c8b55 100644 --- a/docs/tools/README.md +++ b/docs/tools/README.md @@ -8,4 +8,4 @@ --- -These tools have been developed by various teams contributing to the Balancer Ecosystem or wider DeFi through grants, as general contributors or separate platforms entirely. This hub serves as an open source repo of these tools created to help users and devs navigate, analyse and interact with the Balancer Ecosystem. This is continuously growing so if you have something you think should be on this page, get in touch and add your project to the [open source repo here](https://github.com/balancer/docs/tree/main/docs/tools) for it to be added! \ No newline at end of file +These tools have been developed by various teams contributing to the Balancer Ecosystem or wider DeFi through grants, as general contributors or entirely separate platforms. This hub serves as an open source repo of these tools created to help users and devs navigate, analyze and interact with the Balancer Ecosystem. This is continuously growing, so if you have something you think should be on this page, get in touch and add your project to the [open source repo here](https://github.com/balancer/docs/tree/main/docs/tools) for it to be added! \ No newline at end of file