diff --git a/README.md b/README.md index 0c366e3b..e9a62d5e 100644 --- a/README.md +++ b/README.md @@ -4,21 +4,27 @@ ๐Ÿ› ๏ธ This repo is a series of guides and internal prototyping tools for creating custom pools that integrate with Balancer v3. It is based off of commit hash 4bc7978d8b8c4ac8e42c5a4adf233663b8390678 from the BalancerV3 monorepo. This repo is to be updated once the monorepo is fully public. -> ๐Ÿ“š๐Ÿ“– PRE-REQs: It is highly recommended to read through the (BalancerV3 docs](https://docs-v3.balancer.fi/) before using this repo. Custom pools are built upon the architecture outlined within these docs. If you cannot find what you are looking for in the docs, and it is not in this README, please refer to the (BalancerV3 monorepo](https://github.com/balancer/balancer-v3-monorepo/tree/main) and/or reachout on the (Balancer Discord](TODO GET LINK). +> ๐Ÿ“š๐Ÿ“– PRE-REQs: It is highly recommended to read through the (BalancerV3 docs](https://docs-v3.balancer.fi/) before using this repo. Custom pools are built upon the architecture outlined within these docs. If you cannot find what you are looking for in the docs, and it is not in this README, please refer to the (BalancerV3 monorepo](https://github.com/balancer/balancer-v3-monorepo/tree/main) and/or reachout on the (Balancer Discord](https://discord.balancer.fi/). ๐Ÿง‘โ€๐Ÿซ This guide walks through example contracts for a custom pool, custom pool factory, test files, and deployment scripts. These files are used to deploy an example BalancerV3 custom pool that can be interacted with using a test, local front-end, on a test network (by default it is a foundry fork of Sepolia). The repo also provides a starting point for developers to create their own custom pools and factories. -> When users clone this repo "off-the-shelf" they simply have to follow the environment setup instructions, run a few commands, and then they will have an example custom pool factory, and custom pools that they can interact with in a local front end. +> When users clone this repo "off-the-shelf" they simply have to follow the environment setup instructions, run a few commands, and then they will have an example custom pool factory, and custom pools that they can interact with in a local front end. Let's outline what this repo provides in more detail: -1. A README to walk a dev through using the different functionalities of the repo. +1. A README to walk a dev through using the different functionalities of the repo. 2. A front-end prototyping tool, example smart contracts and scripts, to help showcase simple integrations with Balancer's core architecture. 3. Use of the same front-end framework with your own custom pools, and walking you through how to do so using the example smart contracts and scripts to start. ## ๐ŸŽฅ Demo -[๐Ÿ‘€ Watch this video](https://www.loom.com/share/31cabf0568a845abadcbdbb0df416b20?sid=9b1176c7-5ee4-4feb-8b6e-c971721440a0) to get a sense of the full stack dev environment where foundry is used to deploy a custom pool contract to a local fork and the nextjs frontend offers the opportunity to visualize the effects of swaps, joins, and exits with the pool. +๐Ÿ‘€ Watch this video to quickly get started with the full stack development environment ๐Ÿ‘‡ + +[![Getting Started Demo](https://github.com/Dev-Rel-as-a-Service/scaffold-balancer-v3/assets/73561520/ad988ac7-67a6-4dd4-8e12-18c2be2773ea)](https://www.loom.com/share/31cabf0568a845abadcbdbb0df416b20?sid=9b1176c7-5ee4-4feb-8b6e-c971721440a0) + +๐Ÿ‘€ Watch this video to get better acquainted with the smart contracts ๐Ÿ‘‡ + +[![Smart Contracts Demo](https://github.com/Dev-Rel-as-a-Service/scaffold-balancer-v3/assets/73561520/ad588a6d-2442-421b-8fbf-4667d29df81f)](https://drive.google.com/file/d/1x5IgDzKE2fQnpZdsfuLvb10c1OUAOH_z/view?usp=drive_link) ## ๐Ÿ‘จ๐Ÿปโ€๐Ÿซ Table of Contents @@ -34,11 +40,13 @@ Let's outline what this repo provides in more detail: In general, all smart contracts sections of this repo will already have `Example` smart contracts. These smart contract examples will be explained within this README. --- + ## ๐Ÿšจ๐Ÿšจ Checkpoint 0: ๐Ÿ“ฆ Environment ๐Ÿ“š This section walks you through the set up of the repo environment so that you have a local front end with a foundry test fork off of Sepolia. The test fork will have deployed contracts to showcase how you can interact with custom pools in a test environment using the local pool explorer tab. --- + ### ๐Ÿ”ง 0.1 Requirements Before you begin, you need to install the following tools: @@ -49,6 +57,7 @@ Before you begin, you need to install the following tools: - [Foundry](https://book.getfoundry.sh/getting-started/installation) --- + ### ๐Ÿƒ๐Ÿปโ€โ™€๏ธ 0.2 Quickstart Next, we will run the following bash commands in your terminal to clone the repo and set up the repo accordingly. @@ -100,11 +109,12 @@ yarn start You now should have a local, testnet fork with newly deployed smart contracts and a local front end communicating with said smart contracts. You can start interacting with the newly deployed pools and other smart contracts, but first let's talk about wallets and this tool. --- -### ๐Ÿฆ 0.3 Wallet Connection Options, and Key Gotchas with Scaffold ETH 2 + +### ๐Ÿ—๏ธ 0.3 Scaffold ETH 2 Configuration Guide SE-2 offers a variety of full stack configuration options for connecting an account, choosing a network, and deploying contracts. -##### 0.3.1 Burner Wallet +
0.3.1 Burner Wallet If you do not have a wallet already connected to your web browser and thus your local host, then you will automatically use a burner wallet. First, what is a burner wallet? @@ -116,21 +126,19 @@ To force the use of burner wallet, disable your browsers wallet extensions and r ![Debug Tab Mint](https://github.com/Dev-Rel-as-a-Service/scaffold-balancer-v3/assets/73561520/fbb53772-8f6d-454d-a153-0e7a2925ef9f) -##### 0.3.2 Browser Extension Wallet +
+ +
0.3.2 Browser Extension Wallet To use your preferred browser extension wallet, ensure that the account you are using matches the PK you previously provided in the `foundry/.env` file. As a convenience, the foundry deploy scripts max approve the vault contract to spend tokens. โš ๏ธ You may need to add a local development network with rpc url `http://127.0.0.1:8545/` and chain id `31337`. Also, you may need to reset the nonce data for your wallet exension if it gets out of sync. - - -With the wallet configurations understood and setup, we will touch on some SE-2 details before fully exploring the newly deployed local-test pool smart contracts. - -#### 0.3.2 Deployment +
-> SE-2 is setup to hot reload the frontend with contracts that are directly deployed via the `DeployFactoryAndPool.s.sol` script. This means our frontend captures the pool factory and mock token contracts, but not the pool contract because it is deployed by calling a method on the factory. +
0.3.3 Deployment Details -This command runs `DeployFactoryAndPool.s.sol` which deploys a pool factory, deploys mock tokens, deploys a pool, and initializes the pool. The factory contract and mock tokens will show on the "Debug" page. The pool contract address will print in the terminal, but can also be selected from the dropdown on the "Pools" page. All deployment configuration options are specified in `HelperConfig.s.sol` +This command runs `DeployFactoryAndPool.s.sol` which deploys a pool factory, deploys mock tokens, deploys a pool, and initializes the pool. The factory contract and mock tokens will show on the "Debug" page. The pool contract address will print in the terminal, but can also be selected from the dropdown on the "Pools" page. All deployment configuration options are specified in `HelperConfig.s.sol`. You may need to refresh your front end in your local host after running the below command. ```bash yarn deploy:all @@ -142,7 +150,11 @@ This command runs `DeployPool.s.sol` using the last pool factory you deployed. Y yarn deploy:pool ``` -#### 0.3.3 Changing The Frontend Network Connection +> ๐Ÿš— Under the hood of SE-2: SE-2 is setup to hot reload the frontend with contracts that are directly deployed via the `DeployFactoryAndPool.s.sol` script. This means our frontend captures the pool factory and mock token contracts, but not the pool contract because it is deployed by calling a method on the factory. + +
+ +
0.3.4 Changing The Frontend Network Connection The network the frontend points at is set via `targetNetworks` in the `scaffold.config.ts` file using `chains` from viem. By default, the frontend runs on a local node at `http://127.0.0.1:8545` @@ -151,7 +163,9 @@ const scaffoldConfig = { targetNetworks: [chains.foundry], ``` -#### 0.3.4 Changing The Forked Network +
+ +
0.3.5 Changing The Forked Network You can modify the `"fork"` alias in the `packages/foundry/package.json` file, but do not change the chain id. By default, the `yarn fork` command uses sepolia, but any of the network aliases from the `[rpc_endpoints]` of `foundry.toml` can be used @@ -159,69 +173,93 @@ You can modify the `"fork"` alias in the `packages/foundry/package.json` file, b "fork": "anvil --fork-url ${0:-sepolia} --chain-id 31337 --config-out localhost.json", ``` -> ** ๐Ÿ˜ฎโ€๐Ÿ’จPHEW, with the quick start deployments done, we can get into the fun stuff and show what can be done with this tool!** +
--- + ## ๐Ÿšจ๐Ÿšจ Checkpoint 1: ๐ŸŠ๐Ÿปโ€โ™€๏ธ Showcase of the Pool Explorer with SE-2 Tech Stack You now should have a local front end started and test contracts deployed on a foundry test fork of the Sepolia network. This section simply highlights some of the actions you can take with the local front end. --- + ### ๐Ÿ” 1.1 Select Your Pool On the "Pools" page, click the dropdown to select the custom pool you just deployed to your local anvil node. - + +
๐Ÿ‘€ See Pool Selection GIF + https://github.com/Dev-Rel-as-a-Service/scaffold-balancer-v3/assets/73561520/cc358227-3bf6-4b02-8dc5-36577c0cbdcd +
+ --- + ### ๐Ÿšฐ 1.2 Use Your Pool Connect the account you specified in the `.env` file using your favorite wallet extension and start splashing around in your pool with swaps, joins, and exits! +
๐Ÿ‘€ Swap Preview + ![Swap](https://github.com/Dev-Rel-as-a-Service/scaffold-balancer-v3/assets/73561520/64629016-5bb5-40ce-a3bd-2e421000b33d) +
+ +
๐Ÿ‘€ Join Preview + ![Join](https://github.com/Dev-Rel-as-a-Service/scaffold-balancer-v3/assets/73561520/cf8dc531-d98b-49fa-9195-ec86d7018e09) +
+ +
๐Ÿ‘€ Exit Preview + ![Exit](https://github.com/Dev-Rel-as-a-Service/scaffold-balancer-v3/assets/73561520/3604dbfb-fea2-414f-8e62-c01dc12cc691) +
+ --- + ### ๐Ÿ›๐Ÿ™…๐Ÿปโ€โ™‚๏ธ 1.3 Troubleshoot with the Debug Tab -Using the SE-2 toolkit, developers can troubleshoot with their smart contracts using the "Debug Tab" where they can see getter and setter functions in a local front end UI. As you saw earlier, we use this handy setup to mint `mockERC20` tokens to any connected wallet to our local host (it could be a foundry wallet, a burner wallet, your `.env` wallet, etc.). +Using the SE-2 toolkit, developers can troubleshoot with their smart contracts using the "Debug Tab" where they can see getter and setter functions in a local front end UI. As you saw earlier, we use this handy setup to mint `mockERC20` tokens to any connected wallet to our local host (it could be a foundry wallet, a burner wallet, your `.env` wallet, etc.). + +
๐Ÿ‘€ Debug Preview ![Debug Tab Mint](https://github.com/Dev-Rel-as-a-Service/scaffold-balancer-v3/assets/73561520/fbb53772-8f6d-454d-a153-0e7a2925ef9f) -At this point, you have now seen the capabilities of this repo and how it helps a developer (or team) onboard in building custom pools in BalancerV3. +
+
+ +At this point, you have now seen the capabilities of this repo and how it helps a developer (or team) onboard in building custom pools in BalancerV3. ๐ŸŽ Let's look under the hood, where we will start with understanding the example custom pool used within this repo., the `ConstantPricePool`. --- + ## ๐Ÿšจ๐Ÿšจ Checkpoint 2: ๐ŸŒŠ Create A Custom Pool Ultimately, this repo can be used to create custom pool factories, custom pools from said factory, and register and initialize them so the pools can be interacted with using this repo's front end, all in a local environment. Before jumping into all of that, it is key that developers understand the general make-up of a custom pool. -Therefore, this checkpoint focuses on writing the smart contract for a custom pool (without a factory). We will walk through the `ConstantPricePoolExample.sol` found within `packages/foundry/contracts/ConstantPricePoolExample.sol`. +Therefore, this checkpoint focuses on writing the smart contract for a custom pool (without a factory). We will walk through the `ConstantPricePool.sol` found within `packages/foundry/contracts/ConstantPricePool.sol`. --- + ### ๐Ÿ‘˜ 2.1 Write a Custom Pool Contract As a refresher, make sure to check out the [docs on creating custom pools as well](https://docs-v3.balancer.fi/concepts/guides/create-custom-amm-with-novel-invariant.html#build-your-custom-amm). All custom pool contracts must inherit from `IBasePool` and `BalancerPoolToken` and implement the three required functions: `onSwap`, `computeInvariant`, and `computeBalance`. -Let's walk through each function in `ConstantPricePoolExample.sol` +Let's walk through each function in `ConstantPricePool.sol` --- -#### 2.1.1 `onSwap()` Implementation - -Looking at monorepo, one sees that `onSwap()` is ultimately called within a `swap()` call in the [`Vault.sol`](https://github.com/balancer/balancer-v3-monorepo/blob/9bc5618d7717dfbafd3cfbf025e7d3317ad7cacb/pkg/vault/contracts/Vault.sol#L327). -Essentially, the `onSwap()` call carries the custom pool logic that the vault queries to understand how much of the requested token the swap should return. +#### 2.1.1 `onSwap()` Implementation -This step can vary between custom pool variations. To paint a contrast, a simple implementation can be seen within this `ConstantPricePool` example, where the amount swapped in is simply the amount swapped out (see toggle below). +Listed here for easy reference, the function discussed can also be found [here](https://github.com/Dev-Rel-as-a-Service/scaffold-balancer-v3/blob/1bb9e662e1f94bba7f30b7ad2a50bcee17ad9232/packages/foundry/contracts/ConstantPricePool.sol#L25C1-L26C1).
๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿซ Code for `onSwap` functions -Inside `ConstantPricePoolExample.sol` +Inside `ConstantPricePool.sol` ``` /** @@ -239,11 +277,16 @@ Inside `ConstantPricePoolExample.sol`
+Looking at monorepo, one sees that `onSwap()` is ultimately called within a `swap()` call in the [`Vault.sol`](https://github.com/balancer/balancer-v3-monorepo/blob/9bc5618d7717dfbafd3cfbf025e7d3317ad7cacb/pkg/vault/contracts/Vault.sol#L327). + +Essentially, the `onSwap()` call carries the custom pool logic that the vault queries to understand how much of the requested token the swap should return. + +This step can vary between custom pool variations. To paint a contrast, a simple implementation can be seen within this `ConstantPricePool` example, where the amount swapped in is simply the amount swapped out (see toggle below). + Whereas you can begin to see the endless possibilities that exist when you take a look at the [WeightedPool implementation](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-weighted/contracts/WeightedPool.sol#L100-L126). There you can see that the return value is dependent on the `SwapKind` and ultimately uses the `WeightedMath` functions to respect the invariant and other details for WeightedPools. - #### ๐Ÿฅ… ** `onSwap()` Goals / Checks** - [ ] โ“ Can you describe how `onSwap()` works within the Balancer V3 monorepo architecture and thus how custom pools must accomodate said architecture? @@ -252,16 +295,10 @@ There you can see that the return value is dependent on the `SwapKind` and ultim #### 2.1.2 `computeInvariant()` Implementation -This function is called throughout a number of sequences within the BalancerV3 architecture. Let's walk through one at a high-level. - -By simply searching within the v3 monorepo, we can see that it is used within the [`Vault.sol`](https://github.com/balancer/balancer-v3-monorepo/blob/fd288fef56cbb20284d34c9b2b1d4227285922dc/pkg/vault/contracts/Vault.sol#L864) and other contracts relying on `BasePoolMath.sol`. Functions like [`computeRemoveLiquiditySingleTokenExactOut()`](https://github.com/balancer/balancer-v3-monorepo/blob/fd288fef56cbb20284d34c9b2b1d4227285922dc/pkg/solidity-utils/contracts/math/BasePoolMath.sol#L261) are inside `BasePoolMath.sol`. They call `computeInvariant()` to calculate the bptAmounts to be used within a respective transaction as well as the fees involved. Other primary contracts that rely on this function are: `VaultExtension.sol`, where it is used to calculate the bpt amounts involved when initializing a new pool. - -Essentially, the invariant is used at different points of the transaction to ensure its mathematical logic is upheld within the respective pool. These of course can vary vastly based on the design that is taken. The most well-known invariant is the constant product invariant, as discussed within the BalancerV3 docs [here](https://docs-v3.balancer.fi/build-a-custom-amm/build-an-amm/create-custom-amm-with-novel-invariant.html#build-your-custom-amm:~:text=%23-,Compute%20Invariant,-Custom%20AMMs%20built) briefly. - -For this example, the invariant is simply a constant sum invariant. Thus we have the below code blob: +Listed here for easy reference, the function discussed can also be found [here](https://github.com/Dev-Rel-as-a-Service/scaffold-balancer-v3/blob/1bb9e662e1f94bba7f30b7ad2a50bcee17ad9232/packages/foundry/contracts/ConstantPricePool.sol#L39).
๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿซ Code for `computeInvariant()` function -Inside `ConstantPricePoolExample.sol` +Inside `ConstantPricePool.sol` ``` /** @@ -280,27 +317,26 @@ Inside `ConstantPricePoolExample.sol`
-#### ๐Ÿฅ… ** `computeInvariant()` Goals / Checks** +This function is called throughout a number of sequences within the BalancerV3 architecture. Let's walk through one at a high-level. -- [ ] โ“ Can you describe how `computeInvariant()` works within the Balancer V3 monorepo architecture with the `Vault.sol`, `BasePoolMath.sol`, `VaultExtension.sol` and thus how custom pools must accomodate said architecture? +By simply searching within the v3 monorepo, we can see that it is used within the [`Vault.sol`](https://github.com/balancer/balancer-v3-monorepo/blob/fd288fef56cbb20284d34c9b2b1d4227285922dc/pkg/vault/contracts/Vault.sol#L864) and other contracts relying on `BasePoolMath.sol`. Functions like [`computeRemoveLiquiditySingleTokenExactOut()`](https://github.com/balancer/balancer-v3-monorepo/blob/fd288fef56cbb20284d34c9b2b1d4227285922dc/pkg/solidity-utils/contracts/math/BasePoolMath.sol#L261) are inside `BasePoolMath.sol`. They call `computeInvariant()` to calculate the bptAmounts to be used within a respective transaction as well as the fees involved. Other primary contracts that rely on this function are: `VaultExtension.sol`, where it is used to calculate the bpt amounts involved when initializing a new pool. ---- +Essentially, the invariant is used at different points of the transaction to ensure its mathematical logic is upheld within the respective pool. These of course can vary vastly based on the design that is taken. The most well-known invariant is the constant product invariant, as discussed within the BalancerV3 docs [here](https://docs-v3.balancer.fi/build-a-custom-amm/build-an-amm/create-custom-amm-with-novel-invariant.html#build-your-custom-amm:~:text=%23-,Compute%20Invariant,-Custom%20AMMs%20built) briefly. -#### 2.1.3 `computeBalance()` Implementation +For this example, the invariant is simply a constant sum invariant. -- Looking at monorepo, one sees that `computeBalance()` is ultimately called to return the new balance of a token after an operation, given the invariant growth ratio and all other balances. -- In this case, it is constant sum invariant that we simply need to create. +#### ๐Ÿฅ… ** `computeInvariant()` Goals / Checks** -The docs outline how `computeBalance()` is used to return the needed balance of a pool token for a specific invariant change. So basically it is used to calculate an amount of a pool token when the resuiltant invariant is known. This can be seen in function calls within the v3 monorepo where expected balances are used to calculate the invariant ratio for luqidity operations such as []`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). +- [ ] โ“ Can you describe how `computeInvariant()` works within the Balancer V3 monorepo architecture with the `Vault.sol`, `BasePoolMath.sol`, `VaultExtension.sol` and thus how custom pools must accomodate said architecture? -To elaborate on things a bit further, within `Vault.sol`, two main internal functions use `computeBalance()`. These are: `_addLiquidity()` which calls upon , and `_removeLiquidity()` which calls upon `computeAddLiquiditySingleTokenExactOut()`, and `computeRemoveLiquiditySingleTokenExactIn()`, respectively. Within both of these sequences, the `computeBalance()` return value is used in calculating the eventual amount to debit (tokens marked as debt for the user as seen in `_takeDebt()` in the `VaultCommon.sol`) or credit (tokens marked as credit for the user as seen in `supplyCredit()` in the `VaultCommon.sol`) for the respective function call. +--- -For the simple constant price pool example, we have a standard calculation of newBalance using the invariantRatio and current invariant. +#### 2.1.3 `computeBalance()` Implementation ---- +Listed here for easy reference, the function discussed can also be found [here](https://github.com/Dev-Rel-as-a-Service/scaffold-balancer-v3/blob/1bb9e662e1f94bba7f30b7ad2a50bcee17ad9232/packages/foundry/contracts/ConstantPricePool.sol#L55).
๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿซ Code for `computeBalance()` function -Inside `ConstantPricePoolExample.sol` +Inside `ConstantPricePool.sol` ``` /** @@ -329,6 +365,15 @@ Inside `ConstantPricePoolExample.sol`
+Looking at monorepo, one sees that `computeBalance()` is ultimately called to return the new balance of a token after an operation, given the invariant growth ratio and all other balances. +In this case, it is constant sum invariant that we simply need to create. + +The docs outline how `computeBalance()` is used to return the needed balance of a pool token for a specific invariant change. So basically it is used to calculate an amount of a pool token when the resultant invariant is known. This can be seen in function calls within the v3 monorepo where expected balances are used to calculate the invariant ratio for liquidity operations such as that seen in functions [`_addLiquidity()`](https://github.com/balancer/balancer-v3-monorepo/blob/c009aa9217070e88ed3a39bda97d83c14342f39b/pkg/vault/contracts/Vault.sol#L630C1-L638C19) and [`_removeLiquidity()_`](https://github.com/balancer/balancer-v3-monorepo/blob/c009aa9217070e88ed3a39bda97d83c14342f39b/pkg/vault/contracts/Vault.sol#L850C13-L858C19). + +To elaborate on things a bit further, within `Vault.sol`, two main internal functions use `computeBalance()`. These are: `_addLiquidity()` which calls upon , and `_removeLiquidity()` which calls upon `computeAddLiquiditySingleTokenExactOut()`, and `computeRemoveLiquiditySingleTokenExactIn()`, respectively. Within both of these sequences, the `computeBalance()` return value is used in calculating the eventual amount to debit (tokens marked as debt for the user as seen in `_takeDebt()` in the `VaultCommon.sol`) or credit (tokens marked as credit for the user as seen in `supplyCredit()` in the `VaultCommon.sol`) for the respective function call. + +For the simple constant price pool example, we have a standard calculation of newBalance using the invariantRatio and current invariant. + #### ๐Ÿฅ… ** `computeBalance()` Goals / Checks** - [ ] โ“ Can you describe how `computeBalance()` works within the Balancer V3 monorepo architecture and thus how custom pools must accomodate said architecture? @@ -336,11 +381,12 @@ Inside `ConstantPricePoolExample.sol` > ๐Ÿ’ก Nice! We've now walked through a basic custom pool construction, let's get into how the custom pool factory contracts work. --- + ## ๐Ÿšจ๐Ÿšจ Checkpoint 3: ๐Ÿ”ง Create a custom pool factory && Interact with Resultant Custom Pools Now that you have created a custom pool, it is time to deploy the associated custom pool factory. As outlined within the [docs](https://docs-v3.balancer.fi/build-a-custom-amm/build-an-amm/create-custom-amm-with-novel-invariant.html#deploy-your-pool:~:text=Creating%20pools%20via,%23), Balancer's off-chain infrastructure uses the `factory` address to ID the `type` of pool. For this repo, we've created a custom pool factory example and associated script to deploy it, and create a new pool using said factory. -In addition to being useful for integrating into Balancer, once the custom pool factory is deployed, anyone can come along and deploy more of that specific custom pool type, with varying pool parameters. +In addition to being useful for integrating into Balancer, once the custom pool factory is deployed, anyone can come along and deploy more of that specific custom pool type, with varying pool parameters. The example factory continues off of the previous section and uses `ConstantPricePool.sol` as the Custom Pool type. Within the script the first pool from said factory is deployed, registered, and initialized, so you can interact with it right away. Another script is created so you can create more pools and enter in the param details via your favorite code editor too. @@ -350,17 +396,15 @@ This section will walk you through: - Running the script to deploy more pools from said custom pool factory. --- + ## ๐Ÿญ 3.1: Creating the Custom Pool Factory -We will focus on creating the `CustomPoolFactoryExample.sol` contract. It is used to deploy the `ConstantPricePool.sol` example custom pool we walked through earlier. It inherits the `BasePoolFactory.sol` from BalancerV3's monorepo. +We will focus on creating the `CustomPoolFactoryExample.sol` contract. Let's focus on the constructor first. -For a factory associated to the `ConstantPricePool.sol` we do not need any extra implementation within the constructor. Thus, all we need to do is write the constructor with the appropriate params as described below for `BasePoolFactory.sol`. +Listed here for easy reference, the constructor discussed can also be found [here](https://github.com/Dev-Rel-as-a-Service/scaffold-balancer-v3/blob/1bb9e662e1f94bba7f30b7ad2a50bcee17ad9232/packages/foundry/contracts/CustomPoolFactoryExample.sol#L22). -1. `IVault vault` - The BalancerV3 vault on the respective network. -2. `uint256 pauseWindowDuration` - The pause window that will be used for all pools created from this factory. It is the timeframe that a newly created pool can be paused before its buffer endtime. Once a pool is paused, it will remain paused til the end of the pause window, and subsequently will wait til the vault's buffer period ends. When the buffer period expires, it will unpause automatically, and remain permissionless forever after. -3. `bytes memory creationCode` - The creation code that is used within the `_create()` internal function call when creating new pools. This is associated to the respective custom pool that you are looking to create. - -Thus the constructor code will be: +
๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿซ Code for `constructor()` +Inside `CustomPoolFactoryExample.sol` ``` constructor( @@ -371,16 +415,30 @@ constructor( } ``` +
+ +It is used to deploy the `ConstantPricePool.sol` example custom pool we walked through earlier. It inherits the `BasePoolFactory.sol` from BalancerV3's monorepo. + +For a factory associated to the `ConstantPricePool.sol` we do not need any extra implementation within the constructor. Thus, all we need to do is write the constructor with the appropriate params as described below for `BasePoolFactory.sol`. + +1. `IVault vault` - The BalancerV3 vault on the respective network. +2. `uint256 pauseWindowDuration` - The pause window that will be used for all pools created from this factory. It is the timeframe that a newly created pool can be paused before its buffer endtime. Once a pool is paused, it will remain paused til the end of the pause window, and subsequently will wait til the vault's buffer period ends. When the buffer period expires, it will unpause automatically, and remain permissionless forever after. +3. `bytes memory creationCode` - The creation code that is used within the `_create()` internal function call when creating new pools. This is associated to the respective custom pool that you are looking to create. + > NOTE: more implementation can be input for the constructor for your own custom pool of course, but for this example we are keeping things simple. Even pools such as [WeightedPools](https://github.com/balancer/balancer-v3-monorepo/blob/9bc5618d7717dfbafd3cfbf025e7d3317ad7cacb/pkg/pool-weighted/contracts/WeightedPool8020Factory.sol#L21) and [StablePools](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-stable/contracts/StablePoolFactory.sol) can have simple constructor setups as seen in the v3 monorepo. Moving on to the next part, the `create()` function is used to create new pools from the custom pool factory, adhering to the specific type of pools for said factory. In this case, that's the `ConstantPricePool`. --- + ### ๐Ÿง‘๐Ÿปโ€๐Ÿ’ป 3.1.1 `create()` Function The `create()` function, in this simple example pool factory, simply calls the `_create()` function within the `BasePoolFactory.sol`. The `_create()` function uses `CREATE3`, similar to `CREATE2` to deploy a pool that has a pre-determined address based on its salt, and encoded creation code & args. -Below is the `create()` function used within our example: +Listed here for easy reference, the the `create()` function discussed can also be found [here](https://github.com/Dev-Rel-as-a-Service/scaffold-balancer-v3/blob/1bb9e662e1f94bba7f30b7ad2a50bcee17ad9232/packages/foundry/contracts/CustomPoolFactoryExample.sol#L42). + +
๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿซ Code for `create()` function +Inside `CustomPoolFactoryExample.sol` ``` /** @@ -424,7 +482,12 @@ Below is the `create()` function used within our example: } ``` -Here is the `_create()` internal function for reference: +
+ +Here is the `_create()` internal function for reference (it can also be found [here](https://github.com/balancer/balancer-v3-monorepo/blob/c009aa9217070e88ed3a39bda97d83c14342f39b/pkg/vault/contracts/factories/BasePoolFactory.sol#L86)): + +
๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿซ Code for `_create()` function +Inside `CustomPoolFactoryExample.sol` ``` function _create(bytes memory constructorArgs, bytes32 salt) internal returns (address) { @@ -432,45 +495,51 @@ function _create(bytes memory constructorArgs, bytes32 salt) internal returns (a } ``` +
+ Within the function `create()` we call `_create()` with appropriate params, which will be touched on later within our scripts. For now, we move onto the next aspect of the `create()` call, which is to `registerPool()` with the BalancerV3 vault. --- + #### 3.1.1.1 Calling `registerPool()` New pools need to be registered to the BalancerV3 vault to operate within the BalancerV3 architecture. The details of this function are outlined well within the [`IVaultExtension.sol` natspec](https://github.com/balancer/balancer-v3-monorepo/blob/9bc5618d7717dfbafd3cfbf025e7d3317ad7cacb/pkg/interfaces/contracts/vault/IVaultExtension.sol#L53). For the example factory contract we are working with, we have no pause manager, no hooks, and do not support "custom" liquidity provision or removal. -Finally, the `create()` function ends by calling `_registerPoolWithFactory(newPool)` which registers the new pool with the respective factory. This is done for accounting purposes, amongst other reasons. +Finally, the `create()` function ends by calling `_registerPoolWithFactory(newPool)` (as seen [here](https://github.com/Dev-Rel-as-a-Service/scaffold-balancer-v3/blob/1bb9e662e1f94bba7f30b7ad2a50bcee17ad9232/packages/foundry/contracts/CustomPoolFactoryExample.sol#L72)) which registers the new pool with the respective factory. This is done for accounting purposes, amongst other reasons. > NOTE: like all other contracts and scripts within this repo, one must adjust aspects within this smart contract when creating their own type of custom pool. --- + ## ๐Ÿญ๐Ÿง‘๐Ÿปโ€๐Ÿ’ป 3.2: Deploying the Custom Pool Factory Now that we have created the `CustomPoolFactoryExample.sol` contract, it is time to write the deployment scripts. We've provided example deployment scripts to reference as you create your own, and will walk through key gotcha's when writing your own deployment scripts. As always, test on your own before deploying! -`DeployFactoryAndPool.s.sol` is the file that we will use as a template. +`DeployFactoryAndPool.s.sol` is the file that we will use as a template, it can be found [here](https://github.com/Dev-Rel-as-a-Service/scaffold-balancer-v3/blob/1bb9e662e1f94bba7f30b7ad2a50bcee17ad9232/packages/foundry/script/DeployFactoryAndPool.s.sol). For sake of simplicity, we will outline the core function of the script, and then explain specific gotchas with the script. The main one being the params involved with initialization. --- + ### ๐Ÿ’ช๐Ÿผ 3.2.1 Core Script Function The script, using the `.env` specified deployer wallet, deploys the custom pool factory example we've just discussed. From there, it creates a new pool with it, registers the new pool with the BalancerV3 Vault on the respective network (default anvil fork of Sepolia), and initializes it. It does all of this so it is ready to use with the ScaffoldBalancer front end tool. The variables defined when calling Balancer functions, like `Router.intiialize()` are specific to the constant price custom pool setup, whereas if you are working with a more complicated pool setup you may want to adjust these params as necessary. --- + ### ๐Ÿ’ก 3.2.2 Key Gotchas -- Specific to this repo, the script `DeployFactoryAndPool.s.sol` inherits `ScaffoldETHDeploy.s.sol`, `DeployPool.s.sol`, `HelperConfig`, and `HelperFunctions`. +- Specific to this repo, the script `DeployFactoryAndPool.s.sol` inherits [`ScaffoldETHDeploy.s.sol`](https://github.com/Dev-Rel-as-a-Service/scaffold-balancer-v3/blob/1bb9e662e1f94bba7f30b7ad2a50bcee17ad9232/packages/foundry/script/ScaffoldETHDeploy.s.sol), /1bb9e662e1f94bba7f30b7ad2a50bcee17ad9232/packages/foundry/script/DeployPool.s.sol, [`HelperConfig`](https://github.com/Dev-Rel-as-a-Service/scaffold-balancer-v3/blob/1bb9e662e1f94bba7f30b7ad2a50bcee17ad9232/packages/foundry/utils/HelperConfig.sol), and [`HelperFunctions`](https://github.com/Dev-Rel-as-a-Service/scaffold-balancer-v3/blob/1bb9e662e1f94bba7f30b7ad2a50bcee17ad9232/packages/foundry/utils/HelperFunctions.sol). - If using this and the associated deployment scripts to help troubleshoot your own custom pool type, then it is advised to use the `HelperConfig.sol` located at: `packages/foundry/utils/HelperConfig.sol`, to outline the appropriate details of your custom pool to use the already written example scripts within this repo. - Balancer integration with `initialize()` has a couple of arrays that must match in terms of token ERC20 we are using, and the amounts of said token. -> It is key to understand that the script is calling `Router.initialize()` ultimately, and so understanding this function call and its params is crucial. See the BalancerV3 monorepo for more info. We touch on some of these params below, but the nat spec is also a great resource. +> It is key to understand that the script is calling [`Router.initialize()`](https://github.com/balancer/balancer-v3-monorepo/blob/c009aa9217070e88ed3a39bda97d83c14342f39b/pkg/interfaces/contracts/vault/IRouter.sol#L39C5-L56C55) ultimately, and so understanding this function call and its params is crucial. See the BalancerV3 monorepo for more info. We touch on some of these params below, but the nat spec is also a great resource. - Some variables have comments on them to assist, such as the custom pool factory - This script uses MockToken contracts to instantly mint 1000 of each test token to deployer wallet. -- The TokenConfig struct is defined within `VaultTypes.sol` in the v3 monorepo. It has a few gotchas: +- The TokenConfig struct is defined within [`VaultTypes.sol`](https://github.com/balancer/balancer-v3-monorepo/blob/c009aa9217070e88ed3a39bda97d83c14342f39b/pkg/interfaces/contracts/vault/VaultTypes.sol#L128) in the v3 monorepo. It has a few gotchas: - `TokenConfig.tokenType` is an enum: `STANDARD` OR `WITH_RATE` - `TokenConfig.token` is the token address - `TokenConfig.rateProvider` is the rate provider for the respective token. Rate Providers are not needed for `STANDARD` tokenTypes. These also are not yield bearing. @@ -486,6 +555,7 @@ The script, using the `.env` specified deployer wallet, deploys the custom pool Cool, now we have these gotcha's understood with the script. We can move on to simulating and deploying! --- + ### ๐Ÿค– 3.2.3: Interacting with the Custom Pool Factory via Scripts As seen in the beginning of this [README](#032-deployment), this repo comes with bash commands to deploy the discussed smart contracts on a local anvil fork of the Sepolia test network. For quick reference, here are the commands again: @@ -493,41 +563,49 @@ As seen in the beginning of this [README](#032-deployment), this repo comes with > If you would like to change the network the local node is forking, [review this section](#033-changing-the-frontend-network-connection) To simulate deployment transactions on your local fork + ```bash forge script script/DeployFactoryAndPool.s.sol --rpc-url localhost ``` To send the deployment transaction to your local fork: + ```bash yarn deploy:all ``` To simulate deployment transactions on sepolia testnet: + ```bash forge script script/DeployFactoryAndPool.s.sol --rpc-url sepolia ``` To send the deployment transactions to sepolia testnet: + ```bash yarn deploy:all โ€”network sepolia ``` -*To simulate or deploy to a different network, swap out `sepolia` for any of the `[rpc_endpoints]` aliases listed in `foundry.toml` + +\*To simulate or deploy to a different network, swap out `sepolia` for any of the `[rpc_endpoints]` aliases listed in `foundry.toml` > Of course, as stated numerous times in this repo, deployments and use of real in production smart contracts are the developer's responsibility and not the creators of this repo. Developers must use their own due diligence in creating extra tests, getting external audits, bug bounties, working with teams, etc. --- + ## ๐ŸŒŠ 3.3: Deploying Only A Pool -Now that the pool factory has been deployed to the local fork, we have the option to deploy just a new pool by calling the `create()` function on the previously deployed pool. Notice that the `DeployPool.s.sol` script also registers and initializes the pool. +Now that the pool factory has been deployed to the local fork, we have the option to deploy just a new pool by calling the `create()` function on the previously deployed pool. Notice that the [`DeployPool.s.sol`](https://github.com/Dev-Rel-as-a-Service/scaffold-balancer-v3/blob/1bb9e662e1f94bba7f30b7ad2a50bcee17ad9232/packages/foundry/script/DeployPool.s.sol) script also registers and initializes the pool. > NOTE: that the pool name will have to be different than that of the initial pool made within the `DeployFactoryAndPool.s.sol` commands. To simulate the pool deployment on your local fork: + ```bash forge script script/DeployPool.s.sol --rpc-url localhost ``` To send the pool deployment transaction to your local fork: + ```bash yarn deploy:pool ``` @@ -537,16 +615,38 @@ At this point, the factory has been deployed, and you have deployed at least one > You can now interact with your custom pool just as the starter video showcased! Go out and have a good time. ๐Ÿ˜‰ --- + ## ๐Ÿšจ๐Ÿšจ Checkpoint 4: ๐Ÿงช Writing Typical Unit and Fuzz Tests for Custom Pool Example At this point we've gone through how to make a simple custom pool and custom pool factory, and to simulate and/or deploy them on a testnet. Testing is of course needed, amongst many other security measures such as audits, for a custom pool implementation. -We will now walk through the testing contract, provided as foundry test files. These testing files can be used as a testing template, similar to how the smart contracts and scripts so far could be used as references or templates for your own custom pool implementation. +We will now walk through the testing contracts, provided as foundry test files. They are written for the Constant Price Custom Pool we use throughout this repo as an example. + +> ๐Ÿง  These test files roughly mirror the typical testing cases that are found in the BalancerV3 monorepo for weighted pool factory tests. As a reference tool, it only makes sense to have tests that, at the very least, roughly mirror how weighted pool factories are tested within BalancerV3 monorepo. + +> If you are unfamiliar with writing tests with foundry please check out their docs [here](https://book.getfoundry.sh/). Within the test files with this repo, we use fuzz testing, that the BalancerV3 monorepo also uses. Foundry docs on fuzz testing can be found [here](https://book.getfoundry.sh/forge/fuzz-testing). + +To start, make sure you make your way to the proper path: `packages/foundry`. Run the following command to run the tests with this repo "off-the-shelf." + +``` +forge test +``` + +You should see the following test results in your terminal window. + +![alt text](image.png) + +These testing files can be used as a testing template, similar to how the smart contracts and scripts so far could be used as references or templates for your own custom pool implementation. + +The tests that you see running are found in the following subdirectory / path: `packages/foundry/test`. There you will find test files, and template test files. The template files are simply copies of the test files but with comments outlining the details of each test, and marking variables to change out if you are using them as a starting template for your own tests. + +> ๐Ÿšจ๐Ÿšจ These files are not production ready, and it is the developer's responsibility to carry out proper testing and auditing for their pool. --- -### ๐ŸŽจ 4.1 `CustomPoolTemplate.t.sol` -#### 4.1.1 Inherited Context for `CustomPoolTemplate.t.sol` (`BaseVaultTest.sol` & `BaseTest.sol`) +### ๐ŸŽจ 4.1 [`CustomPoolTemplate.t.sol`](https://github.com/Dev-Rel-as-a-Service/scaffold-balancer-v3/blob/1bb9e662e1f94bba7f30b7ad2a50bcee17ad9232/packages/foundry/test/CustomPoolTemplate.t.sol) + +#### 4.1.1 Inherited Context for `CustomPoolTemplate.t.sol` ([`BaseVaultTest.sol`](https://github.com/balancer/balancer-v3-monorepo/blob/c009aa9217070e88ed3a39bda97d83c14342f39b/pkg/vault/test/foundry/utils/BaseVaultTest.sol#L4) & [`BaseTest.sol`](https://github.com/balancer/balancer-v3-monorepo/blob/c009aa9217070e88ed3a39bda97d83c14342f39b/pkg/solidity-utils/test/foundry/utils/BaseTest.sol#L4)) The v3 monorepo has pool tests inheriting a base setup implemented within `BaseVaultTest.sol` & `BaseTest.sol`. @@ -563,6 +663,7 @@ The v3 monorepo has pool tests inheriting a base setup implemented within `BaseV - Initializes pool with user `lp` --- + #### 4.1.2 Walking Through the `CustomPoolTemplate.t.sol` Now that we understand the base `BaseVaultTest.setUp()` call made within the `CustomPoolTemplate.t.sol`, we can get into the actual template files. @@ -570,14 +671,16 @@ Now that we understand the base `BaseVaultTest.setUp()` call made within the `Cu Each test has comments added to them to help guide the developer with this starter test template. There are "TODO" comments added on several lines to assist users in creating their own custom pool tests for their own custom pool types they are working on. Of course, one has to update dependencies and other aspects as needed for their purposes. --- -### ๐Ÿ–ผ 4.2 `CustomPoolFactoryTemplate.t.sol` + +### ๐Ÿ–ผ 4.2 [`CustomPoolFactoryTemplate.t.sol`](https://github.com/Dev-Rel-as-a-Service/scaffold-balancer-v3/blob/1bb9e662e1f94bba7f30b7ad2a50bcee17ad9232/packages/foundry/test/CustomPoolFactoryTemplate.t.sol) Unlike the `CustomPoolTemplate.t.sol`, the `CustomPoolFactoryTemplate.t.sol` has a simpler setup where a mock vault, a custom pool factory (specific to the one that is being tested), and two test tokens are deployed. Similar to the `CustomPoolTemplate.t.sol` file, the `CustomPoolFactoryTemplate.t.sol` file has "TODOs" to guide users in creating their own appropriate tests once they have a custom pool factory type of their own that they need to test. --- -## ๐Ÿšจ๐Ÿšจ Checkpoint 5: ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿ”ฌ Creating Your Own Custom Pool with the Template Files + +## ๐Ÿšจ๐Ÿšจ Checkpoint 5: ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿ”ฌ Creating Your Own Custom Pool with the Template Files This is just a guide, so please use your own due diligence with your project before deploying any actual smart contracts of course. This section will simply outlines key areas to look at updating if you are creating your own custom pool. Again, this is not the full extent that you should take to create your own custom pool, it is up to you and your team to carry out everything necessary (including but not limited to: testing, audits, etc.). @@ -591,6 +694,5 @@ This is just a guide, so please use your own due diligence with your project bef The next step is to reach out, if you haven't already, to the Balancer ecosystem via: -1. [Discord](TODO - get the right link) -2. [Balancer Grants](TODO - get the right link) if you've got an idea for a custom pool that you'd like to apply for a grant with. -3. [BD team](TODO - Link to BD team) +1. [Discord](https://discord.balancer.fi/) +2. [Balancer Grants](https://www.notion.so/Balancer-Grants-938f1b979810427f8d903a904315da41) if you've got an idea for a custom pool that you'd like to apply for a grant with. diff --git a/image.png b/image.png new file mode 100644 index 00000000..436dacee Binary files /dev/null and b/image.png differ diff --git a/packages/foundry/contracts/ConstantPricePool.sol b/packages/foundry/contracts/ConstantPricePool.sol index b4632547..fd36c714 100644 --- a/packages/foundry/contracts/ConstantPricePool.sol +++ b/packages/foundry/contracts/ConstantPricePool.sol @@ -67,7 +67,7 @@ contract ConstantPricePool is IBasePool, BalancerPoolToken { /** * @notice Gets the tokens registered to a pool. * @dev Delegated to the Vault; added here as a convenience, mainly for off-chain processes. - * @dev TODO - left blank for now, but for finished example w/ scaffoldBalancer we need to implement this correctly. + * @dev TODO - left blank for now, but for post milestone 1 w/ scaffoldBalancer it'd be good to implement. * @return tokens List of tokens in the pool */ function getPoolTokens() external view returns (IERC20[] memory tokens) {}