Skip to content

Commit

Permalink
Update backend with hooks contract support
Browse files Browse the repository at this point in the history
  • Loading branch information
MattPereira committed Jun 29, 2024
1 parent 5342df9 commit ba4b8f8
Show file tree
Hide file tree
Showing 13 changed files with 1,416 additions and 302 deletions.
13 changes: 9 additions & 4 deletions packages/foundry/contracts/ConstantSumFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,16 @@ contract ConstantSumFactory is BasePoolFactory {
) BasePoolFactory(vault, pauseWindowDuration, type(ConstantSumPool).creationCode) {}

/**
* @notice Deploys a new `CustomPool`
* @notice Deploys a new pool and registers it with the vault
* @param name The name of the pool
* @param symbol The symbol of the pool
* @param tokens An array of descriptors for the tokens the pool will manage
* @param salt The salt value that will be passed to create3 deployment
* @param tokens An array of descriptors for the tokens the pool will manage
* @param swapFeePercentage Initial swap fee percentage
* @param protocolFeeExempt true, the pool's initial aggregate fees will be set to 0
* @param roleAccounts Addresses the Vault will allow to change certain pool settings
* @param poolHooksContract Contract that implements the hooks for the pool
* @param liquidityManagement Liquidity management flags with implemented methods
*/
function create(
string memory name,
Expand All @@ -39,9 +44,9 @@ contract ConstantSumFactory is BasePoolFactory {
address poolHooksContract,
LiquidityManagement memory liquidityManagement
) external returns (address pool) {
// Deploy the pool
// First deploy a new pool
pool = _create(abi.encode(getVault(), name, symbol), salt);
// Register the pool
// Then register the pool
_registerPoolWithVault(
pool,
tokens,
Expand Down
22 changes: 0 additions & 22 deletions packages/foundry/contracts/MockToken1.sol

This file was deleted.

22 changes: 0 additions & 22 deletions packages/foundry/contracts/MockToken2.sol

This file was deleted.

72 changes: 72 additions & 0 deletions packages/foundry/contracts/VeBALFeeDiscountHook.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

import {
BasePoolHooks,
IVault,
IHooks,
TokenConfig,
LiquidityManagement
} from "@balancer-labs/v3-vault/contracts/BasePoolHooks.sol";
import { IBasePoolFactory } from "@balancer-labs/v3-interfaces/contracts/vault/IBasePoolFactory.sol";
import { IBasePool } from "@balancer-labs/v3-interfaces/contracts/vault/IBasePool.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
*/
contract VeBALFeeDiscountHook is BasePoolHooks {
// 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) BasePoolHooks(vault) {
_allowedFactory = allowedFactory;
_trustedRouter = trustedRouter;
_veBAL = IERC20(veBAL);
}

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

/// @inheritdoc IHooks
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.
return factory == _allowedFactory && IBasePoolFactory(factory).isPoolFromFactory(pool);
}

/// @inheritdoc IHooks
function onComputeDynamicSwapFee(
IBasePool.PoolSwapParams calldata params,
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.
if (params.router != _trustedRouter) {
return (true, staticSwapFeePercentage);
}

address user = IRouterCommon(params.router).getSender();

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

return (true, staticSwapFeePercentage);
}
}
23 changes: 23 additions & 0 deletions packages/foundry/contracts/mocks/MockToken1.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//SPDX-License-Identifier: MIT
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 {
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
*/
function mint(uint256 amount) external {
_mint(msg.sender, amount);
}
}
23 changes: 23 additions & 0 deletions packages/foundry/contracts/mocks/MockToken2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//SPDX-License-Identifier: MIT
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 {
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
*/
function mint(uint256 amount) external {
_mint(msg.sender, amount);
}
}
23 changes: 23 additions & 0 deletions packages/foundry/contracts/mocks/MockVeBAL.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//SPDX-License-Identifier: MIT
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 {
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
*/
function mint(uint256 amount) external {
_mint(msg.sender, amount);
}
}
6 changes: 3 additions & 3 deletions packages/foundry/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
"account": "node script/ListAccount.js",
"chain": "anvil --config-out localhost.json",
"compile": "forge compile",
"deploy:factory": "forge build --build-info --build-info-path out/build-info/ && forge script script/01_DeployFactory.s.sol --rpc-url ${1:-default_network} --broadcast --legacy && node script/generateTsAbis.js",
"deploy:pool1": "forge script script/02_DeployPool1.s.sol --rpc-url ${1:-default_network} --broadcast",
"deploy:pool2": "forge script script/03_DeployPool2.s.sol --rpc-url ${1:-default_network} --broadcast",
"deploy:factory": "forge build --build-info --build-info-path out/build-info/ && forge script script/01_DeployConstantSumFactory.s.sol --rpc-url ${1:-default_network} --broadcast --legacy && node script/generateTsAbis.js",
"deploy:pool1": "forge script script/02_DeployConstantSumPool1.s.sol --rpc-url ${1:-default_network} --broadcast",
"deploy:pool2": "forge script script/03_DeployConstantSumPool2.s.sol --rpc-url ${1:-default_network} --broadcast",
"deploy:verify": "forge build --build-info --build-info-path out/build-info/ && forge script script/DeployFactoryAndPool.s.sol --rpc-url ${1:-default_network} --broadcast --legacy --verify ; node script/generateTsAbis.js",
"flatten": "forge flatten",
"fork": "anvil --fork-url ${0:-sepolia} --chain-id 31337 --config-out localhost.json",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@ pragma solidity ^0.8.24;

import { ScaffoldETHDeploy, console } from "./ScaffoldETHDeploy.s.sol";
import { ConstantSumFactory } from "../contracts/ConstantSumFactory.sol";
import { VeBALFeeDiscountHook } from "../contracts/VeBALFeeDiscountHook.sol";
import { HelperConfig } from "../utils/HelperConfig.sol";
import { MockToken1 } from "../contracts/MockToken1.sol";
import { MockToken2 } from "../contracts/MockToken2.sol";
import { MockToken1 } from "../contracts/mocks/MockToken1.sol";
import { MockToken2 } from "../contracts/mocks/MockToken2.sol";
import { MockVeBAL } from "../contracts/mocks/MockVeBAL.sol";

/**
* @title Deploy Factory
* @notice Deploys mock tokens, a factory contract and a hook contract
* @dev Set the factory pauseWindowDuration in `HelperConfig.sol`
* @dev Run this script with `yarn deploy:factory`
*/
contract DeployFactory is HelperConfig, ScaffoldETHDeploy {
contract DeployConstantSumFactory is HelperConfig, ScaffoldETHDeploy {
function run() external virtual {
uint256 deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY");
if (deployerPrivateKey == 0) {
Expand All @@ -25,19 +28,27 @@ contract DeployFactory is HelperConfig, ScaffoldETHDeploy {

vm.startBroadcast(deployerPrivateKey);
/**
* @notice Deploy mock tokens to be used for initializing pools
* @dev remove this if you plan to use already deployed tokens
* @notice Deploys mock tokens used for pool initialization and hooks contract
* @dev Remove this if you plan to work with already deployed tokens
*/
MockToken1 scUSD = new MockToken1("Scaffold USD", "scUSD");
MockToken2 scDAI = new MockToken2("Scaffold DAI", "scDAI");
console.log("Deployed MockToken1 Address: %s", address(scUSD));
console.log("Deployed MockToken2 Address: %s", address(scDAI));
MockToken1 token1 = new MockToken1("Mock Token 1", "MT1", 1000e18);
MockToken2 token2 = new MockToken2("Mock Token 2", "MT2", 1000e18);
MockVeBAL veBAL = new MockVeBAL("Vote-escrow BAL", "veBAL", 1000e18);
console.log("Deployed MockToken1 Address: %s", address(token1));
console.log("Deployed MockToken2 Address: %s", address(token2));
console.log("Deployed Vote-escrow BAL Address: %s", address(veBAL));

/**
* @notice Deploys the factory contract using the pauseWindowDuration set in `HelperConfig.sol`
* @notice Deploys the factory contract
*/
ConstantSumFactory factory = new ConstantSumFactory(vault, pauseWindowDuration);
console.log("Deployed Factory Address: %s", address(factory));

/**
* @notice Deploys the hook contract
*/
VeBALFeeDiscountHook hook = new VeBALFeeDiscountHook(vault, address(factory), address(veBAL), address(router));

vm.stopBroadcast();

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import { Script, console } from "forge-std/Script.sol";
import { RegistrationConfig, InitializationConfig } from "../utils/PoolTypes.sol";

/**
* @title Deploy Pool Script
* @title Deploy Constant Sum Pool #1
* @notice This script deploys a new pool using the most recently deployed pool factory and mock tokens
* @dev Set the pool registration and initialization configurations in `HelperConfig.sol`
* @dev Run this script with `yarn deploy:pool`
* @dev Run this script with `yarn deploy:pool1`
*/
contract DeployPool is HelperFunctions, Script {
contract DeployConstantSumPool1 is HelperFunctions, Script {
error InvalidPrivateKey(string);

function run() external virtual {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ import { Script, console } from "forge-std/Script.sol";
import { RegistrationConfig, InitializationConfig } from "../utils/PoolTypes.sol";

/**
* @title Deploy Pool Script
* @title Deploy Constant Sum Pool #2
* @notice This script deploys a new pool using the most recently deployed pool factory and mock tokens
* @dev Some of the pool registration/initialization configurations are set in `HelperConfig.sol`, some are set directly in this script
* @dev Run this script with `yarn deploy:pool`
* @notice Some of the pool registration/initialization configurations are set in `HelperConfig.sol`
* @notice Some config is set directly in this script including the pool hooks contract
* @dev Run this script with `yarn deploy:pool2`
*/
contract DeployPool is HelperFunctions, Script {
contract DeployConstantSumPool2 is HelperFunctions, Script {
error InvalidPrivateKey(string);

function run() external virtual {
Expand All @@ -29,21 +30,24 @@ contract DeployPool is HelperFunctions, Script {
// Set the pool registration and initialization configurations in `HelperConfig.sol`
RegistrationConfig memory regConfig = getPoolConfig();
InitializationConfig memory initConfig = getInitializationConfig(regConfig.tokenConfig);
// Grab the most recently deployed address of the pool factory
address poolFactoryAddress = DevOpsTools.get_most_recent_deployment(
"ConstantSumFactory", // Must match the pool factory contract name
block.chainid
);
ConstantSumFactory factory = ConstantSumFactory(poolFactoryAddress);
/**
* @notice Altering some of the config for this second pool
* @dev watch out for "stack too deep" error if you declare too many vars directly in this `run()` function
* @dev Watch out for "stack too deep" error if you declare too many vars directly in this `run()` function
*/
PoolRoleAccounts memory roleAccounts = PoolRoleAccounts({
pauseManager: msg.sender, // Account empowered to pause/unpause the pool (or 0 to delegate to governance)
swapFeeManager: msg.sender, // Account empowered to set static swap fees for a pool (or 0 to delegate to goverance)
poolCreator: msg.sender // Account empowered to set the pool creator fee percentage
});
address veBalFeeDiscountHook = DevOpsTools.get_most_recent_deployment(
"VeBALFeeDiscountHook", // Must match the mock token contract name
block.chainid
);
LiquidityManagement memory liquidityManagement = LiquidityManagement({
disableUnbalancedLiquidity: false,
enableAddLiquidityCustom: true,
Expand All @@ -60,7 +64,7 @@ contract DeployPool is HelperFunctions, Script {
33e12, // swapFeePercentage of 0.000033%
regConfig.protocolFeeExempt,
roleAccounts,
regConfig.poolHooksContract,
veBalFeeDiscountHook, // poolHooksContract
liquidityManagement
);
console.log("Deployed pool at address: %s", newPool);
Expand Down
6 changes: 2 additions & 4 deletions packages/foundry/utils/HelperConfig.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@ import { IRateProvider } from "@balancer-labs/v3-interfaces/contracts/vault/IRat
import { InputHelpers } from "@balancer-labs/v3-solidity-utils/contracts/helpers/InputHelpers.sol";
import { IPermit2 } from "permit2/src/interfaces/IPermit2.sol";

import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";
import { MockToken1 } from "../contracts/MockToken1.sol";
import { MockToken2 } from "../contracts/MockToken2.sol";
import { RegistrationConfig, InitializationConfig } from "./PoolTypes.sol";
import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";
import { DevOpsTools } from "lib/foundry-devops/src/DevOpsTools.sol";

/**
Expand Down Expand Up @@ -78,7 +76,7 @@ contract HelperConfig {
swapFeeManager: address(0), // Account empowered to set static swap fees for a pool (or 0 to delegate to goverance)
poolCreator: address(0) // Account empowered to set the pool creator fee percentage
});
address poolHooksContract = address(0); // No hook contract
address poolHooksContract = address(0); // zero address if no hooks contract is needed
LiquidityManagement memory liquidityManagement = LiquidityManagement({
disableUnbalancedLiquidity: false,
enableAddLiquidityCustom: false,
Expand Down
Loading

0 comments on commit ba4b8f8

Please sign in to comment.