Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved deploy script pattern #64

Merged
merged 7 commits into from
Aug 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 35 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,17 @@ A full stack prototyping tool for building on top of Balancer v3. Accelerate the

### 🪧 Table Of Contents

0. [Environment Setup 🧑‍💻](#0-environment-setup-)
1. [Create a Custom Pool 🌊](#1-create-a-custom-pool-)
2. [Create a Pool Factory 🏭](#2-create-a-pool-factory-)
3. [Create a Pool Hook 🪝](#3-create-a-pool-hook-)
4. [Deploy the Contracts 🚢](#4-deploy-the-contracts-)
5. [Test the Contracts 🧪](#5-test-the-contracts-)
- [🧑‍💻 Environment Setup](#0-environment-setup-)
- [🌊 Create a Custom Pool](#1-create-a-custom-pool-)
- [🏭 Create a Pool Factory](#2-create-a-pool-factory-)
- [🪝 Create a Pool Hook](#3-create-a-pool-hook-)
- [🚢 Deploy the Contracts](#4-deploy-the-contracts-)
- [🧪 Test the Contracts](#5-test-the-contracts-)

## 0. Environment Setup 🧑‍💻
## 🧑‍💻 Environment Setup

[![image](https://github.com/user-attachments/assets/2d0d5c6d-647d-4782-8d7a-9076b39319b9)](https://www.youtube.com/watch?v=2lInvpCt2o4)
<!-- TODO: Record Updated Video -->
<!-- [![image](https://github.com/user-attachments/assets/2d0d5c6d-647d-4782-8d7a-9076b39319b9)](https://www.youtube.com/watch?v=2lInvpCt2o4) -->

### 📜 Requirements

Expand Down Expand Up @@ -144,7 +145,7 @@ const scaffoldConfig = {

</details>

## 1. Create a Custom Pool 🌊
## 🌊 Create a Custom Pool

Your journey begins with planning the custom computation logic for the pool, which defines how an AMM exchanges one asset for another.

Expand All @@ -162,7 +163,7 @@ Your journey begins with planning the custom computation logic for the pool, whi

- To get started, edit the`ConstantSumPool.sol` contract directly or make a copy

## 2. Create a Pool Factory 🏭
## 🏭 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

Expand All @@ -180,7 +181,7 @@ After designing a pool contract, the next step is to prepare a factory contract

- To get started, edit the`ConstantSumFactory.sol` contract directly or make a copy

## 3. Create a Pool Hook 🪝
## 🪝 Create a Pool Hook

Next, consider further extending the functionality of the custom pool contract with a hooks contract. If your custom pool does not need a hooks contract, use the zero address during pool registration

Expand All @@ -198,33 +199,39 @@ Next, consider further extending the functionality of the custom pool contract w

- To get started, edit the `VeBALFeeDiscountHook.sol` contract directly or make a copy

## 4. Deploy the Contracts 🚢
## 🚢 Deploy the Contracts

The deploy scripts are all located in the [foundry/script/](https://github.com/balancer/scaffold-balancer-v3/tree/main/packages/foundry/script) directory and are prefixed with a number based on the order the order they're intended to be run. The mock tokens, factories, and hooks contracts must be deployed before the pools. On the frontend, the [Pools](http://localhost:3000/pools) page will automatically add a button above the search bar for any pools deployed using the latest factory contract
The deploy scripts are all located in the [foundry/script/](https://github.com/balancer/scaffold-balancer-v3/tree/main/packages/foundry/script) directory. All deploy scripts should be run inside of `Deploy.s.sol` so that the `export` modifier can automate the transfer of deployed contract info to `nextjs/contracts/depoloyedContracts.ts`

### 🛠️ Adjust the Deploy Scripts
### 👯 Follow the Pattern

#### `00_DeploySetup.s.sol`
To add a new deploy script, import it into `Deploy.s.sol`, create a new instance of it, and run it

Deploy mock tokens, factory contracts, and hooks contracts to be used by pools
```
function run() external virtual export {
DeployYourContract deployYourContract = new DeployYourContract();
deployYourContract.run();
}
```

- Set the `pauseWindowDuration` for the factory contracts
- Set the mock token names, symbols, and supply
- Set any hooks contracts constructor args
### 🛠️ Examine the Example Deploy Scripts

#### `01_DeployConstantSumPool.s.sol`
#### `00_DeployMockTokens.s.sol`

Deploy, register, and initialize a Constant Sum Pool
1. Deploys mock tokens that are used to register and initialize pools
2. Deploys a mock token used for the example `VeBALFeeDiscountHook` contract

- Set the pool registration config in the `getRegistrationConfig()` function
- Set the pool initialization config in the `getInitializationConfig()` function
#### `01_DeployConstantSum.s.sol`

#### `02_DeployConstantProductPool.s.sol`
1. Deploys a `ConstantSumFactory`
2. Deploys and registers a `ConstantSumPool`
3. Initializes the `ConstantSumPool` using mock tokens

Deploy, register, and initialize a Constant Product Pool
#### `02_DeployConstantProduct.s.sol`

- Set the pool registration config in the `getRegistrationConfig()` function
- Set the pool initialization config in the `getInitializationConfig()` function
1. Deploys a `ConstantProductFactory`
2. Deploys and registers a `ConstantProductPool`
3. Initializes the `ConstantProductPool` using mock tokens

### 📡 Broadcast the Transactions

Expand All @@ -234,28 +241,9 @@ To run all the deploy scripts
yarn deploy
```

To run only the `DeploySetup` script

```bash
yarn deploy:setup
```

To run only the `DeployConstantSumPool` script

```bash
yarn deploy:sum
```

To run only the `DeployConstantProductPool` script

```bash
yarn deploy:product
```

🛈 To deploy to the live sepolia testnet, add the `--network sepolia` flag
🛈 To modify the yarn commands, edit the "scripts" section of the [/foundry/package.json](https://github.com/balancer/scaffold-balancer-v3/blob/main/packages/foundry/package.json)

## 5. Test the Contracts 🧪
## 🧪 Test the Contracts

The [balancer-v3-monorepo](https://github.com/balancer/balancer-v3-monorepo) provides testing utility contracts like [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 factory, pool, and hook contracts is to utilize the patterns and methods established by the source code.

Expand Down
57 changes: 37 additions & 20 deletions packages/foundry/contracts/hooks/VeBALFeeDiscountHook.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,63 +9,80 @@ import {
TokenConfig,
LiquidityManagement
} from "@balancer-labs/v3-vault/contracts/BaseHooks.sol";
import { IBasePoolFactory } from "@balancer-labs/v3-interfaces/contracts/vault/IBasePoolFactory.sol";
import { IBasePool } from "@balancer-labs/v3-interfaces/contracts/vault/IBasePool.sol";
import { IBasePoolFactory } from "@balancer-labs/v3-interfaces/contracts/vault/IBasePoolFactory.sol";
import { IRouterCommon } from "@balancer-labs/v3-interfaces/contracts/vault/IRouterCommon.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/**
* @title VeBAL Fee Discount Hook Example
* @title VeBAL Fee Discount Hook
* @notice Applies a 50% discount to the swap fee for users holding veBAL tokens
*/
contract VeBALFeeDiscountHook is BaseHooks {
// only pools from the allowedFactory are able to register and use this hook
address private immutable _allowedFactory;
// only calls from a trusted routers are allowed to call this hook, because the hook relies on the getSender
// implementation to work properly
address private immutable _trustedRouter;
IERC20 private immutable _veBAL;

constructor(IVault vault, address allowedFactory, address veBAL, address trustedRouter) BaseHooks(vault) {
constructor(IVault vault, address allowedFactory, address trustedRouter, IERC20 veBAL) BaseHooks(vault) {
_allowedFactory = allowedFactory;
_trustedRouter = trustedRouter;
_veBAL = IERC20(veBAL);
}

/// @inheritdoc IHooks
function getHookFlags() external pure override returns (IHooks.HookFlags memory hookFlags) {
hookFlags.shouldCallComputeDynamicSwapFee = true;
_veBAL = veBAL;
}

/// @inheritdoc IHooks
/**
* @notice Hook executed when pool is registered
* @dev Return true if registration was successful
* @dev Return false to revert the registration of the pool
* @dev Vault address can be accessed with msg.sender
* @param factory Address of the pool factory
* @param pool Address of the pool
* @return success True if the hook allowed the registration, false otherwise
*/
function onRegister(
address factory,
address pool,
TokenConfig[] memory,
LiquidityManagement calldata
) external view override returns (bool) {
// This hook implements a restrictive approach, where we check if the factory is an allowed factory and if
// the pool was created by the allowed factory. Since we only use onComputeDynamicSwapFee, this might be an
// overkill in real applications because the pool math doesn't play a role in the discount calculation.
// Only pools deployed by an allowed factory may register
return factory == _allowedFactory && IBasePoolFactory(factory).isPoolFromFactory(pool);
}

/**
* @notice Returns flags informing which hooks are implemented in the contract.
* @return hookFlags Flags indicating which hooks the contract supports
*/
function getHookFlags() external pure override returns (IHooks.HookFlags memory hookFlags) {
// Support the `onComputeDynamicSwapFeePercentage` hook
hookFlags.shouldCallComputeDynamicSwapFee = true;
}

/**
* @notice Called before `onBeforeSwap` if the pool has dynamic fees.
* @param params Swap parameters (see IBasePool.PoolSwapParams for struct definition)
* @param staticSwapFeePercentage Value of the static swap fee, for reference
* @return success True if the pool wishes to proceed with settlement
* @return dynamicSwapFee Value of the swap fee
*/
function onComputeDynamicSwapFee(
IBasePool.PoolSwapParams calldata params,
address,
address, // pool
uint256 staticSwapFeePercentage
) external view override returns (bool, uint256) {
// If the router is not trusted, does not apply the veBAL discount because getSender() may be manipulated by a malicious router.
) external view override returns (bool success, uint256 dynamicSwapFee) {
// If the router is not trusted, do not apply a fee discount
if (params.router != _trustedRouter) {
return (true, staticSwapFeePercentage);
}

// Find the user's address
address user = IRouterCommon(params.router).getSender();

// If user has veBAL, apply a 50% discount to the current fee (divides fees by 2)
// If the user owns veBAL, apply a 50% discount to the swap fee
if (_veBAL.balanceOf(user) > 0) {
return (true, staticSwapFeePercentage / 2);
}

// Otherwise, do not apply the discount
return (true, staticSwapFeePercentage);
}
}
11 changes: 2 additions & 9 deletions packages/foundry/contracts/mocks/MockToken1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,13 @@ pragma solidity ^0.8.24;

import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

/**
* @title Mock Token 1
* @notice Entire initial supply is minted to the deployer
* @dev Default decimals is 18, but you can override the decimals function from ERC20
*/
contract MockToken1 is ERC20 {
// Mint the initial supply to the deployer
constructor(string memory name, string memory symbol, uint256 initialSupply) ERC20(name, symbol) {
_mint(msg.sender, initialSupply);
}

/**
* Allow any user to mint any amount of tokens to their wallet
* This function is accessible on the frontend's "Debug" page
*/
// Allow any user to mint any amount of tokens to their wallet
function mint(uint256 amount) external {
_mint(msg.sender, amount);
}
Expand Down
11 changes: 2 additions & 9 deletions packages/foundry/contracts/mocks/MockToken2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,13 @@ pragma solidity ^0.8.24;

import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

/**
* @title Mock Token 2
* @notice Entire initial supply is minted to the deployer
* @dev Default decimals is 18, but you can override the decimals function from ERC20
*/
contract MockToken2 is ERC20 {
// Mint the initial supply to the deployer
constructor(string memory name, string memory symbol, uint256 initialSupply) ERC20(name, symbol) {
_mint(msg.sender, initialSupply);
}

/**
* Allow any user to mint any amount of tokens to their wallet
* This function is accessible on the frontend's "Debug" page
*/
// Allow any user to mint any amount of tokens to their wallet
function mint(uint256 amount) external {
_mint(msg.sender, amount);
}
Expand Down
11 changes: 2 additions & 9 deletions packages/foundry/contracts/mocks/MockVeBAL.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,13 @@ pragma solidity ^0.8.24;

import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

/**
* @title Mock VeBAL
* @notice Entire initial supply is minted to the deployer
* @dev Default decimals is 18, but you can override the decimals function from ERC20
*/
contract MockVeBAL is ERC20 {
// Mint the initial supply to the deployer
constructor(string memory name, string memory symbol, uint256 initialSupply) ERC20(name, symbol) {
_mint(msg.sender, initialSupply);
}

/**
* Allow any user to mint any amount of tokens to their wallet
* This function is accessible on the frontend's "Debug" page
*/
// Allow any user to mint any amount of tokens to their wallet
function mint(uint256 amount) external {
_mint(msg.sender, amount);
}
Expand Down
5 changes: 1 addition & 4 deletions packages/foundry/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@
"account": "node script/ListAccount.js",
"chain": "anvil --config-out localhost.json",
"compile": "forge compile",
"deploy": "yarn deploy:setup && yarn deploy:sum && yarn deploy:product",
"deploy:setup": "forge build --build-info --build-info-path out/build-info/ && forge script script/00_DeploySetup.s.sol --rpc-url ${1:-default_network} --broadcast --legacy && node scripts-js/generateTsAbis.js",
"deploy:sum": "forge script script/01_DeployConstantSumPool.s.sol --rpc-url ${1:-default_network} --broadcast",
"deploy:product": "forge script script/02_DeployConstantProductPool.s.sol --rpc-url ${1:-default_network} --broadcast",
"deploy": "forge build --build-info --build-info-path out/build-info/ && forge script script/Deploy.s.sol --rpc-url ${1:-default_network} --broadcast --legacy && node scripts-js/generateTsAbis.js",
"flatten": "forge flatten",
"fork": "anvil --fork-url ${0:-sepolia} --chain-id 31337 --config-out localhost.json",
"format": "npx prettier --write --plugin=prettier-plugin-solidity 'contracts/**/*.sol' 'test/**/*.sol' 'script/*.sol' 'utils/*.sol'",
Expand Down
34 changes: 34 additions & 0 deletions packages/foundry/script/00_DeployMockTokens.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol";
import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";

import { ScaffoldHelpers, console } from "./ScaffoldHelpers.sol";
import { MockToken1 } from "../contracts/mocks/MockToken1.sol";
import { MockToken2 } from "../contracts/mocks/MockToken2.sol";
import { MockVeBAL } from "../contracts/mocks/MockVeBAL.sol";

/**
* @title Deploy Mock Tokens
* @notice Deploys mock tokens for use with pools and hooks
*/
contract DeployMockTokens is ScaffoldHelpers {
function run() external virtual returns (IERC20 mockToken1, IERC20 mockToken2, IERC20 mockVeBAL) {
uint256 deployerPrivateKey = getDeployerPrivateKey();

vm.startBroadcast(deployerPrivateKey);

// For use with pool contracts
mockToken1 = new MockToken1("Mock Token 1", "MT1", 1000e18);
mockToken2 = new MockToken2("Mock Token 2", "MT2", 1000e18);
console.log("MockToken1 deployed at: %s", address(mockToken1));
console.log("MockToken2 deployed at: %s", address(mockToken2));

// For use with VeBALFeeDiscountHook
mockVeBAL = new MockVeBAL("Vote-escrow BAL", "veBAL", 1000e18);
console.log("Mock Vote-escrow BAL deployed at: %s", address(mockVeBAL));

vm.stopBroadcast();
}
}
Loading
Loading