Skip to content

Commit

Permalink
Add support for weighted pools (#70)
Browse files Browse the repository at this point in the history
* add deploy script for weighted pool

* Add support for weighted pool to FE

* add min/max info about pools to FE

* Polish support for weighted pool
  • Loading branch information
MattPereira authored Aug 25, 2024
1 parent a488423 commit a5fbeee
Show file tree
Hide file tree
Showing 35 changed files with 1,028 additions and 356 deletions.
8 changes: 4 additions & 4 deletions packages/foundry/contracts/pools/ConstantProductPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,9 @@ import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
contract ConstantProductPool is BalancerPoolToken, IBasePool {
using FixedPoint for uint256;

// Invariant growth limit: non-proportional add cannot cause the invariant to increase by more than this ratio.
uint256 private constant _MAX_INVARIANT_RATIO = 300e16; // 300%
// Invariant shrink limit: non-proportional remove cannot cause the invariant to decrease by less than this ratio.
uint256 private constant _MIN_INVARIANT_RATIO = 70e16; // 70%
uint256 private constant _MIN_SWAP_FEE_PERCENTAGE = 0.001e18; // 0.1%
uint256 private constant _MAX_INVARIANT_RATIO = 300e16; // 300%
uint256 private constant _MIN_SWAP_FEE_PERCENTAGE = 1e12; // 0.00001%
uint256 private constant _MAX_SWAP_FEE_PERCENTAGE = 0.10e18; // 10%

constructor(IVault vault, string memory name, string memory symbol) BalancerPoolToken(vault, name, symbol) {}
Expand Down Expand Up @@ -70,11 +68,13 @@ contract ConstantProductPool is BalancerPoolToken, IBasePool {
newBalance = ((newInvariant * newInvariant) / poolBalanceOtherToken);
}

// Invariant shrink limit: non-proportional remove cannot cause the invariant to decrease by less than this ratio.
/// @return minimumInvariantRatio The minimum invariant ratio for a pool during unbalanced remove liquidity
function getMinimumInvariantRatio() external pure returns (uint256) {
return _MIN_INVARIANT_RATIO;
}

// Invariant growth limit: non-proportional add cannot cause the invariant to increase by more than this ratio.
/// @return maximumInvariantRatio The maximum invariant ratio for a pool during unbalanced add liquidity
function getMaximumInvariantRatio() external pure returns (uint256) {
return _MAX_INVARIANT_RATIO;
Expand Down
14 changes: 7 additions & 7 deletions packages/foundry/contracts/pools/ConstantSumPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pragma solidity ^0.8.24;

import { BalancerPoolToken } from "@balancer-labs/v3-vault/contracts/BalancerPoolToken.sol";
import { IBasePool, ISwapFeePercentageBounds } from "@balancer-labs/v3-interfaces/contracts/vault/IBasePool.sol";
import { IBasePool } from "@balancer-labs/v3-interfaces/contracts/vault/IBasePool.sol";
import { PoolSwapParams } from "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol";
import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol";

Expand All @@ -12,12 +12,10 @@ import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol"
* https://docs-v3.balancer.fi/build-a-custom-amm/build-an-amm/create-custom-amm-with-novel-invariant.html
*/
contract ConstantSumPool is IBasePool, BalancerPoolToken {
// Invariant growth limit: non-proportional add cannot cause the invariant to increase by more than this ratio.
uint256 private constant _MAX_INVARIANT_RATIO = 300e16; // 300%
// Invariant shrink limit: non-proportional remove cannot cause the invariant to decrease by less than this ratio.
uint256 private constant _MIN_INVARIANT_RATIO = 70e16; // 70%
uint256 private constant _MAX_INVARIANT_RATIO = 300e16; // 300%
uint256 private constant _MIN_SWAP_FEE_PERCENTAGE = 1e12; // 0.00001%
uint256 private constant _MAX_SWAP_FEE_PERCENTAGE = 0.1e18; // 10%
uint256 private constant _MAX_SWAP_FEE_PERCENTAGE = 0.10e18; // 10%

constructor(IVault vault, string memory name, string memory symbol) BalancerPoolToken(vault, name, symbol) {}

Expand Down Expand Up @@ -57,22 +55,24 @@ contract ConstantSumPool is IBasePool, BalancerPoolToken {
newBalance = (balancesLiveScaled18[tokenInIndex] + invariant * (invariantRatio)) - invariant;
}

// Invariant shrink limit: non-proportional remove cannot cause the invariant to decrease by less than this ratio.
/// @return minimumInvariantRatio The minimum invariant ratio for a pool during unbalanced remove liquidity
function getMinimumInvariantRatio() external pure returns (uint256) {
return _MIN_INVARIANT_RATIO;
}

// Invariant growth limit: non-proportional add cannot cause the invariant to increase by more than this ratio.
/// @return maximumInvariantRatio The maximum invariant ratio for a pool during unbalanced add liquidity
function getMaximumInvariantRatio() external pure returns (uint256) {
return _MAX_INVARIANT_RATIO;
}

/// @inheritdoc ISwapFeePercentageBounds
/// @return minimumSwapFeePercentage The minimum swap fee percentage for a pool
function getMinimumSwapFeePercentage() external pure returns (uint256) {
return _MIN_SWAP_FEE_PERCENTAGE;
}

/// @inheritdoc ISwapFeePercentageBounds
/// @return maximumSwapFeePercentage The maximum swap fee percentage for a pool
function getMaximumSwapFeePercentage() external pure returns (uint256) {
return _MAX_SWAP_FEE_PERCENTAGE;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/foundry/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"account": "node script/ListAccount.js",
"chain": "anvil --config-out localhost.json",
"compile": "forge compile",
"deploy": "forge build --build-info --build-info-path out/build-info/ && forge script script/Deploy.s.sol --rpc-url ${1:-default_network} --broadcast --slow && node scripts-js/generateTsAbis.js",
"deploy": "forge build --build-info --build-info-path out/build-info/ && forge script script/Deploy.s.sol --rpc-url ${1:-default_network} --broadcast && 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
1 change: 1 addition & 0 deletions packages/foundry/remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
@balancer-labs/v3-solidity-utils/=lib/balancer-v3-monorepo/pkg/solidity-utils/
@balancer-labs/v3-pool-utils/=lib/balancer-v3-monorepo/pkg/pool-utils/
@balancer-labs/v3-interfaces/=lib/balancer-v3-monorepo/pkg/interfaces/
@balancer-labs/v3-pool-weighted/=lib/balancer-v3-monorepo/pkg/pool-weighted/
@balancer-labs/v3-vault/=lib/balancer-v3-monorepo/pkg/vault/
permit2/=lib/permit2/
forge-gas-snapshot/=node_modules/forge-gas-snapshot/src/
Expand Down
47 changes: 22 additions & 25 deletions packages/foundry/script/01_DeployConstantSum.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { IRateProvider } from "@balancer-labs/v3-interfaces/contracts/vault/IRat
import { InputHelpers } from "@balancer-labs/v3-solidity-utils/contracts/helpers/InputHelpers.sol";
import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";

import { PoolHelpers, PoolRegistrationConfig, PoolInitializationConfig } from "./PoolHelpers.sol";
import { PoolHelpers, CustomPoolConfig, InitializationConfig } from "./PoolHelpers.sol";
import { ScaffoldHelpers, console } from "./ScaffoldHelpers.sol";
import { ConstantSumFactory } from "../contracts/pools/ConstantSumFactory.sol";

Expand All @@ -23,8 +23,8 @@ contract DeployConstantSum is PoolHelpers, ScaffoldHelpers {
function run(address token1, address token2) external {
// Set the deployment configurations
uint32 pauseWindowDuration = 365 days;
PoolRegistrationConfig memory regConfig = getPoolRegistrationConfig(token1, token2);
PoolInitializationConfig memory initConfig = getPoolInitializationConfig(token1, token2);
CustomPoolConfig memory poolConfig = getPoolConfig(token1, token2);
InitializationConfig memory initConfig = getInitializationConfig(token1, token2);

// Start creating the transactions
uint256 deployerPrivateKey = getDeployerPrivateKey();
Expand All @@ -36,15 +36,15 @@ contract DeployConstantSum is PoolHelpers, ScaffoldHelpers {

// Deploy a pool and register it with the vault
address pool = factory.create(
regConfig.name,
regConfig.symbol,
regConfig.salt,
regConfig.tokenConfig,
regConfig.swapFeePercentage,
regConfig.protocolFeeExempt,
regConfig.roleAccounts,
regConfig.poolHooksContract,
regConfig.liquidityManagement
poolConfig.name,
poolConfig.symbol,
poolConfig.salt,
poolConfig.tokenConfigs,
poolConfig.swapFeePercentage,
poolConfig.protocolFeeExempt,
poolConfig.roleAccounts,
poolConfig.poolHooksContract,
poolConfig.liquidityManagement
);
console.log("Constant Sum Pool deployed at: %s", pool);

Expand Down Expand Up @@ -73,25 +73,22 @@ contract DeployConstantSum is PoolHelpers, ScaffoldHelpers {
* For STANDARD tokens, the rate provider address must be 0, and paysYieldFees must be false.
* All WITH_RATE tokens need a rate provider, and may or may not be yield-bearing.
*/
function getPoolRegistrationConfig(
address token1,
address token2
) internal view returns (PoolRegistrationConfig memory config) {
function getPoolConfig(address token1, address token2) internal view returns (CustomPoolConfig memory config) {
string memory name = "Constant Sum Pool"; // name for the pool
string memory symbol = "CSP"; // symbol for the BPT
bytes32 salt = keccak256(abi.encode(block.number)); // salt for the pool deployment via factory
uint256 swapFeePercentage = 0.001e18; // 0.1%
uint256 swapFeePercentage = 0.01e18; // 1%
bool protocolFeeExempt = true;
address poolHooksContract = address(0); // zero address if no hooks contract is needed

TokenConfig[] memory tokenConfig = new TokenConfig[](2); // An array of descriptors for the tokens the pool will manage.
tokenConfig[0] = TokenConfig({ // Make sure to have proper token order (alphanumeric)
TokenConfig[] memory tokenConfigs = new TokenConfig[](2); // An array of descriptors for the tokens the pool will manage.
tokenConfigs[0] = TokenConfig({ // Make sure to have proper token order (alphanumeric)
token: IERC20(token1),
tokenType: TokenType.STANDARD, // STANDARD or WITH_RATE
rateProvider: IRateProvider(address(0)), // The rate provider for a token (see further documentation above)
paysYieldFees: false // Flag indicating whether yield fees should be charged on this token
});
tokenConfig[1] = TokenConfig({ // Make sure to have proper token order (alphanumeric)
tokenConfigs[1] = TokenConfig({ // Make sure to have proper token order (alphanumeric)
token: IERC20(token2),
tokenType: TokenType.STANDARD, // STANDARD or WITH_RATE
rateProvider: IRateProvider(address(0)), // The rate provider for a token (see further documentation above)
Expand All @@ -110,11 +107,11 @@ contract DeployConstantSum is PoolHelpers, ScaffoldHelpers {
enableDonation: false
});

config = PoolRegistrationConfig({
config = CustomPoolConfig({
name: name,
symbol: symbol,
salt: salt,
tokenConfig: sortTokenConfig(tokenConfig),
tokenConfigs: sortTokenConfig(tokenConfigs),
swapFeePercentage: swapFeePercentage,
protocolFeeExempt: protocolFeeExempt,
roleAccounts: roleAccounts,
Expand All @@ -127,10 +124,10 @@ contract DeployConstantSum is PoolHelpers, ScaffoldHelpers {
* @dev Set the pool initialization configurations here
* @notice this is where the amounts of tokens to be initially added to the pool are set
*/
function getPoolInitializationConfig(
function getInitializationConfig(
address token1,
address token2
) internal pure returns (PoolInitializationConfig memory config) {
) internal pure returns (InitializationConfig memory config) {
IERC20[] memory tokens = new IERC20[](2); // Array of tokens to be used in the pool
tokens[0] = IERC20(token1);
tokens[1] = IERC20(token2);
Expand All @@ -141,7 +138,7 @@ contract DeployConstantSum is PoolHelpers, ScaffoldHelpers {
bool wethIsEth = false; // If true, incoming ETH will be wrapped to WETH; otherwise the Vault will pull WETH tokens
bytes memory userData = bytes(""); // Additional (optional) data required for adding initial liquidity

config = PoolInitializationConfig({
config = InitializationConfig({
tokens: InputHelpers.sortTokens(tokens),
exactAmountsIn: exactAmountsIn,
minBptAmountOut: minBptAmountOut,
Expand Down
45 changes: 21 additions & 24 deletions packages/foundry/script/02_DeployConstantProduct.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { IRateProvider } from "@balancer-labs/v3-interfaces/contracts/vault/IRat
import { InputHelpers } from "@balancer-labs/v3-solidity-utils/contracts/helpers/InputHelpers.sol";
import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol";

import { PoolHelpers, PoolRegistrationConfig, PoolInitializationConfig } from "./PoolHelpers.sol";
import { PoolHelpers, CustomPoolConfig, InitializationConfig } from "./PoolHelpers.sol";
import { ScaffoldHelpers, console } from "./ScaffoldHelpers.sol";
import { VeBALFeeDiscountHook } from "../contracts/hooks/VeBALFeeDiscountHook.sol";
import { ConstantProductFactory } from "../contracts/pools/ConstantProductFactory.sol";
Expand All @@ -25,8 +25,8 @@ contract DeployConstantProduct is PoolHelpers, ScaffoldHelpers {
function run(address token1, address token2, address veBAL) external {
// Set the deployment configurations
uint32 pauseWindowDuration = 365 days;
PoolRegistrationConfig memory regConfig = getPoolRegistrationConfig(token1, token2);
PoolInitializationConfig memory initConfig = getPoolInitializationConfig(token1, token2);
CustomPoolConfig memory poolConfig = getPoolConfig(token1, token2);
InitializationConfig memory initConfig = getInitializationConfig(token1, token2);

// Start creating the transactions
uint256 deployerPrivateKey = getDeployerPrivateKey();
Expand All @@ -47,15 +47,15 @@ contract DeployConstantProduct is PoolHelpers, ScaffoldHelpers {

// Deploy a pool and register it with the vault
address pool = factory.create(
regConfig.name,
regConfig.symbol,
regConfig.salt,
regConfig.tokenConfig,
regConfig.swapFeePercentage,
regConfig.protocolFeeExempt,
regConfig.roleAccounts,
poolConfig.name,
poolConfig.symbol,
poolConfig.salt,
poolConfig.tokenConfigs,
poolConfig.swapFeePercentage,
poolConfig.protocolFeeExempt,
poolConfig.roleAccounts,
address(poolHooksContract),
regConfig.liquidityManagement
poolConfig.liquidityManagement
);
console.log("Constant Product Pool deployed at: %s", pool);

Expand Down Expand Up @@ -84,25 +84,22 @@ contract DeployConstantProduct is PoolHelpers, ScaffoldHelpers {
* For STANDARD tokens, the rate provider address must be 0, and paysYieldFees must be false.
* All WITH_RATE tokens need a rate provider, and may or may not be yield-bearing.
*/
function getPoolRegistrationConfig(
address token1,
address token2
) internal view returns (PoolRegistrationConfig memory config) {
function getPoolConfig(address token1, address token2) internal view returns (CustomPoolConfig memory config) {
string memory name = "Constant Product Pool"; // name for the pool
string memory symbol = "CPP"; // symbol for the BPT
bytes32 salt = keccak256(abi.encode(block.number)); // salt for the pool deployment via factory
uint256 swapFeePercentage = 0.05e18; // 5%
uint256 swapFeePercentage = 0.02e18; // 2%
bool protocolFeeExempt = false;
address poolHooksContract = address(0); // zero address if no hooks contract is needed

TokenConfig[] memory tokenConfig = new TokenConfig[](2); // An array of descriptors for the tokens the pool will manage
tokenConfig[0] = TokenConfig({ // Make sure to have proper token order (alphanumeric)
TokenConfig[] memory tokenConfigs = new TokenConfig[](2); // An array of descriptors for the tokens the pool will manage
tokenConfigs[0] = TokenConfig({ // Make sure to have proper token order (alphanumeric)
token: IERC20(token1),
tokenType: TokenType.STANDARD, // STANDARD or WITH_RATE
rateProvider: IRateProvider(address(0)), // The rate provider for a token (see further documentation above)
paysYieldFees: false // Flag indicating whether yield fees should be charged on this token
});
tokenConfig[1] = TokenConfig({ // Make sure to have proper token order (alphanumeric)
tokenConfigs[1] = TokenConfig({ // Make sure to have proper token order (alphanumeric)
token: IERC20(token2),
tokenType: TokenType.STANDARD, // STANDARD or WITH_RATE
rateProvider: IRateProvider(address(0)), // The rate provider for a token (see further documentation above)
Expand All @@ -121,11 +118,11 @@ contract DeployConstantProduct is PoolHelpers, ScaffoldHelpers {
enableDonation: false
});

config = PoolRegistrationConfig({
config = CustomPoolConfig({
name: name,
symbol: symbol,
salt: salt,
tokenConfig: sortTokenConfig(tokenConfig),
tokenConfigs: sortTokenConfig(tokenConfigs),
swapFeePercentage: swapFeePercentage,
protocolFeeExempt: protocolFeeExempt,
roleAccounts: roleAccounts,
Expand All @@ -138,10 +135,10 @@ contract DeployConstantProduct is PoolHelpers, ScaffoldHelpers {
* @dev Set the pool initialization configurations here
* @notice This is where the amounts of tokens to seed the pool with initial liquidity are set
*/
function getPoolInitializationConfig(
function getInitializationConfig(
address token1,
address token2
) internal pure returns (PoolInitializationConfig memory config) {
) internal pure returns (InitializationConfig memory config) {
IERC20[] memory tokens = new IERC20[](2); // Array of tokens to be used in the pool
tokens[0] = IERC20(token1);
tokens[1] = IERC20(token2);
Expand All @@ -152,7 +149,7 @@ contract DeployConstantProduct is PoolHelpers, ScaffoldHelpers {
bool wethIsEth = false; // If true, incoming ETH will be wrapped to WETH; otherwise the Vault will pull WETH tokens
bytes memory userData = bytes(""); // Additional (optional) data required for adding initial liquidity

config = PoolInitializationConfig({
config = InitializationConfig({
tokens: InputHelpers.sortTokens(tokens),
exactAmountsIn: exactAmountsIn,
minBptAmountOut: minBptAmountOut,
Expand Down
Loading

0 comments on commit a5fbeee

Please sign in to comment.