Skip to content

Commit

Permalink
Merge pull request #164 from balancer/cumulative-updates
Browse files Browse the repository at this point in the history
Cumulative updates (rest of the pages)
  • Loading branch information
mkflow27 authored Oct 10, 2024
2 parents 5f5ab3d + 70dba95 commit a6f0df0
Show file tree
Hide file tree
Showing 41 changed files with 276 additions and 225 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ To expedite the development process, Balancer provides two contracts to inherit

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)).

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:
Below, we present a naive implementation of a two token `ConstantProductPool` and `ConstantSumPool` utilizing (X * Y = K) and (X + Y = K) as references for walking through the required functions necessary to implement a custom AMM on Balancer protocol:

::: code-tabs#shell
@tab Constant Product Pool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ To fully set up a new custom pool so that normal liquidity operations and swaps
2. Deploy the pool contract using the factory's `_create` function
3. Register the pool using the factory's `_registerPoolWithVault` function
4. Use [Permit2](https://github.com/Uniswap/permit2) to approve the Router to spend the tokens that will be used to initialize the pool
5. Call [`router.initialize()`](https://github.com/balancer/balancer-v3-monorepo/blob/e9bd6b0b154f2bd083a5049267b7a417c5a2c984/pkg/interfaces/contracts/vault/IRouter.sol#L39-L56) to seed the pool with initial liquidity
5. Call [`router.initialize()`](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/IRouter.sol#L46-L53) to seed the pool with initial liquidity

::: tip

Expand Down Expand Up @@ -155,6 +155,6 @@ After a custom pool has been deployed and registered, the next step is to add in

1. Ensure the [Permit2](https://github.com/Uniswap/permit2) contract has been granted sufficient allowance to spend tokens on behalf of the `msg.sender`
2. Transfer sufficient allowance to the [Router](https://docs-v3.balancer.fi/concepts/router/overview.html) with [`Permit2.approve`](https://github.com/Uniswap/permit2/blob/cc56ad0f3439c502c246fc5cfcc3db92bb8b7219/src/AllowanceTransfer.sol#L25-L30)
3. Call [`router.initialize()`](https://github.com/balancer/balancer-v3-monorepo/blob/e9bd6b0b154f2bd083a5049267b7a417c5a2c984/pkg/interfaces/contracts/vault/IRouter.sol#L39-L56)
3. Call [`router.initialize()`](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/IRouter.sol#L46-L53)

After a pool has been initialized, normal liquidity operations and swaps are instantly enabled.
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ function onRegister(

The `onRegister` function enables developers to implement custom validation logic to ensure the registration is valid. When a new pool is registered, a hook address can be provided to "link" the pool and the hook. At this stage, the `onRegister` function is invoked by the Vault, and it must return true for the registration to be successful. If the validation fails, the function should return false, preventing the registration from being completed.

In this example we validate that the `factory` param forwarded from the Vault matches the `allowedFactory` set during the hook deployment, and that the pool was deployed by that factory. If successful, it emits an event for tracking by offchain processes.
In this example we validate that the `factory` param forwarded from the Vault matches the `allowedFactory` set during the hook deployment, and that the pool was deployed by that factory. If successful, it emits an event for tracking by off-chain processes.

### Implementing the Swap Fee Logic

Expand Down
20 changes: 10 additions & 10 deletions docs/concepts/core-concepts/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ order: 1

The Balancer protocol architecture comprises three primary components, each strategically designed to enhance flexibility and minimize the intricacies involved in constructing pools. By distributing responsibilities across these components, Balancer simplifies pool development, empowering builders to focus on innovation rather than grappling with complex code.

- Router: Serves as the user's gateway to the protocol, offering straightforward interfaces for executing operations.
- Router: Serves as the user's gateway to the protocol, offering straightforward interfaces for executing operations. (This includes the basic Router, BatchRouter, and CompositeLiquidityRouter.)
- Vault: Centralizes liquidity operations and manages accounting, streamlining the handling of token balances across multiple pools.
- Pool: Exposes precise pool mathematics through invariant calculations, enabling developers to harness powerful functionalities without delving into intricate details.

This design philosophy ensures that building pools on Balancer is intuitive and efficient, with the vault shouldering the burden of complexity, allowing pool creators to unleash their creativity without worrying about accounting details.
This design philosophy ensures that building pools on Balancer is intuitive and efficient, with the vault shouldering the burden of complexity, allowing pool creators to unleash their creativity without worrying about accounting details. The Vault is part of the core protocol, but users can build custom pools and routers.

## Simplified transaction flow

Expand All @@ -37,9 +37,9 @@ The Vault verifies that the Router has correctly settled its accrued debts and c

![Detailed Router Vault interaction](/images/architecture-detailed.png)

1. The Balancer Router is the main interface for interacting with Balancer, providing a user-friendly way to access functions and simplify interactions with the Vault. Any smart contract can serve as a Router, tailored to the specific use case.
1. The Balancer Router is the main interface for interacting with Balancer, providing a user-friendly way to access functions and simplify interactions with the Vault. Any smart contract can serve as a Router, tailored to the specific use case. For instance, hooks and pools can "act" as routers and make calls on the Vault. It's also possible to create custom routers to combine Vault operations in novel ways (e.g., interact with a custom protocol).

2. The Router calls the Vault's `unlock` method and opens up the vault to record debts & credits based on liquidity or swap operations. This allows operations on the Vault to be combined atomically and still ensure correct accounting.
2. The Router calls the Vault's `unlock` method and opens up the vault to record debts and credits based on liquidity or swap operations. This allows operations on the Vault to be combined atomically and still ensure correct accounting.

3. With the Vault unlocked, the Vault calls back into the Router. This is where arbitrary logic can be executed, including any combination of calls to Vault primitives, interacting with external protocols, etc.
For example, in the case of a swap action, the Vault calls the Router's specific action hook implementation, such as `swapSingleTokenHook`, and passes the initial function payload from step 1 back to the Router to continue the regular transaction flow.
Expand All @@ -53,13 +53,13 @@ The inputs from step 1 are passed to the Vault's core functions, such as swap.
These functions calculate the tokens that need to be deposited into or withdrawn from the Vault.
Proportional liquidity management operations are an exception to this rule: the math is solved at the Vault level in this case.

6. The outcomes of these calculations are attributed as either debt or credit, which must be settled at a later stage. Additionally, the required amount of Balancer Pool Tokens (BPT) is minted or burned accordingly in the case of liquidity management operations.
6. The outcomes of these calculations are categorized as either debts or credits, which must be settled at a later stage. Additionally, in the case of liquidity management operations, the required amount of Balancer Pool Tokens (BPT) is minted or burned.

7. After debt & credit has been recorded for and shared with the Router by the Vault, the execution flow is passed to the Router. This allows the Router to be aware of the amounts owed (both by the sender to the Vault, and vice-versa).
It is important to mention that the Router contract has the ability to retrieve the current debt and credit owed to the Vault at any point during the execution by calling a specific function on the Vault.
7. After debts and credits have been recorded and shared with the Router by the Vault, the execution flow is passed to the Router. This allows the Router to be aware of the amounts owed (both by the sender to the Vault, and vice-versa).
It is important to mention that the Router contract has the ability to retrieve the current debts and credits associated with the Vault at any point during the execution by calling a specific function on the Vault.

8. The Router is responsible for settling the remaining debt and credit, which must be done for the transaction to succeed. If ETH or WETH is to be used in the transaction, the Router wraps or unwraps Ether right before settling WETH debts.
8. The Router is responsible for settling the remaining debts and credits, which must be done for the transaction to succeed. If ETH or WETH is to be used in the transaction, the Router wraps or unwraps Ether right before settling WETH debts.

9. When the router is done with settlement, the Vault verifies that all credit and debt accrued during the operations has been settled as the final step. Once the verification is complete, the Vault is locked again. If all debt has been correctly settled, the transaction will succeed; otherwise, it will be reverted.
9. When the router is done with settlement, the Vault verifies that all credits and debts accrued during the operations have been settled as the final step. Once the verification is complete, the Vault is locked again. If all debt has been correctly settled, the transaction will succeed; otherwise, it will be reverted.

On top of the basic workflow, pools can be extended with standalone hooks contracts that can be leveraged at different stages of the pool's lifecycle. These hooks contracts can be called either before or after a pool operation (i.e. step 5), depending on how the pool is configured during deployment. By utilizing hooks, developers can customize and enhance the functionality of pools, enabling the integration of features like oracles or time-weighted average market maker capabilities. See [hooks](./hooks.md) article for more detailed information about it.
On top of the basic workflow, pools can be extended with standalone hooks contracts that can be leveraged at different stages of the pool's lifecycle. These hooks contracts can be called either before or after a pool operation (i.e. step 5), depending on how the pool is configured during deployment. By utilizing hooks, developers can customize and enhance the functionality of pools, enabling the integration of features like oracles or time-weighted average market maker capabilities. See [hooks](./hooks.md) article for more detailed information.
13 changes: 10 additions & 3 deletions docs/concepts/core-concepts/balancer-pool-tokens.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,24 @@ The BalancerPoolToken contract adheres to the ERC20 token standard by incorporat

Here's how the [`BalancerPoolToken`](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/BalancerPoolToken.sol) contract achieves this:

Inheritance: The [`BalancerPoolToken`](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/BalancerPoolToken.sol) contract also inherits from `IERC20`, `IERC20Metadata` and `IERC20Permit`. This means it has all the methods and properties required by the ERC20 standard.
Inheritance: The [`BalancerPoolToken`](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/BalancerPoolToken.sol) contract also inherits from `IERC20`, `IERC20Metadata`, `IERC20Permit`, `IRateProvider`, `EIP712`, `Nonces`, `ERC165` and our local `VaultGuard`. This means it has all the methods and properties required by the ERC20 standard.

- Delegation: The BalancerPoolToken contract doesn't manage the token state itself. Instead, it delegates this responsibility to the Vault contract. For example, the totalSupply, balanceOf, transfer, allowance, approve, and transferFrom methods all call the corresponding methods on the Vault contract.

- ERC20 Events: The BalancerPoolToken contract emits the Transfer and Approval events, which are required by the ERC20 standard. These events are emitted in the emitTransfer and emitApproval methods, which can only be called by the Vault contract on the pool contract.

- ERC20Permit: The BalancerPoolToken contract also implements the ERC20 Permit extension, which allows approvals to be made via signatures. This is done in the permit method, which again delegates the approval to the Vault contract.

- IRateProvider: Each pool can serve as a rate provider, with a natural "BPT Rate" = pool value (= invariant) / totalSupply. However, great care must be taken when using pool tokens this way, as rates may be unpredictable or manipulable. In general, since the rate calculation does not include a "rounding hint" (as does the base invariant calculation), if there is any significant or non-linear error in the invariant, the computed value of the invariant might go down when it should go up. Weighted Pool invariants have this property, so calling `getRate` on a Weighted Pool is unsafe, and reverts unconditionally.

- EIP712, Nonces: These have to do with supporting permit2 and signatures, so that explicit token approvals can be avoided in many cases.

- ERC165: This is not used in core pool types, but allows interface detection. See the [Ethereum docs](https://eips.ethereum.org/EIPS/eip-165).

- VaultGuard: This simply stores a reference to the Vault and defines the `onlyVault` modifier for functions that can only be called by the Vault.

By doing this, the BalancerPoolToken contract ensures that Balancer Pool Tokens (BPTs) are fully ERC20 compliant, while also allowing the Vault contract to have full control over BPT accounting. This design ensures atomic updates to critical pool state and supports composability, which is crucial for integration with other DeFi protocols.

## Composability

As BPTs adhere to the ERC20 standard, they can seamlessly integrate as pool tokens in other pools. For instance, the BPT of a boosted pool comprising DAI, USDC, and USDT can be paired with tokens from new projects. This composability ensures the maintenance of deep and capital-efficient stable liquidity, while simultaneously creating efficient swap paths for the project token.

As BPTs adhere to the ERC20 standard, they can seamlessly integrate as pool tokens in other pools. For instance, the BPT of an ERC4626 pool comprising wrapped versions of DAI, USDC, and USDT can be paired with tokens from new projects. This composability ensures the maintenance of deep and capital-efficient stable liquidity, while simultaneously creating efficient swap paths for the project token.
12 changes: 6 additions & 6 deletions docs/concepts/core-concepts/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,13 @@ struct HookFlags {
bool shouldCallAfterRemoveLiquidity;
}
```
This decision is final and cannot be changed for a pool once it is registered, as each pool's hook configuration is stored in the Vault and set at pool registration time. During pool registration, the Vault calls into the Hooks contract and [retrieves](https://github.com/balancer/balancer-v3-monorepo/blob/49553c0546121f7725e0b024b240d6e722f02538/pkg/vault/contracts/VaultExtension.sol#L198) the `HookFlags`.
This decision is final and cannot be changed for a pool once it is registered, as each pool's hook configuration is stored in the Vault and set at pool registration time. During pool registration, the Vault calls into the Hooks contract and [retrieves](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/VaultExtension.sol#L569-L573) the `HookFlags`.

:::info Hooks & reentrancy
It is possible to reenter the Vault as part of a hook execution, as only the internal functions for each operation are reentrancy protected (e.g., `_swap`, `_addLiquidity` & `_removeLiquidity`).
:::info Hooks and reentrancy
It is possible to reenter the Vault as part of a hook execution, as only the internal functions for each operation are reentrancy protected (e.g., `_swap`, `_addLiquidity` and `_removeLiquidity`).
:::

## How Pools & Hooks Are Connected
## How Pools and Hooks Are Connected

When a new pool is registered a hook contract address can be passed to "link" the pool and the hook (use the zero address if there is no hook). This configuration is immutable and cannot change after the pool is registered.

Expand All @@ -82,7 +82,7 @@ function registerPool(
```

::: info
If you want your Hooks contract to be used, you must implement `onRegister` as the Vault calls it during the [pool registration](https://github.com/balancer/balancer-v3-monorepo/blob/49553c0546121f7725e0b024b240d6e722f02538/pkg/vault/contracts/VaultExtension.sol#L184). The intention of `onRegister` is for the developer to verify that the pool should be allowed to use the hooks contract.
If you want your Hooks contract to be used, you must implement `onRegister` as the Vault calls it during the [pool registration](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/VaultExtension.sol#L144-L166). The intention of `onRegister` is for the developer to verify that the pool should be allowed to use the hooks contract.
:::

Afterwards the pool is linked to the hook via the `_hooksContracts` mapping, shown below.
Expand All @@ -95,7 +95,7 @@ mapping(address pool => IHooks hooksContract) internal _hooksContracts;

## Adjusted amounts - using hooks to change `amountCalculated`.

Remember that pool liquidity operations like `swap`, `addLiquidity` and `removeLiquidity` signal to the Vault the entries on the credit & debt tab. These entries can either be calculated as part of custom pool implementations or pools in combination with hooks. Both have the capability to determine the amount of credit & debt the vault adds to the tab.
Remember that pool liquidity operations like `swap`, `addLiquidity` and `removeLiquidity` signal to the Vault the entries on the credit and debt tab. These entries can either be calculated as part of custom pool implementations or pools in combination with hooks. Both have the capability to determine the amount of credits and debts the vault adds to the tab.

The reason hooks also have this capability is to change `amountCalculated` for existing pool types from established factories. This allows for more fine-grained pool tuning capabilities in `after` hooks.
![Vault-Pool-Hooks relation](/images/hook-delta.png)
Expand Down
Loading

0 comments on commit a6f0df0

Please sign in to comment.