From 2ef1de5c0a613008695222d9b6e69e8b8857f3b1 Mon Sep 17 00:00:00 2001 From: dilawari2008 Date: Wed, 2 Oct 2024 11:16:36 +0530 Subject: [PATCH 1/2] BAL Hookathon - TimeBasedHookSubmission --- .../contracts/hooks/TimeBasedDiscountHook.sol | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 packages/foundry/contracts/hooks/TimeBasedDiscountHook.sol diff --git a/packages/foundry/contracts/hooks/TimeBasedDiscountHook.sol b/packages/foundry/contracts/hooks/TimeBasedDiscountHook.sol new file mode 100644 index 00000000..9a561dea --- /dev/null +++ b/packages/foundry/contracts/hooks/TimeBasedDiscountHook.sol @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +pragma solidity ^0.8.24; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import { IBasePoolFactory } from "@balancer-labs/v3-interfaces/contracts/vault/IBasePoolFactory.sol"; +import { IHooks } from "@balancer-labs/v3-interfaces/contracts/vault/IHooks.sol"; +import { IRouterCommon } from "@balancer-labs/v3-interfaces/contracts/vault/IRouterCommon.sol"; +import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol"; +import { + LiquidityManagement, + TokenConfig, + PoolSwapParams, + HookFlags +} from "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol"; + +import { VaultGuard } from "@balancer-labs/v3-vault/contracts/VaultGuard.sol"; +import { BaseHooks } from "@balancer-labs/v3-vault/contracts/BaseHooks.sol"; + +/** + * @title TimeBasedDiscountHook + * @notice Implements a hook that provides a time-based discount on swap fees. + */ +contract TimeBasedDiscountHook is BaseHooks, VaultGuard { + address private immutable _allowedFactory; + address private immutable _trustedRouter; + IERC20 private immutable _veBAL; + + int256 private constant TIMEZONE_OFFSET = 0; + + uint256 public peakHourStart; + uint256 public peakHourEnd; + + uint256 public discountPercentage; + + /** + * @notice Emitted when the TimeBasedDiscountHook is registered. + * @param hooksContract The address of the hooks contract. + * @param factory The address of the factory. + * @param pool The address of the pool. + */ + event TimeBasedDiscountHookRegistered(address indexed hooksContract, address indexed factory, address indexed pool); + + + /** + * @notice Constructs the TimeBasedDiscountHook contract. + * @param vault The address of the vault. + * @param allowedFactory The address of the allowed factory. + * @param veBAL The address of the veBAL token. + * @param trustedRouter The address of the trusted router. + * @param _peakHourStart The start of the peak hour. + * @param _peakHourEnd The end of the peak hour. + * @param _discountPercentage The discount percentage for off-peak hours. + */ + constructor( + IVault vault, + address allowedFactory, + address trustedRouter, + address veBAL, + uint256 _peakHourStart, + uint256 _peakHourEnd, + uint256 _discountPercentage + ) VaultGuard(vault) { + _allowedFactory = allowedFactory; + _trustedRouter = trustedRouter; + _veBAL = IERC20(veBAL); + peakHourStart = _peakHourStart; + peakHourEnd = _peakHourEnd; + discountPercentage = _discountPercentage; + } + + /** + * @notice Returns the hook flags indicating which hooks should be called. + * @return hookFlags The hook flags. + */ + function getHookFlags() public pure override returns (HookFlags memory hookFlags) { + hookFlags.shouldCallComputeDynamicSwapFee = true; + } + + /** + * @notice Called when the hook is registered. + * @param factory The address of the factory. + * @param pool The address of the pool. + * @param tokenConfigs The token configurations. + * @param liquidityManagement The liquidity management settings. + * @return success True if the registration is successful. + */ + function onRegister( + address factory, + address pool, + TokenConfig[] memory tokenConfigs, + LiquidityManagement calldata liquidityManagement + ) public override onlyVault returns (bool success) { + emit TimeBasedDiscountHookRegistered(address(this), factory, pool); + return factory == _allowedFactory && IBasePoolFactory(factory).isPoolFromFactory(pool); + } + + /** + * @notice Computes the dynamic swap fee percentage. + * @param params The pool swap parameters. + * @param staticSwapFeePercentage The static swap fee percentage. + * @return success True if the computation is successful. + * @return dynamicFee The dynamic swap fee percentage. + */ + function onComputeDynamicSwapFeePercentage( + PoolSwapParams calldata params, + address, + uint256 staticSwapFeePercentage + ) public view override onlyVault returns (bool, uint256) { + // Get the current hour (0-23) adjusted for timezone + int256 currentHour = getCurrentTimeStampWithOffset(); + + // Define peak hours based on the configured start and end hours + bool isPeakHour = getIfPeakHour(currentHour); + + uint256 dynamicFee; + if (isPeakHour) { + // During peak hours, keep the fee as the staticSwapFeePercentage + dynamicFee = staticSwapFeePercentage; + } else { + // During off-peak hours, decrease the fee by the configured discount percentage + dynamicFee = (staticSwapFeePercentage * (100 - discountPercentage)) / 100; + } + + return (true, dynamicFee); + } + + function getCurrentTimeStampWithOffset() public view returns (int256) { + int256 currentHour = (int256(block.timestamp / 3600) + TIMEZONE_OFFSET) % 24; + if (currentHour < 0) currentHour += 24; + return currentHour; + } + + function getIfPeakHour(int256 currentHour) public view returns (bool) { + bool isPeakHour = uint256(currentHour) >= peakHourStart && uint256(currentHour) <= peakHourEnd; + return isPeakHour; + } +} \ No newline at end of file From 339f1dee6eea74f3c97f882c98439fd87beadec4 Mon Sep 17 00:00:00 2001 From: Vagish Dilawari <40742522+dilawari2008@users.noreply.github.com> Date: Wed, 2 Oct 2024 11:25:29 +0530 Subject: [PATCH 2/2] Update README.md --- README.md | 302 +----------------------------------------------------- 1 file changed, 4 insertions(+), 298 deletions(-) diff --git a/README.md b/README.md index 1397b2da..9530408e 100644 --- a/README.md +++ b/README.md @@ -1,301 +1,7 @@ -# ๐Ÿ—๏ธŽ Scaffold Balancer v3 +# Time Based Discount Hook -A starter kit for building on top of Balancer v3. Accelerate the process of creating custom pools and hooks contracts. Concentrate on mastering the core concepts within a swift and responsive environment augmented by a local fork and a frontend pool operations playground. +There are times during the day where the traffic is higher as compared to the other times. In order to accelerate traffic in the non peak hours, we can incentivise users by giving discount in the non peak hours on the swap fee. -[![intro-to-scaffold-balancer](https://github.com/user-attachments/assets/f862091d-2fe9-4b4b-8d70-cb2fdc667384)](https://www.youtube.com/watch?v=m6q5M34ZdXw) +Pool Lifecycle Implementation Point - onComputeDynamicSwapFeePercentage -### ๐Ÿ” Development Life Cycle - -1. Learn the core concepts for building on top of Balancer v3 -2. Configure and deploy factories, pools, and hooks contracts to a local anvil fork of Sepolia -3. Interact with pools via a frontend that runs at [localhost:3000](http://localhost:3000/) - -### ๐Ÿชง Table Of Contents - -- [๐Ÿง‘โ€๐Ÿ’ป Environment Setup](#-environment-setup) -- [๐Ÿ‘ฉโ€๐Ÿซ Learn Core Concepts](#-learn-core-concepts) -- [๐Ÿ•ต๏ธ Explore the Examples](#-explore-the-examples) -- [๐ŸŒŠ Create a Custom Pool](#-create-a-custom-pool) -- [๐Ÿญ Create a Pool Factory](#-create-a-pool-factory) -- [๐Ÿช Create a Pool Hook](#-create-a-pool-hook) -- [๐Ÿšข Deploy the Contracts](#-deploy-the-contracts) -- [๐Ÿงช Test the Contracts](#-test-the-contracts) - -## ๐Ÿง‘โ€๐Ÿ’ป Environment Setup - -### 1. Requirements ๐Ÿ“œ - -- [Node (>= v18.17)](https://nodejs.org/en/download/) -- Yarn ([v1](https://classic.yarnpkg.com/en/docs/install/) or [v2+](https://yarnpkg.com/getting-started/install)) -- [Git](https://git-scm.com/downloads) -- [Foundry](https://book.getfoundry.sh/getting-started/installation) (>= v0.2.0) - -### 2. Quickstart ๐Ÿƒ - -1. Ensure you have the latest version of foundry installed - -``` -foundryup -``` - -2. Clone this repo & install dependencies - -```bash -git clone https://github.com/balancer/scaffold-balancer-v3.git -cd scaffold-balancer-v3 -yarn install -``` - -3. Set a `SEPOLIA_RPC_URL` in the `packages/foundry/.env` file - -``` -SEPOLIA_RPC_URL=... -``` - -4. Start a local anvil fork of the Sepolia testnet - -```bash -yarn fork -``` - -5. Deploy the mock tokens, pool factories, pool hooks, and custom pools contracts - > By default, the anvil account #0 will be the deployer and recieve the mock tokens and BPT from pool initialization - -```bash -yarn deploy -``` - -6. Start the nextjs frontend - -```bash -yarn start -``` - -7. Explore the frontend - -- Navigate to http://localhost:3000 to see the home page -- Visit the [Pools Page](http://localhost:3000/pools) to search by address or select using the pool buttons -- Vist the [Debug Page](http://localhost:3000/debug) to see the mock tokens, factory, and hooks contracts - -8. Run the Foundry tests - -``` -yarn test -``` - -### 3. Scaffold ETH 2 Tips ๐Ÿ—๏ธ - -SE-2 offers a variety of configuration options for connecting an account, choosing networks, and deploying contracts - -
๐Ÿ”ฅ Burner Wallet - -If you do not have an active wallet extension connected to your web browser, then scaffold eth will automatically connect to a "burner wallet" that is randomly generated on the frontend and saved to the browser's local storage. When using the burner wallet, transactions will be instantly signed, which is convenient for quick iterative development. - -To force the use of burner wallet, disable your browsers wallet extensions and refresh the page. Note that the burner wallet comes with 0 ETH to pay for gas so you will need to click the faucet button in top right corner. Also the mock tokens for the pool are minted to your deployer account set in `.env` so you will want to navigate to the "Debug Contracts" page to mint your burner wallet some mock tokens to use with the pool. - -![Burner Wallet](https://github.com/Dev-Rel-as-a-Service/scaffold-balancer-v3/assets/73561520/0a1f3456-f22a-46b5-9e05-0ef5cd17cce7) - -![Debug Tab Mint](https://github.com/Dev-Rel-as-a-Service/scaffold-balancer-v3/assets/73561520/fbb53772-8f6d-454d-a153-0e7a2925ef9f) - -
- -
๐Ÿ‘› 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 -- 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. - -
- -
๐Ÿ› Debug Contracts Page - -The [Debug Contracts Page](http://localhost:3000/debug) can be useful for viewing and interacting with all of the externally avaiable read and write functions of a contract. The page will automatically hot reload with contracts that are deployed via the `01_DeployConstantSumFactory.s.sol` script. We use this handy setup to mint `mockERC20` tokens to any connected wallet - -
- -
๐ŸŒ 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` - -```typescript -const scaffoldConfig = { - targetNetworks: [chains.foundry], -``` - -
- -
๐Ÿด Changing The Forked Network - -- By default, the `yarn fork` command points at sepolia, but any of the network aliases from the `[rpc_endpoints]` of `foundry.toml` can be used to modify the `"fork"` alias in the `packages/foundry/package.json` file - -```json - "fork": "anvil --fork-url ${0:-sepolia} --chain-id 31337 --config-out localhost.json", -``` - -- To point the frontend at a different forked network, change the `targetFork` in `scaffold.config.ts` - -```typescript -const scaffoldConfig = { - // The networks the frontend can connect to - targetNetworks: [chains.foundry], - - // If using chains.foundry as your targetNetwork, you must specify a network to fork - targetFork: chains.sepolia, -``` - -
- -## ๐Ÿ‘ฉโ€๐Ÿซ Learn Core Concepts - -- [Contract Architecture](https://docs-v3.balancer.fi/concepts/core-concepts/architecture.html) -- [Balancer Pool Tokens](https://docs-v3.balancer.fi/concepts/core-concepts/balancer-pool-tokens.html) -- [Balancer Pool Types](https://docs-v3.balancer.fi/concepts/explore-available-balancer-pools/) -- [Building Custom AMMs](https://docs-v3.balancer.fi/build-a-custom-amm/) -- [Exploring Hooks and Custom Routers](https://pitchandrolls.com/2024/08/30/unlocking-the-power-of-balancer-v3-exploring-hooks-and-custom-routers/) -- [Hook Development Tips](https://medium.com/@johngrant/unlocking-the-power-of-balancer-v3-hook-development-made-simple-831391a68296) - -![v3-components](https://github.com/user-attachments/assets/ccda9323-790f-4276-b092-c867fd80bf9e) - -## ๐Ÿ•ต๏ธ Explore the Examples - -Each of the following examples have turn key deploy scripts that can be found in the [foundry/script/](https://github.com/balancer/scaffold-balancer-v3/tree/main/packages/foundry/script) directory - -### 1. Constant Sum Pool with Dynamic Swap Fee Hook - -The swap fee percentage is altered by the hook contract before the pool calculates the amount for the swap - -![dynamic-fee-hook](https://github.com/user-attachments/assets/5ba69ea3-6894-4eeb-befa-ed87cfeb6b13) - -### 2. Constant Product Pool with Lottery Hook - -An after swap hook makes a request to an oracle contract for a random number - -![after-swap-hook](https://github.com/user-attachments/assets/594ce1ac-2edc-4d16-9631-14feb2d085f8) - -### 3. Weighted Pool with Exit Fee Hook - -An after remove liquidity hook adjusts the amounts before the vault transfers tokens to the user - -![after-remove-liquidity-hook](https://github.com/user-attachments/assets/2e8f4a5c-f168-4021-b316-28a79472c8d1) - -## ๐ŸŒŠ Create a Custom Pool - -[![custom-amm-video](https://github.com/user-attachments/assets/e6069a51-f1b5-4f98-a2a9-3a2098696f96)](https://www.youtube.com/watch?v=kXynS3jAu0M) - -### 1. Review the Docs ๐Ÿ“– - -- [Create a custom AMM with a novel invariant](https://docs-v3.balancer.fi/build-a-custom-amm/build-an-amm/create-custom-amm-with-novel-invariant.html) - -### 2. Recall the Key Requirements ๐Ÿ”‘ - -- Must inherit from `IBasePool` and `BalancerPoolToken` -- Must implement `onSwap`, `computeInvariant`, and `computeBalance` -- Must implement `getMaximumSwapFeePercentage` and `getMinimumSwapFeePercentage` - -### 3. Write a Custom Pool Contract ๐Ÿ“ - -- To get started, edit the`ConstantSumPool.sol` contract directly or make a copy - -## ๐Ÿญ Create a Pool Factory - -After designing a pool contract, the next step is to prepare a factory contract because Balancer's off-chain infrastructure uses the factory address as a means to identify the type of pool, which is important for integration into the UI, SDK, and external aggregators - -### 1. Review the Docs ๐Ÿ“– - -- [Deploy a Custom AMM Using a Factory](https://docs-v3.balancer.fi/build-a-custom-amm/build-an-amm/deploy-custom-amm-using-factory.html) - -### 2. Recall the Key Requirements ๐Ÿ”‘ - -- A pool factory contract must inherit from [BasePoolFactory](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/factories/BasePoolFactory.sol) -- Use the internal `_create` function to deploy a new pool -- Use the internal `_registerPoolWithVault` fuction to register a pool immediately after creation - -### 3. Write a Factory Contract ๐Ÿ“ - -- To get started, edit the`ConstantSumFactory.sol` contract directly or make a copy - -## ๐Ÿช Create a Pool Hook - -[![hook-video](https://github.com/user-attachments/assets/96e12c29-53c2-4a52-9437-e477f6d992d1)](https://www.youtube.com/watch?v=kaz6duliRPA) - -### 1. Review the Docs ๐Ÿ“– - -- [Extend an Existing Pool Type Using Hooks](https://docs-v3.balancer.fi/build-a-custom-amm/build-an-amm/extend-existing-pool-type-using-hooks.html) - -### 2. Recall the Key Requirements ๐Ÿ”‘ - -- A hooks contract must inherit from [BasePoolHooks.sol](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/BaseHooks.sol) -- A hooks contract should also inherit from [VaultGuard.sol](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/VaultGuard.sol) -- Must implement `onRegister` to determine if a pool is allowed to use the hook contract -- Must implement `getHookFlags` to define which hooks are supported -- The `onlyVault` modifier should be applied to all hooks functions (i.e. `onRegister`, `onBeforeSwap`, `onAfterSwap` ect.) - -### 3. Write a Hook Contract ๐Ÿ“ - -- To get started, edit the `VeBALFeeDiscountHook.sol` contract directly or make a copy - -## ๐Ÿšข Deploy the Contracts - -The deploy scripts are located in the [foundry/script/](https://github.com/balancer/scaffold-balancer-v3/tree/main/packages/foundry/script) directory. To better understand the lifecycle of deploying a pool that uses a hooks contract, see the diagram below - -![pool-deploy-scripts](https://github.com/user-attachments/assets/bb906080-8f42-46c0-af90-ba01ba1754fc) - -### 1. Modifying the Deploy Scripts ๐Ÿ› ๏ธ - -For all the scaffold integrations to work properly, each deploy script must be imported into `Deploy.s.sol` and inherited by the `DeployScript` contract in `Deploy.s.sol` - -### 2. Broadcast the Transactions ๐Ÿ“ก - -#### Deploy to local fork - -1. Run the following command - -```bash -yarn deploy -``` - -#### Deploy to a live network - -1. Add a `DEPLOYER_PRIVATE_KEY` to the `packages/foundry/.env` file - -``` -DEPLOYER_PRIVATE_KEY=0x... -SEPOLIA_RPC_URL=... -``` - -> The `DEPLOYER_PRIVATE_KEY` must start with `0x` and must hold enough Sepolia ETH to deploy the contracts. This account will receive the BPT from pool initialization - -2. Run the following command - -``` -yarn deploy --network sepolia -``` - -## ๐Ÿงช Test the Contracts - -The [balancer-v3-monorepo](https://github.com/balancer/balancer-v3-monorepo) provides testing utility contracts like [BasePoolTest](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/test/foundry/utils/BasePoolTest.sol) and [BaseVaultTest](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/test/foundry/utils/BaseVaultTest.sol). Therefore, the best way to begin writing tests for custom factories, pools, and hooks contracts is to leverage the examples established by the source code. - -### 1. Testing Factories ๐Ÿ‘จโ€๐Ÿ”ฌ - -The `ConstantSumFactoryTest` roughly mirrors the [WeightedPool8020FactoryTest -](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-weighted/test/foundry/WeightedPool8020Factory.t.sol) - -``` -yarn test --match-contract ConstantSumFactoryTest -``` - -### 2. Testing Pools ๐ŸŠ - -The `ConstantSumPoolTest` roughly mirrors the [WeightedPoolTest](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-weighted/test/foundry/WeightedPool.t.sol) - -``` -yarn test --match-contract ConstantSumPoolTest -``` - -### 3. Testing Hooks ๐ŸŽฃ - -The `VeBALFeeDiscountHookExampleTest` mirrors the [VeBALFeeDiscountHookExampleTest](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-hooks/test/foundry/VeBALFeeDiscountHookExample.t.sol) - -``` -yarn test --match-contract VeBALFeeDiscountHookExampleTest -``` +This is a clean pr of the hook. Some code changes were done for demo purposes, the pr of the demo is - https://github.com/balancer/scaffold-balancer-v3/pull/83/files.