From 8ade135c9cf284957e9895d36c235f1e2cf0e4e7 Mon Sep 17 00:00:00 2001 From: Jeff Bennett Date: Sat, 17 Aug 2024 16:22:40 -0400 Subject: [PATCH 01/24] checkpoint --- .../vault/IProtocolFeeController.sol | 7 + pkg/vault/contracts/ProtocolFeeController.sol | 7 + .../ProtocolFeePercentagesProvider.sol | 179 ++++++++++++++++++ 3 files changed, 193 insertions(+) create mode 100644 pkg/vault/contracts/ProtocolFeePercentagesProvider.sol diff --git a/pkg/interfaces/contracts/vault/IProtocolFeeController.sol b/pkg/interfaces/contracts/vault/IProtocolFeeController.sol index 152811b89..8e31b055c 100644 --- a/pkg/interfaces/contracts/vault/IProtocolFeeController.sol +++ b/pkg/interfaces/contracts/vault/IProtocolFeeController.sol @@ -94,6 +94,13 @@ interface IProtocolFeeController { /// @dev Returns the main Vault address. function vault() external view returns (IVault); + /** + * @dev Return the maximum swap and yield protocol fee percentages. + * @return maxProtocolSwapFeePercentage The maximum protocol swap fee percentage + * @return maxProtocolYieldFeePercentage The maximum protocol yield fee percentage + */ + function getMaximumProtocolFeePercentages() external pure returns (uint256, uint256); + /// @dev Collects aggregate fees from the Vault for a given pool. function collectAggregateFees(address pool) external; diff --git a/pkg/vault/contracts/ProtocolFeeController.sol b/pkg/vault/contracts/ProtocolFeeController.sol index 471095f0d..c92e2b150 100644 --- a/pkg/vault/contracts/ProtocolFeeController.sol +++ b/pkg/vault/contracts/ProtocolFeeController.sol @@ -82,6 +82,8 @@ contract ProtocolFeeController is bool isOverride; } + // Note that the `ProtocolFeePercentagesProvider` assumes the maximum fee bounds are constant. + // Maximum protocol swap fee percentage. FixedPoint.ONE corresponds to a 100% fee. uint256 internal constant _MAX_PROTOCOL_SWAP_FEE_PERCENTAGE = 50e16; // 50% @@ -161,6 +163,11 @@ contract ProtocolFeeController is return _vault; } + /// @inheritdoc IProtocolFeeController + function getMaximumProtocolFeePercentages() external pure returns (uint256, uint256) { + return (_MAX_PROTOCOL_SWAP_FEE_PERCENTAGE, _MAX_PROTOCOL_YIELD_FEE_PERCENTAGE); + } + /// @inheritdoc IProtocolFeeController function collectAggregateFees(address pool) public { _vault.unlock(abi.encodeWithSelector(ProtocolFeeController.collectAggregateFeesHook.selector, pool)); diff --git a/pkg/vault/contracts/ProtocolFeePercentagesProvider.sol b/pkg/vault/contracts/ProtocolFeePercentagesProvider.sol new file mode 100644 index 000000000..3ea17cfb6 --- /dev/null +++ b/pkg/vault/contracts/ProtocolFeePercentagesProvider.sol @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +pragma solidity ^0.8.24; + +import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; + +import { IProtocolFeeController } from "@balancer-labs/v3-interfaces/contracts/vault/IProtocolFeeController.sol"; +import { IBasePoolFactory } from "@balancer-labs/v3-interfaces/contracts/vault/IBasePoolFactory.sol"; +import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol"; + +import { + SingletonAuthentication +} from "@balancer-labs/v3-solidity-utils/contracts/helpers/SingletonAuthentication.sol"; +import { EnumerableSet } from "@balancer-labs/v3-solidity-utils/contracts/openzeppelin/EnumerableSet.sol"; + +contract ProtocolFeePercentagesProvider is SingletonAuthentication { + using EnumerableSet for EnumerableSet.AddressSet; + using SafeCast for uint256; + + /** + * @dev Data structure to store default protocol fees by factory. Fee percentages are 18-decimal floating point + * numbers, so we know they fit in 64 bits, allowing the fees to be stored in a single slot. + * + * @param protocolSwapFee The protocol swap fee + * @param protocolYieldFee The protocol yield fee + * @param isFactoryRegistered Flag indicating fees have been set (allows zero values) + */ + struct FactoryProtocolFees { + uint64 protocolSwapFeePercentage; + uint64 protocolYieldFeePercentage; + bool isFactoryRegistered; + } + + IProtocolFeeController private immutable _protocolFeeController; + + uint256 private immutable _maxProtocolSwapFeePercentage; + uint256 private immutable _maxProtocolYieldFeePercentage; + + // Factory address => FactoryProtocolFees + mapping(IBasePoolFactory => FactoryProtocolFees) private _factoryDefaultFeePercentages; + + // Iterable list of the factories with default fees set. + EnumerableSet.AddressSet private _factories; + + /// @notice The protocol fee controller was configured with an incorrect Vault address. + error WrongProtocolFeeControllerDeployment(); + + /** + * @notice `setDefaultProtocolFees` has not been called for this factory address. + * @param factory The unregistered factory address + */ + error FactoryNotRegistered(address factory); + + /** + * @notice The factory address provided is not a valid IBasePoolFactory. + * @dev This means it does not implement or responds incorrectly to `isPoolFromFactory`. + * @param factory The address of the invalid factory + */ + error InvalidFactory(address factory); + + /** + * @notice The given pool is not from any of the registered factories. + * @param pool The address of the pool + */ + error PoolNotFromRegisteredFactory(address pool); + + constructor(IVault vault, IProtocolFeeController protocolFeeController) SingletonAuthentication(vault) { + _protocolFeeController = protocolFeeController; + + if (protocolFeeController.vault() != vault) { + revert WrongProtocolFeeControllerDeployment(); + } + + // These values are constant in the `ProtocolFeeController`. + (_maxProtocolSwapFeePercentage, _maxProtocolYieldFeePercentage) = protocolFeeController + .getMaximumProtocolFeePercentages(); + } + + function getProtocolFeeController() external view returns (IProtocolFeeController) { + return _protocolFeeController; + } + + function getFactorySpecificProtocolFees( + address factory + ) external view returns (uint256 protocolSwapFeePercentage, uint256 protocolYieldFeePercentage) { + FactoryProtocolFees memory factoryFees = _factoryDefaultFeePercentages[IBasePoolFactory(factory)]; + + if (factoryFees.isFactoryRegistered == false) { + revert FactoryNotRegistered(factory); + } + + protocolSwapFeePercentage = factoryFees.protocolSwapFeePercentage; + protocolYieldFeePercentage = factoryFees.protocolYieldFeePercentage; + } + + function setProtocolFeesForPools(address[] memory pools) external { + uint256 numPools = pools.length; + + if (numPools > 0) { + address currentPool = pools[0]; + + (IBasePoolFactory currentFactory, uint256 protocolSwapFee, uint256 protocolYieldFee) = _findFactoryForPool( + currentPool + ); + + _setPoolProtocolFees(currentPool, protocolSwapFee, protocolYieldFee); + + // Common usage will be to call this for pools from the same factory. Or at a minimum, the pools will be + // grouped by factory. Check to see whether subsequent pools are from the `currentFactory`, to make as few + // expensive calls to `_findFactoryForPool` as possible. You can call this with an unordered set of pools, + // but it will be more expensive. + for (uint256 i = 1; i < numPools; ++i) { + currentPool = pools[i]; + + if (currentFactory.isPoolFromFactory(currentPool) == false) { + (currentFactory, protocolSwapFee, protocolYieldFee) = _findFactoryForPool(currentPool); + } + + _setPoolProtocolFees(currentPool, protocolSwapFee, protocolYieldFee); + } + } + } + + function setFactorySpecificProtocolFees( + address factory, + uint256 protocolSwapFeePercentage, + uint256 protocolYieldFeePercentage + ) external authenticate { + // Validate the fee percentages; don't store values that the `ProtocolFeeCollector` will reject. + if (protocolSwapFeePercentage > _maxProtocolSwapFeePercentage) { + revert IProtocolFeeController.ProtocolSwapFeePercentageTooHigh(); + } + + if (protocolYieldFeePercentage > _maxProtocolYieldFeePercentage) { + revert IProtocolFeeController.ProtocolYieldFeePercentageTooHigh(); + } + + // Best effort check that the factory is an IBasePoolFactory. + bool poolFromFactory = IBasePoolFactory(factory).isPoolFromFactory(address(0)); + if (poolFromFactory) { + revert InvalidFactory(factory); + } + + // Store the default fee percentages, and mark the factory as registered. + _factoryDefaultFeePercentages[IBasePoolFactory(factory)] = FactoryProtocolFees({ + protocolSwapFeePercentage: protocolSwapFeePercentage.toUint64(), + protocolYieldFeePercentage: protocolYieldFeePercentage.toUint64(), + isFactoryRegistered: true + }); + + // Add to iterable set. Ignore return value; it's possible to call this multiple times on a factory to update + // the fee percentages. + _factories.add(factory); + } + + function _findFactoryForPool(address pool) private view returns (IBasePoolFactory, uint256, uint256) { + uint256 numFactories = _factories.length(); + IBasePoolFactory basePoolFactory; + + for (uint256 i = 0; i < numFactories; ++i) { + basePoolFactory = IBasePoolFactory(_factories.unchecked_at(i)); + + if (basePoolFactory.isPoolFromFactory(pool)) { + FactoryProtocolFees memory fees = _factoryDefaultFeePercentages[basePoolFactory]; + + return (basePoolFactory, fees.protocolSwapFeePercentage, fees.protocolYieldFeePercentage); + } + } + + revert PoolNotFromRegisteredFactory(pool); + } + + // These are permissioned functions on `ProtocolFeeController`, so governance will need to allow this contract + // to call `setProtocolSwapFeePercentage` and `setProtocolYieldFeePercentage`. + function _setPoolProtocolFees(address pool, uint256 protocolSwapFee, uint256 protocolYieldFee) private { + _protocolFeeController.setProtocolSwapFeePercentage(pool, protocolSwapFee); + _protocolFeeController.setProtocolYieldFeePercentage(pool, protocolYieldFee); + } +} From d354182aa91fcaa414c764197f15ca0fed9ca8d8 Mon Sep 17 00:00:00 2001 From: Jeff Bennett Date: Sat, 17 Aug 2024 17:22:28 -0400 Subject: [PATCH 02/24] checkpoint - most general --- .../ProtocolFeePercentagesProvider.sol | 48 ++++++++++++++----- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/pkg/vault/contracts/ProtocolFeePercentagesProvider.sol b/pkg/vault/contracts/ProtocolFeePercentagesProvider.sol index 3ea17cfb6..150cdd999 100644 --- a/pkg/vault/contracts/ProtocolFeePercentagesProvider.sol +++ b/pkg/vault/contracts/ProtocolFeePercentagesProvider.sol @@ -83,27 +83,43 @@ contract ProtocolFeePercentagesProvider is SingletonAuthentication { function getFactorySpecificProtocolFees( address factory ) external view returns (uint256 protocolSwapFeePercentage, uint256 protocolYieldFeePercentage) { - FactoryProtocolFees memory factoryFees = _factoryDefaultFeePercentages[IBasePoolFactory(factory)]; - - if (factoryFees.isFactoryRegistered == false) { - revert FactoryNotRegistered(factory); - } + FactoryProtocolFees memory factoryFees = _getValidatedProtocolFees(factory); protocolSwapFeePercentage = factoryFees.protocolSwapFeePercentage; protocolYieldFeePercentage = factoryFees.protocolYieldFeePercentage; } + function setProtocolFeesForPools(address factory, address[] memory pools) external { + FactoryProtocolFees memory factoryFees = _getValidatedProtocolFees(factory); + + for (uint256 i = 0; i < pools.length; ++i) { + address currentPool = pools[i]; + + if (IBasePoolFactory(factory).isPoolFromFactory(currentPool) == false) { + revert PoolNotFromRegisteredFactory(currentPool); + } + + _setPoolProtocolFees( + currentPool, + factoryFees.protocolSwapFeePercentage, + factoryFees.protocolYieldFeePercentage + ); + } + } + function setProtocolFeesForPools(address[] memory pools) external { uint256 numPools = pools.length; if (numPools > 0) { address currentPool = pools[0]; - (IBasePoolFactory currentFactory, uint256 protocolSwapFee, uint256 protocolYieldFee) = _findFactoryForPool( - currentPool - ); + ( + IBasePoolFactory currentFactory, + uint256 protocolSwapFeePercentage, + uint256 protocolYieldFeePercentage + ) = _findFactoryForPool(currentPool); - _setPoolProtocolFees(currentPool, protocolSwapFee, protocolYieldFee); + _setPoolProtocolFees(currentPool, protocolSwapFeePercentage, protocolYieldFeePercentage); // Common usage will be to call this for pools from the same factory. Or at a minimum, the pools will be // grouped by factory. Check to see whether subsequent pools are from the `currentFactory`, to make as few @@ -113,10 +129,12 @@ contract ProtocolFeePercentagesProvider is SingletonAuthentication { currentPool = pools[i]; if (currentFactory.isPoolFromFactory(currentPool) == false) { - (currentFactory, protocolSwapFee, protocolYieldFee) = _findFactoryForPool(currentPool); + (currentFactory, protocolSwapFeePercentage, protocolYieldFeePercentage) = _findFactoryForPool( + currentPool + ); } - _setPoolProtocolFees(currentPool, protocolSwapFee, protocolYieldFee); + _setPoolProtocolFees(currentPool, protocolSwapFeePercentage, protocolYieldFeePercentage); } } } @@ -170,6 +188,14 @@ contract ProtocolFeePercentagesProvider is SingletonAuthentication { revert PoolNotFromRegisteredFactory(pool); } + function _getValidatedProtocolFees(address factory) private view returns (FactoryProtocolFees memory factoryFees) { + factoryFees = _factoryDefaultFeePercentages[IBasePoolFactory(factory)]; + + if (factoryFees.isFactoryRegistered == false) { + revert FactoryNotRegistered(factory); + } + } + // These are permissioned functions on `ProtocolFeeController`, so governance will need to allow this contract // to call `setProtocolSwapFeePercentage` and `setProtocolYieldFeePercentage`. function _setPoolProtocolFees(address pool, uint256 protocolSwapFee, uint256 protocolYieldFee) private { From 963b1584aadf1346814c5e4f9c275cbcd01fc03b Mon Sep 17 00:00:00 2001 From: Jeff Bennett Date: Sun, 18 Aug 2024 11:21:36 -0400 Subject: [PATCH 03/24] refactor: add interface, and simplify (remove completely generic pool list support) --- .../vault/IProtocolFeePercentagesProvider.sol | 66 +++++++++ .../ProtocolFeePercentagesProvider.sol | 126 ++++-------------- 2 files changed, 93 insertions(+), 99 deletions(-) create mode 100644 pkg/interfaces/contracts/vault/IProtocolFeePercentagesProvider.sol diff --git a/pkg/interfaces/contracts/vault/IProtocolFeePercentagesProvider.sol b/pkg/interfaces/contracts/vault/IProtocolFeePercentagesProvider.sol new file mode 100644 index 000000000..3ee3323d0 --- /dev/null +++ b/pkg/interfaces/contracts/vault/IProtocolFeePercentagesProvider.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +pragma solidity ^0.8.24; + +import { IProtocolFeeController } from "./IProtocolFeeController.sol"; + +interface IProtocolFeePercentagesProvider { + /** + * @notice `setDefaultProtocolFees` has not been called for this factory address. + * @param factory The unregistered factory address + */ + error FactoryNotRegistered(address factory); + + /** + * @notice The factory address provided is not a valid IBasePoolFactory. + * @dev This means it does not implement or responds incorrectly to `isPoolFromFactory`. + * @param factory The address of the invalid factory + */ + error InvalidFactory(address factory); + + /** + * @notice The given pool is not from the expected factory. + * @param pool The address of the pool + * @param factory The address of the factory + */ + error PoolNotFromFactory(address pool, address factory); + + /** + * @notice Get the address of the `ProtocolFeeController` used to set fees. + * @return protocolFeeController The address of the fee controller + */ + function getProtocolFeeController() external view returns (IProtocolFeeController); + + /** + * @notice Query the protocol fee percentages for a given factory. + * @param factory The address of the factory + * @return protocolSwapFeePercentage The protocol swap fee percentage set for that factory + * @return protocolYieldFeePercentage The protocol yield fee percentage set for that factory + */ + function getFactorySpecificProtocolFeePercentages( + address factory + ) external view returns (uint256 protocolSwapFeePercentage, uint256 protocolYieldFeePercentage); + + /** + * @notice Assign intended protocol fee percentages for a given factory. + * @dev This is a permissioned call. After the fee percentages have been set, anyone can call + * `setProtocolFeePercentagesForPools` to update the fees on a set of pools from that factory. + * + * @param factory The address of the factory + * @param protocolSwapFeePercentage The new protocol swap fee percentage + * @param protocolYieldFeePercentage The new protocol yield fee percentage + */ + function setFactorySpecificProtocolFeePercentages( + address factory, + uint256 protocolSwapFeePercentage, + uint256 protocolYieldFeePercentage + ) external; + + /** + * @notice Update the protocol fees for a set of pools from a given factory. + * @dev This call is permissionless. Anyone can update the fees, once they're set by governance. + * @param factory The address of the factory + * @param pools The pools whose fees will be set according to `setFactorySpecificProtocolFeePercentages` + */ + function setProtocolFeePercentagesForPools(address factory, address[] memory pools) external; +} diff --git a/pkg/vault/contracts/ProtocolFeePercentagesProvider.sol b/pkg/vault/contracts/ProtocolFeePercentagesProvider.sol index 150cdd999..d4759631e 100644 --- a/pkg/vault/contracts/ProtocolFeePercentagesProvider.sol +++ b/pkg/vault/contracts/ProtocolFeePercentagesProvider.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.24; import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import { IProtocolFeePercentagesProvider } from "@balancer-labs/v3-interfaces/contracts/vault/IProtocolFeePercentagesProvider.sol"; import { IProtocolFeeController } from "@balancer-labs/v3-interfaces/contracts/vault/IProtocolFeeController.sol"; import { IBasePoolFactory } from "@balancer-labs/v3-interfaces/contracts/vault/IBasePoolFactory.sol"; import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol"; @@ -11,12 +12,13 @@ import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol" import { SingletonAuthentication } from "@balancer-labs/v3-solidity-utils/contracts/helpers/SingletonAuthentication.sol"; -import { EnumerableSet } from "@balancer-labs/v3-solidity-utils/contracts/openzeppelin/EnumerableSet.sol"; -contract ProtocolFeePercentagesProvider is SingletonAuthentication { - using EnumerableSet for EnumerableSet.AddressSet; +contract ProtocolFeePercentagesProvider is IProtocolFeePercentagesProvider, SingletonAuthentication { using SafeCast for uint256; + /// @notice The protocol fee controller was configured with an incorrect Vault address. + error WrongProtocolFeeControllerDeployment(); + /** * @dev Data structure to store default protocol fees by factory. Fee percentages are 18-decimal floating point * numbers, so we know they fit in 64 bits, allowing the fees to be stored in a single slot. @@ -39,31 +41,6 @@ contract ProtocolFeePercentagesProvider is SingletonAuthentication { // Factory address => FactoryProtocolFees mapping(IBasePoolFactory => FactoryProtocolFees) private _factoryDefaultFeePercentages; - // Iterable list of the factories with default fees set. - EnumerableSet.AddressSet private _factories; - - /// @notice The protocol fee controller was configured with an incorrect Vault address. - error WrongProtocolFeeControllerDeployment(); - - /** - * @notice `setDefaultProtocolFees` has not been called for this factory address. - * @param factory The unregistered factory address - */ - error FactoryNotRegistered(address factory); - - /** - * @notice The factory address provided is not a valid IBasePoolFactory. - * @dev This means it does not implement or responds incorrectly to `isPoolFromFactory`. - * @param factory The address of the invalid factory - */ - error InvalidFactory(address factory); - - /** - * @notice The given pool is not from any of the registered factories. - * @param pool The address of the pool - */ - error PoolNotFromRegisteredFactory(address pool); - constructor(IVault vault, IProtocolFeeController protocolFeeController) SingletonAuthentication(vault) { _protocolFeeController = protocolFeeController; @@ -76,11 +53,13 @@ contract ProtocolFeePercentagesProvider is SingletonAuthentication { .getMaximumProtocolFeePercentages(); } + /// @inheritdoc IProtocolFeePercentagesProvider function getProtocolFeeController() external view returns (IProtocolFeeController) { return _protocolFeeController; } - function getFactorySpecificProtocolFees( + /// @inheritdoc IProtocolFeePercentagesProvider + function getFactorySpecificProtocolFeePercentages( address factory ) external view returns (uint256 protocolSwapFeePercentage, uint256 protocolYieldFeePercentage) { FactoryProtocolFees memory factoryFees = _getValidatedProtocolFees(factory); @@ -89,57 +68,8 @@ contract ProtocolFeePercentagesProvider is SingletonAuthentication { protocolYieldFeePercentage = factoryFees.protocolYieldFeePercentage; } - function setProtocolFeesForPools(address factory, address[] memory pools) external { - FactoryProtocolFees memory factoryFees = _getValidatedProtocolFees(factory); - - for (uint256 i = 0; i < pools.length; ++i) { - address currentPool = pools[i]; - - if (IBasePoolFactory(factory).isPoolFromFactory(currentPool) == false) { - revert PoolNotFromRegisteredFactory(currentPool); - } - - _setPoolProtocolFees( - currentPool, - factoryFees.protocolSwapFeePercentage, - factoryFees.protocolYieldFeePercentage - ); - } - } - - function setProtocolFeesForPools(address[] memory pools) external { - uint256 numPools = pools.length; - - if (numPools > 0) { - address currentPool = pools[0]; - - ( - IBasePoolFactory currentFactory, - uint256 protocolSwapFeePercentage, - uint256 protocolYieldFeePercentage - ) = _findFactoryForPool(currentPool); - - _setPoolProtocolFees(currentPool, protocolSwapFeePercentage, protocolYieldFeePercentage); - - // Common usage will be to call this for pools from the same factory. Or at a minimum, the pools will be - // grouped by factory. Check to see whether subsequent pools are from the `currentFactory`, to make as few - // expensive calls to `_findFactoryForPool` as possible. You can call this with an unordered set of pools, - // but it will be more expensive. - for (uint256 i = 1; i < numPools; ++i) { - currentPool = pools[i]; - - if (currentFactory.isPoolFromFactory(currentPool) == false) { - (currentFactory, protocolSwapFeePercentage, protocolYieldFeePercentage) = _findFactoryForPool( - currentPool - ); - } - - _setPoolProtocolFees(currentPool, protocolSwapFeePercentage, protocolYieldFeePercentage); - } - } - } - - function setFactorySpecificProtocolFees( + /// @inheritdoc IProtocolFeePercentagesProvider + function setFactorySpecificProtocolFeePercentages( address factory, uint256 protocolSwapFeePercentage, uint256 protocolYieldFeePercentage @@ -153,7 +83,7 @@ contract ProtocolFeePercentagesProvider is SingletonAuthentication { revert IProtocolFeeController.ProtocolYieldFeePercentageTooHigh(); } - // Best effort check that the factory is an IBasePoolFactory. + // Best effort check that `factory` is the address of an IBasePoolFactory. bool poolFromFactory = IBasePoolFactory(factory).isPoolFromFactory(address(0)); if (poolFromFactory) { revert InvalidFactory(factory); @@ -165,27 +95,25 @@ contract ProtocolFeePercentagesProvider is SingletonAuthentication { protocolYieldFeePercentage: protocolYieldFeePercentage.toUint64(), isFactoryRegistered: true }); - - // Add to iterable set. Ignore return value; it's possible to call this multiple times on a factory to update - // the fee percentages. - _factories.add(factory); } - function _findFactoryForPool(address pool) private view returns (IBasePoolFactory, uint256, uint256) { - uint256 numFactories = _factories.length(); - IBasePoolFactory basePoolFactory; - - for (uint256 i = 0; i < numFactories; ++i) { - basePoolFactory = IBasePoolFactory(_factories.unchecked_at(i)); + /// @inheritdoc IProtocolFeePercentagesProvider + function setProtocolFeePercentagesForPools(address factory, address[] memory pools) external { + FactoryProtocolFees memory factoryFees = _getValidatedProtocolFees(factory); - if (basePoolFactory.isPoolFromFactory(pool)) { - FactoryProtocolFees memory fees = _factoryDefaultFeePercentages[basePoolFactory]; + for (uint256 i = 0; i < pools.length; ++i) { + address currentPool = pools[i]; - return (basePoolFactory, fees.protocolSwapFeePercentage, fees.protocolYieldFeePercentage); + if (IBasePoolFactory(factory).isPoolFromFactory(currentPool) == false) { + revert PoolNotFromFactory(currentPool, factory); } - } - revert PoolNotFromRegisteredFactory(pool); + _setPoolProtocolFees( + currentPool, + factoryFees.protocolSwapFeePercentage, + factoryFees.protocolYieldFeePercentage + ); + } } function _getValidatedProtocolFees(address factory) private view returns (FactoryProtocolFees memory factoryFees) { @@ -198,8 +126,8 @@ contract ProtocolFeePercentagesProvider is SingletonAuthentication { // These are permissioned functions on `ProtocolFeeController`, so governance will need to allow this contract // to call `setProtocolSwapFeePercentage` and `setProtocolYieldFeePercentage`. - function _setPoolProtocolFees(address pool, uint256 protocolSwapFee, uint256 protocolYieldFee) private { - _protocolFeeController.setProtocolSwapFeePercentage(pool, protocolSwapFee); - _protocolFeeController.setProtocolYieldFeePercentage(pool, protocolYieldFee); + function _setPoolProtocolFees(address pool, uint256 protocolSwapFeePercentage, uint256 protocolYieldFeePercentage) private { + _protocolFeeController.setProtocolSwapFeePercentage(pool, protocolSwapFeePercentage); + _protocolFeeController.setProtocolYieldFeePercentage(pool, protocolYieldFeePercentage); } } From 507dfb58f9ebd7f989dddf94732bd4020f21e744 Mon Sep 17 00:00:00 2001 From: Jeff Bennett Date: Sun, 18 Aug 2024 16:03:04 -0400 Subject: [PATCH 04/24] lint --- pkg/vault/contracts/ProtocolFeePercentagesProvider.sol | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pkg/vault/contracts/ProtocolFeePercentagesProvider.sol b/pkg/vault/contracts/ProtocolFeePercentagesProvider.sol index d4759631e..513b1c296 100644 --- a/pkg/vault/contracts/ProtocolFeePercentagesProvider.sol +++ b/pkg/vault/contracts/ProtocolFeePercentagesProvider.sol @@ -4,7 +4,9 @@ pragma solidity ^0.8.24; import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import { IProtocolFeePercentagesProvider } from "@balancer-labs/v3-interfaces/contracts/vault/IProtocolFeePercentagesProvider.sol"; +import { + IProtocolFeePercentagesProvider +} from "@balancer-labs/v3-interfaces/contracts/vault/IProtocolFeePercentagesProvider.sol"; import { IProtocolFeeController } from "@balancer-labs/v3-interfaces/contracts/vault/IProtocolFeeController.sol"; import { IBasePoolFactory } from "@balancer-labs/v3-interfaces/contracts/vault/IBasePoolFactory.sol"; import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol"; @@ -126,7 +128,11 @@ contract ProtocolFeePercentagesProvider is IProtocolFeePercentagesProvider, Sing // These are permissioned functions on `ProtocolFeeController`, so governance will need to allow this contract // to call `setProtocolSwapFeePercentage` and `setProtocolYieldFeePercentage`. - function _setPoolProtocolFees(address pool, uint256 protocolSwapFeePercentage, uint256 protocolYieldFeePercentage) private { + function _setPoolProtocolFees( + address pool, + uint256 protocolSwapFeePercentage, + uint256 protocolYieldFeePercentage + ) private { _protocolFeeController.setProtocolSwapFeePercentage(pool, protocolSwapFeePercentage); _protocolFeeController.setProtocolYieldFeePercentage(pool, protocolYieldFeePercentage); } From 68b65e2ae68c5c896a8d7f7fa47dfa88574d28d8 Mon Sep 17 00:00:00 2001 From: Jeff Bennett Date: Sun, 18 Aug 2024 16:03:24 -0400 Subject: [PATCH 05/24] test: add tests for new fee controller getter --- pkg/vault/contracts/test/PoolFactoryMock.sol | 4 ++++ pkg/vault/test/foundry/ProtocolFeeController.t.sol | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/pkg/vault/contracts/test/PoolFactoryMock.sol b/pkg/vault/contracts/test/PoolFactoryMock.sol index 3fd49aa23..44903cd64 100644 --- a/pkg/vault/contracts/test/PoolFactoryMock.sol +++ b/pkg/vault/contracts/test/PoolFactoryMock.sol @@ -169,6 +169,10 @@ contract PoolFactoryMock is IBasePoolFactory, SingletonAuthentication, FactoryWi ); } + function manualSetPoolFromFactory(address pool) external { + _isPoolFromFactory[pool] = true; + } + function _getDefaultLiquidityManagement() private pure returns (LiquidityManagement memory) { LiquidityManagement memory liquidityManagement; liquidityManagement.enableAddLiquidityCustom = true; diff --git a/pkg/vault/test/foundry/ProtocolFeeController.t.sol b/pkg/vault/test/foundry/ProtocolFeeController.t.sol index 0a8ddb9ea..de0acaebc 100644 --- a/pkg/vault/test/foundry/ProtocolFeeController.t.sol +++ b/pkg/vault/test/foundry/ProtocolFeeController.t.sol @@ -74,6 +74,14 @@ contract ProtocolFeeControllerTest is BaseVaultTest { assertEq(feeAmounts[1], 0, "Collected creator fee amount [1] is non-zero"); } + function testGetMaximumProtocolFeePercentages() public view { + (uint256 maxSwapFeePercentage, uint256 maxYieldFeePercentage) = feeController + .getMaximumProtocolFeePercentages(); + + assertEq(maxSwapFeePercentage, 50e16, "Wrong maximum swap fee percentage"); + assertEq(maxYieldFeePercentage, 50e16, "Wrong maximum yield fee percentage"); + } + function testSetGlobalProtocolSwapFeePercentageRange() public { authorizer.grantRole( feeControllerAuth.getActionId(IProtocolFeeController.setGlobalProtocolSwapFeePercentage.selector), From b2089b6196bc63379cc41fbf0f09e5466cdda191 Mon Sep 17 00:00:00 2001 From: Jeff Bennett Date: Sun, 18 Aug 2024 16:03:36 -0400 Subject: [PATCH 06/24] test: add percentages provider tests --- .../ProtocolFeePercentagesProvider.t.sol | 232 ++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 pkg/vault/test/foundry/ProtocolFeePercentagesProvider.t.sol diff --git a/pkg/vault/test/foundry/ProtocolFeePercentagesProvider.t.sol b/pkg/vault/test/foundry/ProtocolFeePercentagesProvider.t.sol new file mode 100644 index 000000000..0eb9e7c84 --- /dev/null +++ b/pkg/vault/test/foundry/ProtocolFeePercentagesProvider.t.sol @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +pragma solidity ^0.8.24; + +import "forge-std/Test.sol"; + +import { + IProtocolFeePercentagesProvider +} from "@balancer-labs/v3-interfaces/contracts/vault/IProtocolFeePercentagesProvider.sol"; +import { IAuthentication } from "@balancer-labs/v3-interfaces/contracts/solidity-utils/helpers/IAuthentication.sol"; +import { IProtocolFeeController } from "@balancer-labs/v3-interfaces/contracts/vault/IProtocolFeeController.sol"; +import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol"; +import { IPoolInfo } from "@balancer-labs/v3-interfaces/contracts/pool-utils/IPoolInfo.sol"; + +import { ProtocolFeePercentagesProvider } from "../../contracts/ProtocolFeePercentagesProvider.sol"; + +import { BaseVaultTest } from "./utils/BaseVaultTest.sol"; + +contract ProtocolFeePercentagesProviderTest is BaseVaultTest { + address internal constant INVALID_ADDRESS = address(0x1234); + + IProtocolFeePercentagesProvider internal percentagesProvider; + + IAuthentication internal percentagesProviderAuth; + IAuthentication internal feeControllerAuth; + + uint256 internal maxSwapFeePercentage; + uint256 internal maxYieldFeePercentage; + + address[] internal pools; + + function setUp() public override { + BaseVaultTest.setUp(); + + percentagesProvider = new ProtocolFeePercentagesProvider(vault, feeController); + + percentagesProviderAuth = IAuthentication(address(percentagesProvider)); + feeControllerAuth = IAuthentication(address(feeController)); + + (maxSwapFeePercentage, maxYieldFeePercentage) = feeController.getMaximumProtocolFeePercentages(); + + // Ensure we aren't comparing to 0. + require(maxSwapFeePercentage > 0, "Zero swap fee percentage"); + require(maxYieldFeePercentage > 0, "Zero yield fee percentage"); + + pools = new address[](1); + pools[0] = pool; + } + + function testInvalidConstruction() public { + vm.expectRevert(ProtocolFeePercentagesProvider.WrongProtocolFeeControllerDeployment.selector); + new ProtocolFeePercentagesProvider(IVault(INVALID_ADDRESS), feeController); + } + + function testGetProtocolFeeController() public view { + assertEq( + address(percentagesProvider.getProtocolFeeController()), + address(feeController), + "Wrong protocol fee controller" + ); + } + + function testGetFactorySpecificProtocolFeePercentagesUnregisteredFactory() public { + vm.expectRevert( + abi.encodeWithSelector(IProtocolFeePercentagesProvider.FactoryNotRegistered.selector, INVALID_ADDRESS) + ); + percentagesProvider.getFactorySpecificProtocolFeePercentages(INVALID_ADDRESS); + } + + function testSetFactorySpecificProtocolFeePercentageNoPermission() public { + vm.expectRevert(IAuthentication.SenderNotAllowed.selector); + percentagesProvider.setFactorySpecificProtocolFeePercentages( + address(factoryMock), + maxSwapFeePercentage, + maxYieldFeePercentage + ); + } + + function testFailSetFactorySpecificProtocolFeePercentageInvalidFactory() public { + _grantPermissions(); + + vm.prank(admin); + percentagesProvider.setFactorySpecificProtocolFeePercentages( + INVALID_ADDRESS, + maxSwapFeePercentage, + maxYieldFeePercentage + ); + } + + function testSetFactorySpecificProtocolFeePercentageBadFactory() public { + _grantPermissions(); + + // Cause `isPoolFromFactory` to return "true" for address(0). + factoryMock.manualSetPoolFromFactory(address(0)); + + vm.expectRevert( + abi.encodeWithSelector(IProtocolFeePercentagesProvider.InvalidFactory.selector, address(factoryMock)) + ); + vm.prank(admin); + percentagesProvider.setFactorySpecificProtocolFeePercentages( + address(factoryMock), + maxSwapFeePercentage, + maxYieldFeePercentage + ); + } + + function testSetFactorySpecificProtocolFeePercentageInvalidSwap() public { + _grantPermissions(); + + vm.expectRevert(IProtocolFeeController.ProtocolSwapFeePercentageTooHigh.selector); + vm.prank(admin); + percentagesProvider.setFactorySpecificProtocolFeePercentages( + address(factoryMock), + maxSwapFeePercentage + 1, + maxYieldFeePercentage + ); + } + + function testSetFactorySpecificProtocolFeePercentageInvalidYield() public { + _grantPermissions(); + + vm.expectRevert(IProtocolFeeController.ProtocolYieldFeePercentageTooHigh.selector); + vm.prank(admin); + percentagesProvider.setFactorySpecificProtocolFeePercentages( + address(factoryMock), + maxSwapFeePercentage, + maxYieldFeePercentage + 1 + ); + } + + function testSetFactorySpecificProtocolFeePercentage() public { + _grantPermissions(); + + // Ensure that they are different, so the test doesn't pass accidentally. + uint256 yieldFeePercentage = maxSwapFeePercentage / 2; + + vm.prank(admin); + percentagesProvider.setFactorySpecificProtocolFeePercentages( + address(factoryMock), + maxSwapFeePercentage, + yieldFeePercentage + ); + + (uint256 actualSwapFeePercentage, uint256 actualYieldFeePercentage) = percentagesProvider + .getFactorySpecificProtocolFeePercentages(address(factoryMock)); + assertEq(actualSwapFeePercentage, maxSwapFeePercentage, "Wrong factory swap fee percentage"); + assertEq(actualYieldFeePercentage, yieldFeePercentage, "Wrong factory swap fee percentage"); + } + + function testSetProtocolFeePercentagesForPoolsUnregisteredFactory() public { + _grantPermissions(); + + vm.expectRevert( + abi.encodeWithSelector(IProtocolFeePercentagesProvider.FactoryNotRegistered.selector, INVALID_ADDRESS) + ); + percentagesProvider.setProtocolFeePercentagesForPools(INVALID_ADDRESS, pools); + } + + function testSetProtocolFeePercentagesForPoolsUnknownPool() public { + _grantPermissions(); + + vm.prank(admin); + percentagesProvider.setFactorySpecificProtocolFeePercentages( + address(factoryMock), + maxSwapFeePercentage, + maxYieldFeePercentage + ); + + pools = new address[](2); + pools[0] = pool; + pools[1] = INVALID_ADDRESS; + + vm.expectRevert( + abi.encodeWithSelector( + IProtocolFeePercentagesProvider.PoolNotFromFactory.selector, + INVALID_ADDRESS, + address(factoryMock) + ) + ); + + percentagesProvider.setProtocolFeePercentagesForPools(address(factoryMock), pools); + } + + function testSetProtocolFeePercentagesForPools() public { + _grantPermissions(); + + // Use random odd values to ensure we're setting them. + uint256 expectedSwapFeePercentage = 5.28e16; + uint256 expectedYieldFeePercentage = 3.14e16; + + vm.prank(admin); + percentagesProvider.setFactorySpecificProtocolFeePercentages( + address(factoryMock), + expectedSwapFeePercentage, + expectedYieldFeePercentage + ); + + // These should be zero initially. Since there is no pool creator here, the aggregate fees = protocol fees. + (uint256 originalSwapFeePercentage, uint256 originalYieldFeePercentage) = IPoolInfo(pool) + .getAggregateFeePercentages(); + assertEq(originalSwapFeePercentage, 0, "Non-zero original swap fee percentage"); + assertEq(originalYieldFeePercentage, 0, "Non-zero original yield fee percentage"); + + // Permissionless call to set fee percentages by factory. + percentagesProvider.setProtocolFeePercentagesForPools(address(factoryMock), pools); + + (uint256 currentSwapFeePercentage, uint256 currentYieldFeePercentage) = IPoolInfo(pool) + .getAggregateFeePercentages(); + assertEq(currentSwapFeePercentage, expectedSwapFeePercentage, "Non-zero original swap fee percentage"); + assertEq(currentYieldFeePercentage, expectedYieldFeePercentage, "Non-zero original yield fee percentage"); + } + + function _grantPermissions() private { + // Allow calling `setFactorySpecificProtocolFeePercentages` on the provider. + authorizer.grantRole( + percentagesProviderAuth.getActionId( + IProtocolFeePercentagesProvider.setFactorySpecificProtocolFeePercentages.selector + ), + admin + ); + + // Allow the provider to call the underlying functions on the fee controller. + authorizer.grantRole( + feeControllerAuth.getActionId(IProtocolFeeController.setProtocolSwapFeePercentage.selector), + address(percentagesProvider) + ); + authorizer.grantRole( + feeControllerAuth.getActionId(IProtocolFeeController.setProtocolYieldFeePercentage.selector), + address(percentagesProvider) + ); + } +} From d384e0e0c6fe20bb9e314c837dafb5c9f60f9db3 Mon Sep 17 00:00:00 2001 From: Jeff Bennett Date: Sun, 18 Aug 2024 16:32:24 -0400 Subject: [PATCH 07/24] docs: clarify permission requirements --- .../vault/IProtocolFeePercentagesProvider.sol | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/pkg/interfaces/contracts/vault/IProtocolFeePercentagesProvider.sol b/pkg/interfaces/contracts/vault/IProtocolFeePercentagesProvider.sol index 3ee3323d0..6eded1eb5 100644 --- a/pkg/interfaces/contracts/vault/IProtocolFeePercentagesProvider.sol +++ b/pkg/interfaces/contracts/vault/IProtocolFeePercentagesProvider.sol @@ -6,21 +6,28 @@ import { IProtocolFeeController } from "./IProtocolFeeController.sol"; interface IProtocolFeePercentagesProvider { /** - * @notice `setDefaultProtocolFees` has not been called for this factory address. + * @notice `setFactorySpecificProtocolFeePercentages` has not been called for this factory address. + * @dev This error can by thrown by `getFactorySpecificProtocolFeePercentages` or + * `setProtocolFeePercentagesForPools`, as both require that valid fee percentages have been set. + * * @param factory The unregistered factory address */ error FactoryNotRegistered(address factory); /** - * @notice The factory address provided is not a valid IBasePoolFactory. - * @dev This means it does not implement or responds incorrectly to `isPoolFromFactory`. + * @notice The factory address provided is not a valid `IBasePoolFactory`. + * @dev This means it responds incorrectly to `isPoolFromFactory` (e.g., always responds true). If it doesn't + * implement `isPoolFromFactory` or isn't a contract at all, calls on `setFactorySpecificProtocolFeePercentages` + * will revert with no data. + * * @param factory The address of the invalid factory */ error InvalidFactory(address factory); /** * @notice The given pool is not from the expected factory. - * @param pool The address of the pool + * @dev Occurs when one of the pools supplied to `setProtocolFeePercentagesForPools` is not from the given factory. + * @param pool The address of the unrecognized pool * @param factory The address of the factory */ error PoolNotFromFactory(address pool, address factory); @@ -43,8 +50,9 @@ interface IProtocolFeePercentagesProvider { /** * @notice Assign intended protocol fee percentages for a given factory. - * @dev This is a permissioned call. After the fee percentages have been set, anyone can call - * `setProtocolFeePercentagesForPools` to update the fees on a set of pools from that factory. + * @dev This is a permissioned call. After the fee percentages have been set, and governance has granted + * this contract permission to set fee percentages on pools, anyone can call `setProtocolFeePercentagesForPools` + * to update the fee percentages on a set of pools from that factory. * * @param factory The address of the factory * @param protocolSwapFeePercentage The new protocol swap fee percentage @@ -58,7 +66,9 @@ interface IProtocolFeePercentagesProvider { /** * @notice Update the protocol fees for a set of pools from a given factory. - * @dev This call is permissionless. Anyone can update the fees, once they're set by governance. + * @dev This call is permissionless. Anyone can update the fee percentages, once they're set by governance. + * Note that goverance must also grant this contract permmission to set protocol fee percentages on pools. + * * @param factory The address of the factory * @param pools The pools whose fees will be set according to `setFactorySpecificProtocolFeePercentages` */ From 0814c98612139b6d16ce3e68b5eb3863b20f0e96 Mon Sep 17 00:00:00 2001 From: Jeff Bennett Date: Sun, 18 Aug 2024 16:38:37 -0400 Subject: [PATCH 08/24] refactor: use constants in test instead of hard-coding --- pkg/vault/test/foundry/ProtocolFeeController.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/vault/test/foundry/ProtocolFeeController.t.sol b/pkg/vault/test/foundry/ProtocolFeeController.t.sol index de0acaebc..e9250a5aa 100644 --- a/pkg/vault/test/foundry/ProtocolFeeController.t.sol +++ b/pkg/vault/test/foundry/ProtocolFeeController.t.sol @@ -78,8 +78,8 @@ contract ProtocolFeeControllerTest is BaseVaultTest { (uint256 maxSwapFeePercentage, uint256 maxYieldFeePercentage) = feeController .getMaximumProtocolFeePercentages(); - assertEq(maxSwapFeePercentage, 50e16, "Wrong maximum swap fee percentage"); - assertEq(maxYieldFeePercentage, 50e16, "Wrong maximum yield fee percentage"); + assertEq(maxSwapFeePercentage, MAX_PROTOCOL_SWAP_FEE_PCT, "Wrong maximum swap fee percentage"); + assertEq(maxYieldFeePercentage, MAX_PROTOCOL_YIELD_FEE_PCT, "Wrong maximum yield fee percentage"); } function testSetGlobalProtocolSwapFeePercentageRange() public { From 9e9a6f24f0c8a48bc88aaf15c90cacbb369823be Mon Sep 17 00:00:00 2001 From: Jeff Bennett Date: Sun, 18 Aug 2024 16:41:36 -0400 Subject: [PATCH 09/24] refactor: remove unnecessary permission set --- pkg/vault/test/foundry/ProtocolFeePercentagesProvider.t.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/vault/test/foundry/ProtocolFeePercentagesProvider.t.sol b/pkg/vault/test/foundry/ProtocolFeePercentagesProvider.t.sol index 0eb9e7c84..a2ba51409 100644 --- a/pkg/vault/test/foundry/ProtocolFeePercentagesProvider.t.sol +++ b/pkg/vault/test/foundry/ProtocolFeePercentagesProvider.t.sol @@ -148,8 +148,6 @@ contract ProtocolFeePercentagesProviderTest is BaseVaultTest { } function testSetProtocolFeePercentagesForPoolsUnregisteredFactory() public { - _grantPermissions(); - vm.expectRevert( abi.encodeWithSelector(IProtocolFeePercentagesProvider.FactoryNotRegistered.selector, INVALID_ADDRESS) ); From 4b2d40d5228751f0821b39c45ae9d701d939bb6f Mon Sep 17 00:00:00 2001 From: Jeff Bennett Date: Mon, 19 Aug 2024 10:24:04 -0400 Subject: [PATCH 10/24] refactor: add event --- .../vault/IProtocolFeePercentagesProvider.sol | 12 ++++++++++++ .../contracts/ProtocolFeePercentagesProvider.sol | 2 ++ .../foundry/ProtocolFeePercentagesProvider.t.sol | 9 ++++++++- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/pkg/interfaces/contracts/vault/IProtocolFeePercentagesProvider.sol b/pkg/interfaces/contracts/vault/IProtocolFeePercentagesProvider.sol index 6eded1eb5..7c1fd278a 100644 --- a/pkg/interfaces/contracts/vault/IProtocolFeePercentagesProvider.sol +++ b/pkg/interfaces/contracts/vault/IProtocolFeePercentagesProvider.sol @@ -5,6 +5,18 @@ pragma solidity ^0.8.24; import { IProtocolFeeController } from "./IProtocolFeeController.sol"; interface IProtocolFeePercentagesProvider { + /** + * @notice Protocol fee percentages have been set for the given factory. + * @param factory The pool factory + * @param protocolSwapFeePercentage The protocol swap fee percentage intended for pools from this factory + * @param protocolYieldFeePercentage The protocol yield fee percentage intended for pools from this factory + */ + event FactorySpecificProtocolFeePercentagesSet( + address indexed factory, + uint256 protocolSwapFeePercentage, + uint256 protocolYieldFeePercentage + ); + /** * @notice `setFactorySpecificProtocolFeePercentages` has not been called for this factory address. * @dev This error can by thrown by `getFactorySpecificProtocolFeePercentages` or diff --git a/pkg/vault/contracts/ProtocolFeePercentagesProvider.sol b/pkg/vault/contracts/ProtocolFeePercentagesProvider.sol index 513b1c296..ad7512bac 100644 --- a/pkg/vault/contracts/ProtocolFeePercentagesProvider.sol +++ b/pkg/vault/contracts/ProtocolFeePercentagesProvider.sol @@ -97,6 +97,8 @@ contract ProtocolFeePercentagesProvider is IProtocolFeePercentagesProvider, Sing protocolYieldFeePercentage: protocolYieldFeePercentage.toUint64(), isFactoryRegistered: true }); + + emit FactorySpecificProtocolFeePercentagesSet(factory, protocolSwapFeePercentage, protocolYieldFeePercentage); } /// @inheritdoc IProtocolFeePercentagesProvider diff --git a/pkg/vault/test/foundry/ProtocolFeePercentagesProvider.t.sol b/pkg/vault/test/foundry/ProtocolFeePercentagesProvider.t.sol index a2ba51409..f4c4a266d 100644 --- a/pkg/vault/test/foundry/ProtocolFeePercentagesProvider.t.sol +++ b/pkg/vault/test/foundry/ProtocolFeePercentagesProvider.t.sol @@ -128,12 +128,19 @@ contract ProtocolFeePercentagesProviderTest is BaseVaultTest { ); } - function testSetFactorySpecificProtocolFeePercentage() public { + function testSetFactorySpecificProtocolFeePercentages() public { _grantPermissions(); // Ensure that they are different, so the test doesn't pass accidentally. uint256 yieldFeePercentage = maxSwapFeePercentage / 2; + vm.expectEmit(); + emit IProtocolFeePercentagesProvider.FactorySpecificProtocolFeePercentagesSet( + address(factoryMock), + maxSwapFeePercentage, + yieldFeePercentage + ); + vm.prank(admin); percentagesProvider.setFactorySpecificProtocolFeePercentages( address(factoryMock), From 557b97d6340b2262df5143e3d7b289788227b34e Mon Sep 17 00:00:00 2001 From: Jeff Bennett Date: Wed, 28 Aug 2024 10:49:02 -0400 Subject: [PATCH 11/24] chore: update gas --- ...ter] add liquidity unbalanced using swapExactIn - warm slots | 2 +- ...atchRouter] remove liquidity using swapExactOut - warm slots | 2 +- ...ightedPool - Standard] add liquidity unbalanced - warm slots | 2 +- ...andard] remove liquidity single token exact out - warm slots | 2 +- ...ter] add liquidity unbalanced using swapExactIn - warm slots | 2 +- ...atchRouter] remove liquidity using swapExactOut - warm slots | 2 +- ...ghtedPool - With rate] add liquidity unbalanced - warm slots | 2 +- ...h rate] remove liquidity single token exact out - warm slots | 2 +- .../gas/.hardhat-snapshots/[WeightedPool] initialize with ETH | 2 +- ...tandard] remove liquidity single token exact in - warm slots | 2 +- ...With rate] add liquidity single token exact out - warm slots | 2 +- ...With rate] swap single token exact in with fees - cold slots | 2 +- ...With rate] swap single token exact in with fees - warm slots | 2 +- ...chRouter] swap exact in with one token and fees - cold slots | 2 +- ...chRouter] swap exact in with one token and fees - warm slots | 2 +- .../[PoolMockWithHooks - Standard] add liquidity proportional | 2 +- ...atchRouter] remove liquidity using swapExactOut - warm slots | 2 +- .../.hardhat-snapshots/[PoolMockWithHooks] initialize with ETH | 2 +- pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] donation | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard - BatchRouter] add liquidity unbalanced using swapExactIn - warm slots b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard - BatchRouter] add liquidity unbalanced using swapExactIn - warm slots index aecc98048..acc0bc17e 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard - BatchRouter] add liquidity unbalanced using swapExactIn - warm slots +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard - BatchRouter] add liquidity unbalanced using swapExactIn - warm slots @@ -1 +1 @@ -209.7k \ No newline at end of file +209.5k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard - BatchRouter] remove liquidity using swapExactOut - warm slots b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard - BatchRouter] remove liquidity using swapExactOut - warm slots index 40d34f0d9..d9ff4ab10 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard - BatchRouter] remove liquidity using swapExactOut - warm slots +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard - BatchRouter] remove liquidity using swapExactOut - warm slots @@ -1 +1 @@ -235.3k \ No newline at end of file +235.2k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] add liquidity unbalanced - warm slots b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] add liquidity unbalanced - warm slots index 4127767f7..d7235b41d 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] add liquidity unbalanced - warm slots +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] add liquidity unbalanced - warm slots @@ -1 +1 @@ -214.0k \ No newline at end of file +213.9k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] remove liquidity single token exact out - warm slots b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] remove liquidity single token exact out - warm slots index 506ee166e..ee15decae 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] remove liquidity single token exact out - warm slots +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] remove liquidity single token exact out - warm slots @@ -1 +1 @@ -192.7k \ No newline at end of file +192.6k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate - BatchRouter] add liquidity unbalanced using swapExactIn - warm slots b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate - BatchRouter] add liquidity unbalanced using swapExactIn - warm slots index e1ae21f2d..15aa845bf 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate - BatchRouter] add liquidity unbalanced using swapExactIn - warm slots +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate - BatchRouter] add liquidity unbalanced using swapExactIn - warm slots @@ -1 +1 @@ -231.5k \ No newline at end of file +231.4k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate - BatchRouter] remove liquidity using swapExactOut - warm slots b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate - BatchRouter] remove liquidity using swapExactOut - warm slots index 922e1b017..cc9eea734 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate - BatchRouter] remove liquidity using swapExactOut - warm slots +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate - BatchRouter] remove liquidity using swapExactOut - warm slots @@ -1 +1 @@ -256.9k \ No newline at end of file +256.7k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] add liquidity unbalanced - warm slots b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] add liquidity unbalanced - warm slots index 6ba047320..43f1f83db 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] add liquidity unbalanced - warm slots +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] add liquidity unbalanced - warm slots @@ -1 +1 @@ -230.8k \ No newline at end of file +230.6k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] remove liquidity single token exact out - warm slots b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] remove liquidity single token exact out - warm slots index 9da2b9249..1406284b4 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] remove liquidity single token exact out - warm slots +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] remove liquidity single token exact out - warm slots @@ -1 +1 @@ -211.8k \ No newline at end of file +211.6k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] initialize with ETH b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] initialize with ETH index dc36fef56..d5ce4e317 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] initialize with ETH +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] initialize with ETH @@ -1 +1 @@ -348.4k \ No newline at end of file +348.3k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] remove liquidity single token exact in - warm slots b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] remove liquidity single token exact in - warm slots index acf77277e..be52068b9 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] remove liquidity single token exact in - warm slots +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] remove liquidity single token exact in - warm slots @@ -1 +1 @@ -169.3k \ No newline at end of file +169.4k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] add liquidity single token exact out - warm slots b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] add liquidity single token exact out - warm slots index be801a3a9..dc73a17ed 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] add liquidity single token exact out - warm slots +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] add liquidity single token exact out - warm slots @@ -1 +1 @@ -199.2k \ No newline at end of file +199.3k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - cold slots b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - cold slots index adf0102cd..fd2c6a061 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - cold slots +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - cold slots @@ -1 +1 @@ -204.9k \ No newline at end of file +205.0k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - warm slots b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - warm slots index f5e4f3723..1bada7ae8 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - warm slots +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - warm slots @@ -1 +1 @@ -170.7k \ No newline at end of file +170.8k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMockWithHooks - Standard - BatchRouter] swap exact in with one token and fees - cold slots b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMockWithHooks - Standard - BatchRouter] swap exact in with one token and fees - cold slots index f155b0969..748a49473 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMockWithHooks - Standard - BatchRouter] swap exact in with one token and fees - cold slots +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMockWithHooks - Standard - BatchRouter] swap exact in with one token and fees - cold slots @@ -1 +1 @@ -180.6k \ No newline at end of file +180.7k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMockWithHooks - Standard - BatchRouter] swap exact in with one token and fees - warm slots b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMockWithHooks - Standard - BatchRouter] swap exact in with one token and fees - warm slots index f155b0969..748a49473 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMockWithHooks - Standard - BatchRouter] swap exact in with one token and fees - warm slots +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMockWithHooks - Standard - BatchRouter] swap exact in with one token and fees - warm slots @@ -1 +1 @@ -180.6k \ No newline at end of file +180.7k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMockWithHooks - Standard] add liquidity proportional b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMockWithHooks - Standard] add liquidity proportional index 2c5955baf..002ebdba4 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMockWithHooks - Standard] add liquidity proportional +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMockWithHooks - Standard] add liquidity proportional @@ -1 +1 @@ -198.4k \ No newline at end of file +198.5k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMockWithHooks - With rate - BatchRouter] remove liquidity using swapExactOut - warm slots b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMockWithHooks - With rate - BatchRouter] remove liquidity using swapExactOut - warm slots index 5f9f6ee63..b20aaf00f 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMockWithHooks - With rate - BatchRouter] remove liquidity using swapExactOut - warm slots +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMockWithHooks - With rate - BatchRouter] remove liquidity using swapExactOut - warm slots @@ -1 +1 @@ -260.6k \ No newline at end of file +260.7k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMockWithHooks] initialize with ETH b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMockWithHooks] initialize with ETH index 3b4f1d72f..48e76b9d7 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMockWithHooks] initialize with ETH +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMockWithHooks] initialize with ETH @@ -1 +1 @@ -356.2k \ No newline at end of file +356.3k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] donation b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] donation index 68b670a11..34e80eef2 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] donation +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] donation @@ -1 +1 @@ -178.8k \ No newline at end of file +178.9k \ No newline at end of file From e4c32d4620f7d6cdbae1b6b2ec658dfdf4658a10 Mon Sep 17 00:00:00 2001 From: Jeff Bennett Date: Mon, 2 Sep 2024 19:39:59 -0400 Subject: [PATCH 12/24] fix: import --- pkg/vault/contracts/ProtocolFeePercentagesProvider.sol | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/vault/contracts/ProtocolFeePercentagesProvider.sol b/pkg/vault/contracts/ProtocolFeePercentagesProvider.sol index ad7512bac..d4ca361c4 100644 --- a/pkg/vault/contracts/ProtocolFeePercentagesProvider.sol +++ b/pkg/vault/contracts/ProtocolFeePercentagesProvider.sol @@ -11,9 +11,7 @@ import { IProtocolFeeController } from "@balancer-labs/v3-interfaces/contracts/v import { IBasePoolFactory } from "@balancer-labs/v3-interfaces/contracts/vault/IBasePoolFactory.sol"; import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol"; -import { - SingletonAuthentication -} from "@balancer-labs/v3-solidity-utils/contracts/helpers/SingletonAuthentication.sol"; +import { SingletonAuthentication } from "./SingletonAuthentication.sol"; contract ProtocolFeePercentagesProvider is IProtocolFeePercentagesProvider, SingletonAuthentication { using SafeCast for uint256; From 81da6281f45133139f314c315a59f18197780a0d Mon Sep 17 00:00:00 2001 From: Jeff Bennett Date: Wed, 4 Sep 2024 12:42:01 -0400 Subject: [PATCH 13/24] refactor: expose the precision check --- .../vault/IProtocolFeeController.sol | 7 +++++ pkg/vault/contracts/ProtocolFeeController.sol | 29 ++++++++++--------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/pkg/interfaces/contracts/vault/IProtocolFeeController.sol b/pkg/interfaces/contracts/vault/IProtocolFeeController.sol index 436360ff4..bec8828e8 100644 --- a/pkg/interfaces/contracts/vault/IProtocolFeeController.sol +++ b/pkg/interfaces/contracts/vault/IProtocolFeeController.sol @@ -227,6 +227,13 @@ interface IProtocolFeeController { */ function updateProtocolYieldFeePercentage(address pool) external; + /** + * @notice Ensure the proposed fee can be stored in the Vault without precision loss. + * @dev Fees are stored with 24 bit precision. The function will revert with `FeePrecisionTooHigh` if invalid. + * @param feePercentage The percentage to be checked + */ + function ensureValidPrecision(uint256 feePercentage) external pure; + /*************************************************************************** Permissioned Functions ***************************************************************************/ diff --git a/pkg/vault/contracts/ProtocolFeeController.sol b/pkg/vault/contracts/ProtocolFeeController.sol index 5c0b4759d..545de3de1 100644 --- a/pkg/vault/contracts/ProtocolFeeController.sol +++ b/pkg/vault/contracts/ProtocolFeeController.sol @@ -126,7 +126,7 @@ contract ProtocolFeeController is if (newSwapFeePercentage > _MAX_PROTOCOL_SWAP_FEE_PERCENTAGE) { revert ProtocolSwapFeePercentageTooHigh(); } - _ensureValidPrecision(newSwapFeePercentage); + ensureValidPrecision(newSwapFeePercentage); _; } @@ -135,7 +135,7 @@ contract ProtocolFeeController is if (newYieldFeePercentage > _MAX_PROTOCOL_YIELD_FEE_PERCENTAGE) { revert ProtocolYieldFeePercentageTooHigh(); } - _ensureValidPrecision(newYieldFeePercentage); + ensureValidPrecision(newYieldFeePercentage); _; } @@ -348,7 +348,7 @@ contract ProtocolFeeController is protocolFeePercentage + protocolFeePercentage.complement().mulDown(poolCreatorFeePercentage); - _ensureValidPrecision(aggregateFeePercentage); + ensureValidPrecision(aggregateFeePercentage); } function _ensureCallerIsPoolCreator(address pool) internal view { @@ -494,6 +494,18 @@ contract ProtocolFeeController is _withdrawPoolCreatorFees(pool, _poolCreators[pool]); } + /// @inheritdoc IProtocolFeeController + function ensureValidPrecision(uint256 feePercentage) public pure { + // Primary fee percentages are 18-decimal values, stored here in 64 bits, and calculated with full 256-bit + // precision. However, the resulting aggregate fees are stored in the Vault with 24-bit precision, which + // corresponds to 0.00001% resolution (i.e., a fee can be 1%, 1.00001%, 1.00002%, but not 1.000005%). + // Ensure there will be no precision loss in the Vault - which would lead to a discrepancy between the + // aggregate fee calculated here and that stored in the Vault. + if ((feePercentage / FEE_SCALING_FACTOR) * FEE_SCALING_FACTOR != feePercentage) { + revert IVaultErrors.FeePrecisionTooHigh(); + } + } + function _withdrawPoolCreatorFees(address pool, address recipient) private { (IERC20[] memory poolTokens, uint256 numTokens) = _getPoolTokensAndCount(pool); @@ -541,15 +553,4 @@ contract ProtocolFeeController is emit ProtocolYieldFeePercentageChanged(pool, newProtocolYieldFeePercentage); } - - function _ensureValidPrecision(uint256 feePercentage) private pure { - // Primary fee percentages are 18-decimal values, stored here in 64 bits, and calculated with full 256-bit - // precision. However, the resulting aggregate fees are stored in the Vault with 24-bit precision, which - // corresponds to 0.00001% resolution (i.e., a fee can be 1%, 1.00001%, 1.00002%, but not 1.000005%). - // Ensure there will be no precision loss in the Vault - which would lead to a discrepancy between the - // aggregate fee calculated here and that stored in the Vault. - if ((feePercentage / FEE_SCALING_FACTOR) * FEE_SCALING_FACTOR != feePercentage) { - revert IVaultErrors.FeePrecisionTooHigh(); - } - } } From fa46b9925126ddb1667d2e9d262df34f95bb850b Mon Sep 17 00:00:00 2001 From: Jeff Bennett Date: Wed, 4 Sep 2024 12:43:02 -0400 Subject: [PATCH 14/24] feat: validate precision in percentages provider --- .../ProtocolFeePercentagesProvider.sol | 4 +++ .../ProtocolFeePercentagesProvider.t.sol | 27 ++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/pkg/vault/contracts/ProtocolFeePercentagesProvider.sol b/pkg/vault/contracts/ProtocolFeePercentagesProvider.sol index d4ca361c4..e02ad06a1 100644 --- a/pkg/vault/contracts/ProtocolFeePercentagesProvider.sol +++ b/pkg/vault/contracts/ProtocolFeePercentagesProvider.sol @@ -83,6 +83,10 @@ contract ProtocolFeePercentagesProvider is IProtocolFeePercentagesProvider, Sing revert IProtocolFeeController.ProtocolYieldFeePercentageTooHigh(); } + // Ensure precision checks will pass. + _protocolFeeController.ensureValidPrecision(protocolSwapFeePercentage); + _protocolFeeController.ensureValidPrecision(protocolYieldFeePercentage); + // Best effort check that `factory` is the address of an IBasePoolFactory. bool poolFromFactory = IBasePoolFactory(factory).isPoolFromFactory(address(0)); if (poolFromFactory) { diff --git a/pkg/vault/test/foundry/ProtocolFeePercentagesProvider.t.sol b/pkg/vault/test/foundry/ProtocolFeePercentagesProvider.t.sol index f4c4a266d..f8011fb33 100644 --- a/pkg/vault/test/foundry/ProtocolFeePercentagesProvider.t.sol +++ b/pkg/vault/test/foundry/ProtocolFeePercentagesProvider.t.sol @@ -9,8 +9,9 @@ import { } from "@balancer-labs/v3-interfaces/contracts/vault/IProtocolFeePercentagesProvider.sol"; import { IAuthentication } from "@balancer-labs/v3-interfaces/contracts/solidity-utils/helpers/IAuthentication.sol"; import { IProtocolFeeController } from "@balancer-labs/v3-interfaces/contracts/vault/IProtocolFeeController.sol"; -import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol"; +import { IVaultErrors } from "@balancer-labs/v3-interfaces/contracts/vault/IVaultErrors.sol"; import { IPoolInfo } from "@balancer-labs/v3-interfaces/contracts/pool-utils/IPoolInfo.sol"; +import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol"; import { ProtocolFeePercentagesProvider } from "../../contracts/ProtocolFeePercentagesProvider.sol"; @@ -116,6 +117,18 @@ contract ProtocolFeePercentagesProviderTest is BaseVaultTest { ); } + function testSetFactorySpecificProtocolFeePercentageHighPrecisionSwap() public { + _grantPermissions(); + + vm.expectRevert(IVaultErrors.FeePrecisionTooHigh.selector); + vm.prank(admin); + percentagesProvider.setFactorySpecificProtocolFeePercentages( + address(factoryMock), + 1e16 + 234234234, + maxYieldFeePercentage + ); + } + function testSetFactorySpecificProtocolFeePercentageInvalidYield() public { _grantPermissions(); @@ -128,6 +141,18 @@ contract ProtocolFeePercentagesProviderTest is BaseVaultTest { ); } + function testSetFactorySpecificProtocolFeePercentageHighPrecisionYield() public { + _grantPermissions(); + + vm.expectRevert(IProtocolFeeController.ProtocolYieldFeePercentageTooHigh.selector); + vm.prank(admin); + percentagesProvider.setFactorySpecificProtocolFeePercentages( + address(factoryMock), + 1e16 + 234234234, + maxYieldFeePercentage + 1 + ); + } + function testSetFactorySpecificProtocolFeePercentages() public { _grantPermissions(); From 20fdd85f5b81053208064e9b88cabf8fbdbeaa6c Mon Sep 17 00:00:00 2001 From: Jeff Bennett Date: Mon, 9 Dec 2024 10:08:58 -0500 Subject: [PATCH 15/24] fix: update constant names --- pkg/vault/contracts/ProtocolFeeController.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/vault/contracts/ProtocolFeeController.sol b/pkg/vault/contracts/ProtocolFeeController.sol index 05d99157b..3435b7038 100644 --- a/pkg/vault/contracts/ProtocolFeeController.sol +++ b/pkg/vault/contracts/ProtocolFeeController.sol @@ -168,7 +168,7 @@ contract ProtocolFeeController is /// @inheritdoc IProtocolFeeController function getMaximumProtocolFeePercentages() external pure returns (uint256, uint256) { - return (_MAX_PROTOCOL_SWAP_FEE_PERCENTAGE, _MAX_PROTOCOL_YIELD_FEE_PERCENTAGE); + return (MAX_PROTOCOL_SWAP_FEE_PERCENTAGE, MAX_PROTOCOL_YIELD_FEE_PERCENTAGE); } /// @inheritdoc IProtocolFeeController From f67bd8cccb716d98a1cdbf0436477652f9648fae Mon Sep 17 00:00:00 2001 From: EndymionJkb Date: Tue, 17 Dec 2024 09:53:35 -0500 Subject: [PATCH 16/24] Add registry of "trusted" contracts (#1179) --- .../vault/IBalancerContractRegistry.sol | 159 +++++++++++ .../vault/IProtocolFeePercentagesProvider.sol | 26 +- .../contracts/BalancerContractRegistry.sol | 203 ++++++++++++++ .../ProtocolFeePercentagesProvider.sol | 36 ++- .../foundry/BalancerContractRegistry.t.sol | 258 ++++++++++++++++++ .../ProtocolFeePercentagesProvider.t.sol | 44 ++- 6 files changed, 689 insertions(+), 37 deletions(-) create mode 100644 pkg/interfaces/contracts/vault/IBalancerContractRegistry.sol create mode 100644 pkg/vault/contracts/BalancerContractRegistry.sol create mode 100644 pkg/vault/test/foundry/BalancerContractRegistry.t.sol diff --git a/pkg/interfaces/contracts/vault/IBalancerContractRegistry.sol b/pkg/interfaces/contracts/vault/IBalancerContractRegistry.sol new file mode 100644 index 000000000..ca9650a83 --- /dev/null +++ b/pkg/interfaces/contracts/vault/IBalancerContractRegistry.sol @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +pragma solidity ^0.8.24; + +/// @notice Registered contracts must be one of these types. +enum ContractType { + POOL_FACTORY, + ROUTER, + HOOK, + ERC4626 +} + +interface IBalancerContractRegistry { + /** + * @notice Contracts can be deprecated, so we store an active flag indicating the status. + * @dev With two flags, we can differentiate between deprecated and non-existent. + * @param exists This flag indicates whether there is an entry for the address or not + * @param active If there is an entry, this flag indicates whether it is active or deprecated + */ + struct ContractStatus { + bool exists; + bool active; + } + + /** + * @notice Emitted wen a new contract is registered. + * @param contractType The type of contract being registered + * @param contractName The name of the contract + * @param contractAddress The address of the contract being registered + */ + event BalancerContractRegistered( + ContractType indexed contractType, + string indexed contractName, + address indexed contractAddress + ); + + /** + * @notice Emitted when a new contract is deprecated. + * @dev This sets the `active` flag to false. + * @param contractAddress The address of the contract being deprecated + */ + event BalancerContractDeprecated(address indexed contractAddress); + + /** + * @notice Emitted when a new contract is deprecated. + * @dev This sets the `active` flag to false. + * @param contractType The type of contract being registered + * @param contractName The name of the contract + * @param existingContract The address of the old contract being replaced + * @param newContract The address of new contract + */ + event BalancerContractReplaced( + ContractType indexed contractType, + string indexed contractName, + address existingContract, + address newContract + ); + + /** + * @notice The given type and name have already been registered. + * @dev Note that the same address can be registered multiple times under different names. For instance, we might + * register an address as both "Factory/20241205-v3-weighted-pool" and "Factory/WeightedPool", or + * "Hook/StableSurgeHook" and "Router/StableSurgeHook". However, the combination of type and name must be unique. + * + * @param contractType The type of the contract + * @param contractName The name of the contract + */ + error ContractAlreadyRegistered(ContractType contractType, string contractName); + + /// @notice The contract being deprecated was never registered. + error ContractNotRegistered(); + + /** + * @notice The contract being deprecated was registered, but already deprecated. + * @param contractAddress The address of the contract to be deprecated + */ + error ContractAlreadyDeprecated(address contractAddress); + + /// @notice Registered contracts cannot have the zero address. + error ZeroContractAddress(); + + /// @notice Registered contract names cannot be blank. + error InvalidContractName(); + + /** + * @notice Register an official Balancer contract (e.g., a trusted router, standard pool factory, or hook). + * @dev This is a permissioned function, and does only basic validation of the address (non-zero) and the name + * (not blank). Governance must ensure this is called with valid information. Emits the + * `BalancerContractRegistered` event if successful. Reverts if the name or address is invalid, or the type/name + * combination has already been registered. + * + * @param contractType The type of contract being registered + * @param contractName A text description of the contract (e.g., "WeightedPool") + * @param contractAddress The address of the contract + */ + function registerBalancerContract( + ContractType contractType, + string memory contractName, + address contractAddress + ) external; + + /** + * @notice Deprecate an official Balancer contract. + * @dev This is a permissioned function that sets the `active` flag to false. The same address might be registered + * multiple times (i.e., unique combinations of types and names); deprecating the address will naturally apply to + * all of them. Emits an `BalancerContractDeprecated` event if successful. Reverts if the address has not been + * registered, or has already been deprecated. + * + * @param contractAddress The address of the contract being deregistered + */ + function deprecateBalancerContract(address contractAddress) external; + + /** + * @notice Migrate a named contract to a new address. + * @dev This is a permissioned function, intended to address one edge case and one feature. The edge case is + * handling mistakes. If an address is mistakenly registered (e.g., set to the address on a different chain), + * this allows correction. The feature is supporting querying for the "latest" contract (e.g., the latest version + * of `WeightedPoolFactory`), vs. having to know the exact version. If the "latest" contract address changes -- + * for instance, if we deprecated `v3-weighted-pool` and registered `v3-weighted-pool-v2`, we would need to + * update `WeightedPoolFactory` to point to the v2 address. Normal registration would fail, as that combination + * was already registered, pointing to v1. + * + * @param contractType The type of contract being replaced + * @param contractName The name of the contract being replaced + * @param newContractAddress The address of the contract that should replace the existing registration + */ + function replaceBalancerContract( + ContractType contractType, + string memory contractName, + address newContractAddress + ) external; + + /** + * @notice Determine whether an address is an official contract of the specified type. + * @dev This is a permissioned function. + * @param contractType The type of contract being renamed + * @param contractAddress The address of the contract + * @return success True if the given address is a registered and active contract of the specified type + */ + function isActiveBalancerContract( + ContractType contractType, + address contractAddress + ) external view returns (bool success); + + /** + * @notice Lookup a registered contract by type and name + * @dev This could target a particular version (e.g. `20241205-v3-weighted-pool`), or a contract name + * (e.g., `WeightedPool`), which could return the "latest" WeightedPool deployment. + * + * @param contractType The type of the contract + * @param contractName The name of the contract + * @return contractAddress The address of the associated contract, if registered, or zero + * @return active True if the address was registered and not deprecated + */ + function getBalancerContract( + ContractType contractType, + string memory contractName + ) external view returns (address contractAddress, bool active); +} diff --git a/pkg/interfaces/contracts/vault/IProtocolFeePercentagesProvider.sol b/pkg/interfaces/contracts/vault/IProtocolFeePercentagesProvider.sol index 7c1fd278a..72d3f7bde 100644 --- a/pkg/interfaces/contracts/vault/IProtocolFeePercentagesProvider.sol +++ b/pkg/interfaces/contracts/vault/IProtocolFeePercentagesProvider.sol @@ -17,24 +17,24 @@ interface IProtocolFeePercentagesProvider { uint256 protocolYieldFeePercentage ); + /// @notice The protocol fee controller was configured with an incorrect Vault address. + error WrongProtocolFeeControllerDeployment(); + /** - * @notice `setFactorySpecificProtocolFeePercentages` has not been called for this factory address. - * @dev This error can by thrown by `getFactorySpecificProtocolFeePercentages` or - * `setProtocolFeePercentagesForPools`, as both require that valid fee percentages have been set. - * - * @param factory The unregistered factory address + * @notice Fees can only be set on recognized factories (i.e., registered in the `BalancerContractRegistry`). + * @param factory The address of the unknown factory */ - error FactoryNotRegistered(address factory); + error UnknownFactory(address factory); /** - * @notice The factory address provided is not a valid `IBasePoolFactory`. - * @dev This means it responds incorrectly to `isPoolFromFactory` (e.g., always responds true). If it doesn't - * implement `isPoolFromFactory` or isn't a contract at all, calls on `setFactorySpecificProtocolFeePercentages` - * will revert with no data. + * @notice `setFactorySpecificProtocolFeePercentages` has not been called for this factory address. + * @dev This error can by thrown by `getFactorySpecificProtocolFeePercentages` or + * `setProtocolFeePercentagesForPools`, as both require that valid fee percentages have been set. + * You need to set the factory fees before you can apply them to pools from that factory. * - * @param factory The address of the invalid factory + * @param factory The factory address where fees have not been set */ - error InvalidFactory(address factory); + error FactoryFeesNotSet(address factory); /** * @notice The given pool is not from the expected factory. @@ -79,7 +79,7 @@ interface IProtocolFeePercentagesProvider { /** * @notice Update the protocol fees for a set of pools from a given factory. * @dev This call is permissionless. Anyone can update the fee percentages, once they're set by governance. - * Note that goverance must also grant this contract permmission to set protocol fee percentages on pools. + * Note that governance must also grant this contract permission to set protocol fee percentages on pools. * * @param factory The address of the factory * @param pools The pools whose fees will be set according to `setFactorySpecificProtocolFeePercentages` diff --git a/pkg/vault/contracts/BalancerContractRegistry.sol b/pkg/vault/contracts/BalancerContractRegistry.sol new file mode 100644 index 000000000..3c48b59c3 --- /dev/null +++ b/pkg/vault/contracts/BalancerContractRegistry.sol @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +pragma solidity ^0.8.24; + +import { + IBalancerContractRegistry, + ContractType +} from "@balancer-labs/v3-interfaces/contracts/vault/IBalancerContractRegistry.sol"; +import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol"; + +import { SingletonAuthentication } from "./SingletonAuthentication.sol"; + +/** + * @notice On-chain registry of standard Balancer contracts. + * @dev Maintain a registry of official Balancer Factories, Routers, Hooks, and valid ERC4626 tokens, for two main + * purposes. The first is to support the many instances where we need to know that a contract is "trusted" (i.e., + * is safe and behaves in the required manner). For instance, some hooks depend critically on the identity of the + * msg.sender, which must be passed down through the Router. Since Routers are permissionless, a malicious one could + * spoof the sender and "fool" the hook. The hook must therefore "trust" the Router. + * + * It is also important for the front-end to know when a particular wrapped token should be used with buffers. Not all + * "ERC4626" wrapped tokens are fully conforming, and buffer operations with non-conforming tokens may fail in various + * unexpected ways. It is not enough to simply check whether a buffer exists (e.g., by calling `getBufferAsset`), + * since best practice is for the pool creator to initialize buffers for all such tokens regardless. They are + * permissionless, and could otherwise be initialized by anyone in unexpected ways. This registry could be used to + * keep track of "known good" buffers, such that `isActiveBalancerContract(ContractType.ERC4626,
)` returns + * true for fully-compliant tokens with properly initialized buffers. + * + * Current solutions involve passing in the address of the trusted Router on deployment: but what if it needs to + * support multiple Routers? Or if the Router is deprecated and replaced? Instead, we can pass the registry address, + * and query this contract to determine whether the Router is a "trusted" one. + * + * The second use case is for off-chain queries, or other protocols that need to easily determine, say, the "latest" + * Weighted Pool Factory. This contract provides `isActiveBalancerContract(type, address)` for the first case, and + * `getBalancerContract(type, name)` for the second. + * + * Note that the `SingletonAuthentication` base contract provides `getVault`, so it is also possible to ask this + * contract for the Vault address, so it doesn't need to be a type. + */ +contract BalancerContractRegistry is IBalancerContractRegistry, SingletonAuthentication { + // ContractId is the hash of ContractType + ContractName. + mapping(bytes32 contractId => address addr) private _contractRegistry; + + // Given an address, store the contract state (i.e., active or deprecated). + mapping(address addr => ContractStatus status) private _contractStatus; + + // We also need to look up address/type combinations (without the name). + mapping(address addr => mapping(ContractType contractType => bool exists)) private _contractTypes; + + constructor(IVault vault) SingletonAuthentication(vault) { + // solhint-disable-previous-line no-empty-blocks + } + + /* + * Example usage: + * + * // Register both the named version and the "latest" Weighted Pool Factory. + * registerBalancerContract( + * ContractType.POOL_FACTORY, '20241205-v3-weighted-pool', 0x201efd508c8DfE9DE1a13c2452863A78CB2a86Cc + * ); + * registerBalancerContract(ContractType.POOL_FACTORY, 'WeightedPool', 0x201efd508c8DfE9DE1a13c2452863A78CB2a86Cc); + * + * // Register the Routers (two of them anyway). + * registerBalancerContract(ContractType.ROUTER, '20241205-v3-router', 0x5C6fb490BDFD3246EB0bB062c168DeCAF4bD9FDd); + * registerBalancerContract( + * ContractType.ROUTER, '20241205-v3-batch-router', 0x136f1EFcC3f8f88516B9E94110D56FDBfB1778d1 + * ); + * + * // Now, hooks that require trusted routers can be deployed with the registry address, and query the router to + * // see whether it's "trusted" (i.e., registered by governance): + * + * isActiveBalancerContract(ContractType.ROUTER, 0x5C6fb490BDFD3246EB0bB062c168DeCAF4bD9FDd) would return true. + * + * Off-chain processes that wanted to know the current address of the Weighted Pool Factory could query by either + * name: + * + * (address, active) = getBalancerContract(ContractType.POOL_FACTORY, '20241205-v3-weighted-pool'); + * (address, active) = getBalancerContract(ContractType.POOL_FACTORY, 'WeightedPool'); + * + * These would return the same result. + * + * If we replaced `20241205-v3-weighted-pool` with `20250107-v3-weighted-pool-v2`, governance would call: + * + * deprecateBalancerContract(0x201efd508c8DfE9DE1a13c2452863A78CB2a86Cc); + * registerBalancerContract( + * ContractType.POOL_FACTORY, '20250107-v3-weighted-pool-v2', 0x9FC3da866e7DF3a1c57adE1a97c9f00a70f010c8) + * ); + * replaceBalancerContract(ContractType.POOL_FACTORY, 'WeightedPool', 0x9FC3da866e7DF3a1c57adE1a97c9f00a70f010c8); + * + * At that point, + * getBalancerContract(ContractType.POOL_FACTORY, '20241205-v3-weighted-pool') returns active=false, + * isActiveBalancerContract(ContractType.POOL_FACTORY, 0x201efd508c8DfE9DE1a13c2452863A78CB2a86Cc) returns false, + * getBalancerContract(ContractType.POOL_FACTORY, 'WeightedPool') returns the v2 address (and active=true). + */ + + /// @inheritdoc IBalancerContractRegistry + function registerBalancerContract( + ContractType contractType, + string memory contractName, + address contractAddress + ) external authenticate { + if (contractAddress == address(0)) { + revert ZeroContractAddress(); + } + + if (bytes(contractName).length == 0) { + revert InvalidContractName(); + } + + bytes32 contractId = _getContractId(contractType, contractName); + + if (_contractRegistry[contractId] != address(0)) { + revert ContractAlreadyRegistered(contractType, contractName); + } + + // Edge case: Cannot register a new "alias" (i.e., type/name) for an address if it's already been deprecated. + ContractStatus memory status = _contractStatus[contractAddress]; + if (status.exists && status.active == false) { + revert ContractAlreadyDeprecated(contractAddress); + } + + // Store the address in the registry, under the unique combination of type and name. + _contractRegistry[contractId] = contractAddress; + + // Record the address as active. The `exists` flag enables differentiating between unregistered and deprecated + // addresses. + _contractStatus[contractAddress] = ContractStatus({ exists: true, active: true }); + + // Enable querying by address + type (without the name, as a single address could be associated with multiple + // types and names). + _contractTypes[contractAddress][contractType] = true; + + emit BalancerContractRegistered(contractType, contractName, contractAddress); + } + + /// @inheritdoc IBalancerContractRegistry + function deprecateBalancerContract(address contractAddress) external authenticate { + ContractStatus memory status = _contractStatus[contractAddress]; + + // Check that the address has been registered. + if (status.exists == false) { + revert ContractNotRegistered(); + } + + // If it was registered, check that it has not already been deprecated. + if (status.active == false) { + revert ContractAlreadyDeprecated(contractAddress); + } + + // Set active to false to indicate that it's now deprecated. This is currently a one-way operation, since + // deprecation is considered permanent. For instance, calling `disable` to deprecate a factory (preventing + // new pool creation) is permanent. + status.active = false; + _contractStatus[contractAddress] = status; + + emit BalancerContractDeprecated(contractAddress); + } + + /// @inheritdoc IBalancerContractRegistry + function replaceBalancerContract( + ContractType contractType, + string memory contractName, + address newContract + ) external authenticate { + if (newContract == address(0)) { + revert ZeroContractAddress(); + } + + // Ensure the type/name combination was already registered. + bytes32 contractId = _getContractId(contractType, contractName); + address existingContract = _contractRegistry[contractId]; + if (existingContract == address(0)) { + revert ContractNotRegistered(); + } + + _contractRegistry[contractId] = newContract; + _contractStatus[newContract] = ContractStatus({ exists: true, active: true }); + _contractTypes[newContract][contractType] = true; + + emit BalancerContractReplaced(contractType, contractName, existingContract, newContract); + } + + /// @inheritdoc IBalancerContractRegistry + function isActiveBalancerContract(ContractType contractType, address contractAddress) external view returns (bool) { + // Ensure the address was registered as the given type - and that it's still active. + return _contractTypes[contractAddress][contractType] && _contractStatus[contractAddress].active; + } + + /// @inheritdoc IBalancerContractRegistry + function getBalancerContract( + ContractType contractType, + string memory contractName + ) external view returns (address contractAddress, bool active) { + bytes32 contractId = _getContractId(contractType, contractName); + + contractAddress = _contractRegistry[contractId]; + active = _contractStatus[contractAddress].active; + } + + function _getContractId(ContractType contractType, string memory contractName) internal pure returns (bytes32) { + return keccak256(abi.encodePacked(contractType, contractName)); + } +} diff --git a/pkg/vault/contracts/ProtocolFeePercentagesProvider.sol b/pkg/vault/contracts/ProtocolFeePercentagesProvider.sol index e02ad06a1..03a08ecc7 100644 --- a/pkg/vault/contracts/ProtocolFeePercentagesProvider.sol +++ b/pkg/vault/contracts/ProtocolFeePercentagesProvider.sol @@ -4,11 +4,15 @@ pragma solidity ^0.8.24; import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import { IProtocolFeeController } from "@balancer-labs/v3-interfaces/contracts/vault/IProtocolFeeController.sol"; +import { IBasePoolFactory } from "@balancer-labs/v3-interfaces/contracts/vault/IBasePoolFactory.sol"; import { IProtocolFeePercentagesProvider } from "@balancer-labs/v3-interfaces/contracts/vault/IProtocolFeePercentagesProvider.sol"; -import { IProtocolFeeController } from "@balancer-labs/v3-interfaces/contracts/vault/IProtocolFeeController.sol"; -import { IBasePoolFactory } from "@balancer-labs/v3-interfaces/contracts/vault/IBasePoolFactory.sol"; +import { + IBalancerContractRegistry, + ContractType +} from "@balancer-labs/v3-interfaces/contracts/vault/IBalancerContractRegistry.sol"; import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol"; import { SingletonAuthentication } from "./SingletonAuthentication.sol"; @@ -16,23 +20,21 @@ import { SingletonAuthentication } from "./SingletonAuthentication.sol"; contract ProtocolFeePercentagesProvider is IProtocolFeePercentagesProvider, SingletonAuthentication { using SafeCast for uint256; - /// @notice The protocol fee controller was configured with an incorrect Vault address. - error WrongProtocolFeeControllerDeployment(); - /** * @dev Data structure to store default protocol fees by factory. Fee percentages are 18-decimal floating point * numbers, so we know they fit in 64 bits, allowing the fees to be stored in a single slot. * * @param protocolSwapFee The protocol swap fee * @param protocolYieldFee The protocol yield fee - * @param isFactoryRegistered Flag indicating fees have been set (allows zero values) + * @param areFactoryFeesSet Flag indicating fees have been set (allows zero values) */ struct FactoryProtocolFees { uint64 protocolSwapFeePercentage; uint64 protocolYieldFeePercentage; - bool isFactoryRegistered; + bool areFactoryFeesSet; } + IBalancerContractRegistry private immutable _trustedContractRegistry; IProtocolFeeController private immutable _protocolFeeController; uint256 private immutable _maxProtocolSwapFeePercentage; @@ -41,8 +43,13 @@ contract ProtocolFeePercentagesProvider is IProtocolFeePercentagesProvider, Sing // Factory address => FactoryProtocolFees mapping(IBasePoolFactory => FactoryProtocolFees) private _factoryDefaultFeePercentages; - constructor(IVault vault, IProtocolFeeController protocolFeeController) SingletonAuthentication(vault) { + constructor( + IVault vault, + IProtocolFeeController protocolFeeController, + IBalancerContractRegistry trustedContractRegistry + ) SingletonAuthentication(vault) { _protocolFeeController = protocolFeeController; + _trustedContractRegistry = trustedContractRegistry; if (protocolFeeController.vault() != vault) { revert WrongProtocolFeeControllerDeployment(); @@ -87,17 +94,16 @@ contract ProtocolFeePercentagesProvider is IProtocolFeePercentagesProvider, Sing _protocolFeeController.ensureValidPrecision(protocolSwapFeePercentage); _protocolFeeController.ensureValidPrecision(protocolYieldFeePercentage); - // Best effort check that `factory` is the address of an IBasePoolFactory. - bool poolFromFactory = IBasePoolFactory(factory).isPoolFromFactory(address(0)); - if (poolFromFactory) { - revert InvalidFactory(factory); + // Ensure the factory is valid. + if (_trustedContractRegistry.isActiveBalancerContract(ContractType.POOL_FACTORY, factory) == false) { + revert UnknownFactory(factory); } // Store the default fee percentages, and mark the factory as registered. _factoryDefaultFeePercentages[IBasePoolFactory(factory)] = FactoryProtocolFees({ protocolSwapFeePercentage: protocolSwapFeePercentage.toUint64(), protocolYieldFeePercentage: protocolYieldFeePercentage.toUint64(), - isFactoryRegistered: true + areFactoryFeesSet: true }); emit FactorySpecificProtocolFeePercentagesSet(factory, protocolSwapFeePercentage, protocolYieldFeePercentage); @@ -125,8 +131,8 @@ contract ProtocolFeePercentagesProvider is IProtocolFeePercentagesProvider, Sing function _getValidatedProtocolFees(address factory) private view returns (FactoryProtocolFees memory factoryFees) { factoryFees = _factoryDefaultFeePercentages[IBasePoolFactory(factory)]; - if (factoryFees.isFactoryRegistered == false) { - revert FactoryNotRegistered(factory); + if (factoryFees.areFactoryFeesSet == false) { + revert FactoryFeesNotSet(factory); } } diff --git a/pkg/vault/test/foundry/BalancerContractRegistry.t.sol b/pkg/vault/test/foundry/BalancerContractRegistry.t.sol new file mode 100644 index 000000000..e56f96d01 --- /dev/null +++ b/pkg/vault/test/foundry/BalancerContractRegistry.t.sol @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +pragma solidity ^0.8.24; + +import "forge-std/Test.sol"; + +import { IAuthentication } from "@balancer-labs/v3-interfaces/contracts/solidity-utils/helpers/IAuthentication.sol"; +import { + IBalancerContractRegistry, + ContractType +} from "@balancer-labs/v3-interfaces/contracts/vault/IBalancerContractRegistry.sol"; + +import { BaseVaultTest } from "@balancer-labs/v3-vault/test/foundry/utils/BaseVaultTest.sol"; + +import { BalancerContractRegistry } from "../../contracts/BalancerContractRegistry.sol"; + +contract BalancerContractRegistryTest is BaseVaultTest { + address private constant ANY_ADDRESS = 0x388C818CA8B9251b393131C08a736A67ccB19297; + address private constant SECOND_ADDRESS = 0x26c212f06675a0149909030D15dc46DAEE9A1f8a; + address private constant ZERO_ADDRESS = address(0); + string private constant DEFAULT_NAME = "Contract"; + + BalancerContractRegistry private registry; + + function setUp() public override { + BaseVaultTest.setUp(); + + registry = new BalancerContractRegistry(vault); + + // Grant permissions. + authorizer.grantRole(registry.getActionId(BalancerContractRegistry.registerBalancerContract.selector), admin); + authorizer.grantRole(registry.getActionId(BalancerContractRegistry.deprecateBalancerContract.selector), admin); + authorizer.grantRole(registry.getActionId(BalancerContractRegistry.replaceBalancerContract.selector), admin); + } + + function testGetVault() public view { + assertEq(address(registry.getVault()), address(vault), "Wrong Vault address"); + } + + function testRegisterWithoutPermission() public { + vm.expectRevert(IAuthentication.SenderNotAllowed.selector); + registry.registerBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME, ANY_ADDRESS); + } + + function testRegisterWithBadAddress() public { + vm.prank(admin); + + vm.expectRevert(IBalancerContractRegistry.ZeroContractAddress.selector); + registry.registerBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME, ZERO_ADDRESS); + } + + function testRegisterWithBadName() public { + vm.prank(admin); + + vm.expectRevert(IBalancerContractRegistry.InvalidContractName.selector); + registry.registerBalancerContract(ContractType.POOL_FACTORY, "", ANY_ADDRESS); + } + + function testValidRegistration() public { + vm.prank(admin); + registry.registerBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME, ANY_ADDRESS); + + // Should return the registered contract as active. + assertTrue( + registry.isActiveBalancerContract(ContractType.POOL_FACTORY, ANY_ADDRESS), + "ANY_ADDRESS is not active" + ); + // Zero address should not be active. + assertFalse( + registry.isActiveBalancerContract(ContractType.POOL_FACTORY, ZERO_ADDRESS), + "ZERO_ADDRESS is active" + ); + // Random address should not be active. + assertFalse( + registry.isActiveBalancerContract(ContractType.POOL_FACTORY, SECOND_ADDRESS), + "SECOND_ADDRESS is active" + ); + // Only active with the correct type. + assertFalse( + registry.isActiveBalancerContract(ContractType.ROUTER, ANY_ADDRESS), + "Address is active as a Router" + ); + + (address contractAddress, bool active) = registry.getBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME); + assertEq(contractAddress, ANY_ADDRESS, "Wrong contract address"); + assertTrue(active, "Contract not active"); + } + + function testBufferRegistration() public { + vm.prank(admin); + registry.registerBalancerContract(ContractType.ERC4626, DEFAULT_NAME, ANY_ADDRESS); + + // Should return the registered contract as active. + assertTrue(registry.isActiveBalancerContract(ContractType.ERC4626, ANY_ADDRESS), "ANY_ADDRESS is not active"); + } + + function testValidRegistrationEmitsEvent() public { + vm.prank(admin); + + vm.expectEmit(); + emit IBalancerContractRegistry.BalancerContractRegistered(ContractType.POOL_FACTORY, DEFAULT_NAME, ANY_ADDRESS); + + registry.registerBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME, ANY_ADDRESS); + } + + function testDeprecateNonExistentContract() public { + vm.prank(admin); + + vm.expectRevert(IBalancerContractRegistry.ContractNotRegistered.selector); + registry.deprecateBalancerContract(ZERO_ADDRESS); + } + + function testValidDeprecation() public { + vm.startPrank(admin); + registry.registerBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME, ANY_ADDRESS); + + (address contractAddress, bool active) = registry.getBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME); + assertEq(contractAddress, ANY_ADDRESS, "Wrong active contract address"); + assertTrue(active, "Contract is not active"); + + registry.deprecateBalancerContract(ANY_ADDRESS); + vm.stopPrank(); + + (contractAddress, active) = registry.getBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME); + assertEq(contractAddress, ANY_ADDRESS, "Wrong deprecated contract address"); + assertFalse(active, "Deprecated contract is active"); + } + + function testDeprecationEmitsEvent() public { + vm.startPrank(admin); + registry.registerBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME, ANY_ADDRESS); + + vm.expectEmit(); + emit IBalancerContractRegistry.BalancerContractDeprecated(ANY_ADDRESS); + + registry.deprecateBalancerContract(ANY_ADDRESS); + vm.stopPrank(); + } + + function testDoubleDeprecation() public { + vm.startPrank(admin); + registry.registerBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME, ANY_ADDRESS); + + registry.deprecateBalancerContract(ANY_ADDRESS); + + vm.expectRevert( + abi.encodeWithSelector(IBalancerContractRegistry.ContractAlreadyDeprecated.selector, ANY_ADDRESS) + ); + registry.deprecateBalancerContract(ANY_ADDRESS); + + vm.stopPrank(); + } + + function testDeprecationOfOverloadedNames() public { + vm.startPrank(admin); + registry.registerBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME, ANY_ADDRESS); + registry.registerBalancerContract(ContractType.POOL_FACTORY, "WeightedPool", ANY_ADDRESS); + + (address contractAddress, bool active) = registry.getBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME); + assertEq(contractAddress, ANY_ADDRESS, "Wrong default address"); + assertTrue(active, "Default contract is not active"); + + (contractAddress, active) = registry.getBalancerContract(ContractType.POOL_FACTORY, "WeightedPool"); + assertEq(contractAddress, ANY_ADDRESS, "Wrong WeightedPool address"); + assertTrue(active, "Canonical contract is not active"); + + registry.deprecateBalancerContract(ANY_ADDRESS); + vm.stopPrank(); + + // Deprecate the address, and all aliases show as inactive. + (contractAddress, active) = registry.getBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME); + assertEq(contractAddress, ANY_ADDRESS, "Wrong deprecated default address"); + assertFalse(active, "Deprecated default contract is active"); + + (contractAddress, active) = registry.getBalancerContract(ContractType.POOL_FACTORY, "WeightedPool"); + assertEq(contractAddress, ANY_ADDRESS, "Wrong deprecated WeightedPool address"); + assertFalse(active, "Deprecated canonical contract is active"); + } + + function testReplacementWithInvalidContract() public { + vm.prank(admin); + + vm.expectRevert(IBalancerContractRegistry.ZeroContractAddress.selector); + registry.replaceBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME, ZERO_ADDRESS); + } + + function testReplacementOfInvalidContract() public { + vm.prank(admin); + + vm.expectRevert(IBalancerContractRegistry.ContractNotRegistered.selector); + registry.replaceBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME, ANY_ADDRESS); + } + + function testValidSimpleReplacement() public { + vm.startPrank(admin); + registry.registerBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME, ANY_ADDRESS); + + registry.deprecateBalancerContract(ANY_ADDRESS); + registry.replaceBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME, SECOND_ADDRESS); + vm.stopPrank(); + + (address contractAddress, bool active) = registry.getBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME); + assertEq(contractAddress, SECOND_ADDRESS, "Wrong replaced address"); + assertTrue(active, "Replaced address is not active"); + + assertTrue( + registry.isActiveBalancerContract(ContractType.POOL_FACTORY, SECOND_ADDRESS), + "SECOND_ADDRESS is not active" + ); + assertFalse(registry.isActiveBalancerContract(ContractType.POOL_FACTORY, ANY_ADDRESS), "ANY_ADDRESS is active"); + } + + function testReplacementEmitsEvent() public { + vm.startPrank(admin); + registry.registerBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME, ANY_ADDRESS); + + vm.expectEmit(); + emit IBalancerContractRegistry.BalancerContractReplaced( + ContractType.POOL_FACTORY, + DEFAULT_NAME, + ANY_ADDRESS, + SECOND_ADDRESS + ); + + registry.replaceBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME, SECOND_ADDRESS); + vm.stopPrank(); + } + + function testComplexReplacement() public { + vm.startPrank(admin); + // Register addr1 as both a named version and generic factory. + registry.registerBalancerContract(ContractType.POOL_FACTORY, "20241205-v3-weighted-pool", ANY_ADDRESS); + registry.registerBalancerContract(ContractType.POOL_FACTORY, "WeightedPool", ANY_ADDRESS); + + registry.deprecateBalancerContract(ANY_ADDRESS); + registry.registerBalancerContract(ContractType.POOL_FACTORY, "20250107-v3-weighted-pool-v2", SECOND_ADDRESS); + registry.replaceBalancerContract(ContractType.POOL_FACTORY, "WeightedPool", SECOND_ADDRESS); + vm.stopPrank(); + + (address contractAddress, bool active) = registry.getBalancerContract( + ContractType.POOL_FACTORY, + "20241205-v3-weighted-pool" + ); + assertEq(contractAddress, ANY_ADDRESS, "Wrong v1 pool address"); + assertFalse(active, "v1 pool address is active"); + + (contractAddress, active) = registry.getBalancerContract( + ContractType.POOL_FACTORY, + "20250107-v3-weighted-pool-v2" + ); + assertEq(contractAddress, SECOND_ADDRESS, "Wrong v2 pool address"); + assertTrue(active, "v2 pool is not active"); + + (contractAddress, active) = registry.getBalancerContract(ContractType.POOL_FACTORY, "WeightedPool"); + assertEq(contractAddress, SECOND_ADDRESS, "Wrong canonical pool address"); + assertTrue(active, "Canonical pool is not address"); + } +} diff --git a/pkg/vault/test/foundry/ProtocolFeePercentagesProvider.t.sol b/pkg/vault/test/foundry/ProtocolFeePercentagesProvider.t.sol index f8011fb33..d5ba6c537 100644 --- a/pkg/vault/test/foundry/ProtocolFeePercentagesProvider.t.sol +++ b/pkg/vault/test/foundry/ProtocolFeePercentagesProvider.t.sol @@ -4,16 +4,21 @@ pragma solidity ^0.8.24; import "forge-std/Test.sol"; -import { - IProtocolFeePercentagesProvider -} from "@balancer-labs/v3-interfaces/contracts/vault/IProtocolFeePercentagesProvider.sol"; import { IAuthentication } from "@balancer-labs/v3-interfaces/contracts/solidity-utils/helpers/IAuthentication.sol"; import { IProtocolFeeController } from "@balancer-labs/v3-interfaces/contracts/vault/IProtocolFeeController.sol"; import { IVaultErrors } from "@balancer-labs/v3-interfaces/contracts/vault/IVaultErrors.sol"; import { IPoolInfo } from "@balancer-labs/v3-interfaces/contracts/pool-utils/IPoolInfo.sol"; +import { + IProtocolFeePercentagesProvider +} from "@balancer-labs/v3-interfaces/contracts/vault/IProtocolFeePercentagesProvider.sol"; +import { + IBalancerContractRegistry, + ContractType +} from "@balancer-labs/v3-interfaces/contracts/vault/IBalancerContractRegistry.sol"; import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol"; import { ProtocolFeePercentagesProvider } from "../../contracts/ProtocolFeePercentagesProvider.sol"; +import { BalancerContractRegistry } from "../../contracts/BalancerContractRegistry.sol"; import { BaseVaultTest } from "./utils/BaseVaultTest.sol"; @@ -21,6 +26,7 @@ contract ProtocolFeePercentagesProviderTest is BaseVaultTest { address internal constant INVALID_ADDRESS = address(0x1234); IProtocolFeePercentagesProvider internal percentagesProvider; + BalancerContractRegistry internal trustedContractRegistry; IAuthentication internal percentagesProviderAuth; IAuthentication internal feeControllerAuth; @@ -33,7 +39,24 @@ contract ProtocolFeePercentagesProviderTest is BaseVaultTest { function setUp() public override { BaseVaultTest.setUp(); - percentagesProvider = new ProtocolFeePercentagesProvider(vault, feeController); + trustedContractRegistry = new BalancerContractRegistry(vault); + percentagesProvider = new ProtocolFeePercentagesProvider(vault, feeController, trustedContractRegistry); + + // Mark the factoryMock as trusted, so that operations on it won't fail. + authorizer.grantRole( + trustedContractRegistry.getActionId(BalancerContractRegistry.registerBalancerContract.selector), + admin + ); + authorizer.grantRole( + trustedContractRegistry.getActionId(BalancerContractRegistry.deprecateBalancerContract.selector), + admin + ); + vm.prank(admin); + trustedContractRegistry.registerBalancerContract( + ContractType.POOL_FACTORY, + "MockFactory", + address(factoryMock) + ); percentagesProviderAuth = IAuthentication(address(percentagesProvider)); feeControllerAuth = IAuthentication(address(feeController)); @@ -49,8 +72,8 @@ contract ProtocolFeePercentagesProviderTest is BaseVaultTest { } function testInvalidConstruction() public { - vm.expectRevert(ProtocolFeePercentagesProvider.WrongProtocolFeeControllerDeployment.selector); - new ProtocolFeePercentagesProvider(IVault(INVALID_ADDRESS), feeController); + vm.expectRevert(IProtocolFeePercentagesProvider.WrongProtocolFeeControllerDeployment.selector); + new ProtocolFeePercentagesProvider(IVault(INVALID_ADDRESS), feeController, trustedContractRegistry); } function testGetProtocolFeeController() public view { @@ -63,7 +86,7 @@ contract ProtocolFeePercentagesProviderTest is BaseVaultTest { function testGetFactorySpecificProtocolFeePercentagesUnregisteredFactory() public { vm.expectRevert( - abi.encodeWithSelector(IProtocolFeePercentagesProvider.FactoryNotRegistered.selector, INVALID_ADDRESS) + abi.encodeWithSelector(IProtocolFeePercentagesProvider.FactoryFeesNotSet.selector, INVALID_ADDRESS) ); percentagesProvider.getFactorySpecificProtocolFeePercentages(INVALID_ADDRESS); } @@ -94,8 +117,11 @@ contract ProtocolFeePercentagesProviderTest is BaseVaultTest { // Cause `isPoolFromFactory` to return "true" for address(0). factoryMock.manualSetPoolFromFactory(address(0)); + vm.prank(admin); + trustedContractRegistry.deprecateBalancerContract(address(factoryMock)); + vm.expectRevert( - abi.encodeWithSelector(IProtocolFeePercentagesProvider.InvalidFactory.selector, address(factoryMock)) + abi.encodeWithSelector(IProtocolFeePercentagesProvider.UnknownFactory.selector, address(factoryMock)) ); vm.prank(admin); percentagesProvider.setFactorySpecificProtocolFeePercentages( @@ -181,7 +207,7 @@ contract ProtocolFeePercentagesProviderTest is BaseVaultTest { function testSetProtocolFeePercentagesForPoolsUnregisteredFactory() public { vm.expectRevert( - abi.encodeWithSelector(IProtocolFeePercentagesProvider.FactoryNotRegistered.selector, INVALID_ADDRESS) + abi.encodeWithSelector(IProtocolFeePercentagesProvider.FactoryFeesNotSet.selector, INVALID_ADDRESS) ); percentagesProvider.setProtocolFeePercentagesForPools(INVALID_ADDRESS, pools); } From 2de3fee997e759091ab82c607477f5eb5671835a Mon Sep 17 00:00:00 2001 From: Jeff Bennett Date: Tue, 17 Dec 2024 23:33:38 -0500 Subject: [PATCH 17/24] chore: update bytecode --- pkg/vault/test/.contract-sizes/CompositeLiquidityRouter | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/vault/test/.contract-sizes/CompositeLiquidityRouter b/pkg/vault/test/.contract-sizes/CompositeLiquidityRouter index c471c89e0..c5c2d49d6 100644 --- a/pkg/vault/test/.contract-sizes/CompositeLiquidityRouter +++ b/pkg/vault/test/.contract-sizes/CompositeLiquidityRouter @@ -1,2 +1,2 @@ -Bytecode 21.675 -InitCode 23.493 \ No newline at end of file +Bytecode 21.554 +InitCode 23.372 \ No newline at end of file From 2a838bfd14b9569b25821889a45437ad2b7b39c7 Mon Sep 17 00:00:00 2001 From: Jeff Bennett Date: Tue, 17 Dec 2024 23:33:47 -0500 Subject: [PATCH 18/24] chore: update gas --- ...atchRouter] swapExactIn - with buffer liquidity - warm slots | 2 +- ...chRouter] swap exact in with one token and fees - cold slots | 2 +- ... Standard] swap single token exact in with fees - cold slots | 2 +- ... Standard] swap single token exact in with fees - warm slots | 2 +- ... WithNestedPool - BatchRouter] swap exact in - tokenA-tokenD | 2 +- ...chRouter] swap exact in with one token and fees - cold slots | 2 +- ... WithRate] swap single token exact in with fees - cold slots | 2 +- ... WithRate] swap single token exact in with fees - warm slots | 2 +- ... BatchRouter] swapExactIn - no buffer liquidity - warm slots | 2 +- ...BatchRouter] swapExactOut - no buffer liquidity - warm slots | 2 +- ...ithRate] remove liquidity single token exact in - warm slots | 2 +- ...dPool - BatchRouter] swap exact in - reverse - tokenD-tokenA | 2 +- ... WithNestedPool - BatchRouter] swap exact in - tokenA-tokenD | 2 +- ...dPool - BatchRouter] swap exact in - reverse - tokenD-tokenA | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - ERC4626 - BatchRouter] swapExactIn - with buffer liquidity - warm slots b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - ERC4626 - BatchRouter] swapExactIn - with buffer liquidity - warm slots index 3c6ece1ca..1afba7063 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - ERC4626 - BatchRouter] swapExactIn - with buffer liquidity - warm slots +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - ERC4626 - BatchRouter] swapExactIn - with buffer liquidity - warm slots @@ -1 +1 @@ -245.1k \ No newline at end of file +245.0k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard - BatchRouter] swap exact in with one token and fees - cold slots b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard - BatchRouter] swap exact in with one token and fees - cold slots index cf1fe3b12..850b54988 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard - BatchRouter] swap exact in with one token and fees - cold slots +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard - BatchRouter] swap exact in with one token and fees - cold slots @@ -1 +1 @@ -194.6k \ No newline at end of file +194.5k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] swap single token exact in with fees - cold slots b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] swap single token exact in with fees - cold slots index 34e80eef2..68b670a11 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] swap single token exact in with fees - cold slots +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] swap single token exact in with fees - cold slots @@ -1 +1 @@ -178.9k \ No newline at end of file +178.8k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] swap single token exact in with fees - warm slots b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] swap single token exact in with fees - warm slots index 28fbaf434..77bc6d810 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] swap single token exact in with fees - warm slots +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] swap single token exact in with fees - warm slots @@ -1 +1 @@ -165.0k \ No newline at end of file +164.9k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithNestedPool - BatchRouter] swap exact in - tokenA-tokenD b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithNestedPool - BatchRouter] swap exact in - tokenA-tokenD index 4aa3cb8e1..722192bb8 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithNestedPool - BatchRouter] swap exact in - tokenA-tokenD +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithNestedPool - BatchRouter] swap exact in - tokenA-tokenD @@ -1 +1 @@ -558.7k \ No newline at end of file +558.6k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate - BatchRouter] swap exact in with one token and fees - cold slots b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate - BatchRouter] swap exact in with one token and fees - cold slots index 4c4616edc..5b0408bcc 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate - BatchRouter] swap exact in with one token and fees - cold slots +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate - BatchRouter] swap exact in with one token and fees - cold slots @@ -1 +1 @@ -227.7k \ No newline at end of file +227.6k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] swap single token exact in with fees - cold slots b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] swap single token exact in with fees - cold slots index 6a6d4e231..11eae2457 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] swap single token exact in with fees - cold slots +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] swap single token exact in with fees - cold slots @@ -1 +1 @@ -212.0k \ No newline at end of file +211.9k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] swap single token exact in with fees - warm slots b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] swap single token exact in with fees - warm slots index bba55110e..9f03a77e5 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] swap single token exact in with fees - warm slots +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] swap single token exact in with fees - warm slots @@ -1 +1 @@ -181.0k \ No newline at end of file +180.9k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - ERC4626 - BatchRouter] swapExactIn - no buffer liquidity - warm slots b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - ERC4626 - BatchRouter] swapExactIn - no buffer liquidity - warm slots index 2c0adaeb7..5db571426 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - ERC4626 - BatchRouter] swapExactIn - no buffer liquidity - warm slots +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - ERC4626 - BatchRouter] swapExactIn - no buffer liquidity - warm slots @@ -1 +1 @@ -298.5k \ No newline at end of file +298.6k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - ERC4626 - BatchRouter] swapExactOut - no buffer liquidity - warm slots b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - ERC4626 - BatchRouter] swapExactOut - no buffer liquidity - warm slots index 28770a155..9a97f9133 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - ERC4626 - BatchRouter] swapExactOut - no buffer liquidity - warm slots +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - ERC4626 - BatchRouter] swapExactOut - no buffer liquidity - warm slots @@ -1 +1 @@ -320.9k \ No newline at end of file +321.0k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - WithRate] remove liquidity single token exact in - warm slots b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - WithRate] remove liquidity single token exact in - warm slots index d3fa14d41..61e5d8518 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - WithRate] remove liquidity single token exact in - warm slots +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - WithRate] remove liquidity single token exact in - warm slots @@ -1 +1 @@ -174.0k \ No newline at end of file +174.1k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - WithNestedPool - BatchRouter] swap exact in - reverse - tokenD-tokenA b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - WithNestedPool - BatchRouter] swap exact in - reverse - tokenD-tokenA index 655a05125..d43e23baf 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - WithNestedPool - BatchRouter] swap exact in - reverse - tokenD-tokenA +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - WithNestedPool - BatchRouter] swap exact in - reverse - tokenD-tokenA @@ -1 +1 @@ -508.4k \ No newline at end of file +508.5k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - WithNestedPool - BatchRouter] swap exact in - tokenA-tokenD b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - WithNestedPool - BatchRouter] swap exact in - tokenA-tokenD index 32bdf3000..107780d07 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - WithNestedPool - BatchRouter] swap exact in - tokenA-tokenD +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - WithNestedPool - BatchRouter] swap exact in - tokenA-tokenD @@ -1 +1 @@ -514.0k \ No newline at end of file +514.1k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMockWithHooks - WithNestedPool - BatchRouter] swap exact in - reverse - tokenD-tokenA b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMockWithHooks - WithNestedPool - BatchRouter] swap exact in - reverse - tokenD-tokenA index 05592e4a8..83ca925e3 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMockWithHooks - WithNestedPool - BatchRouter] swap exact in - reverse - tokenD-tokenA +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMockWithHooks - WithNestedPool - BatchRouter] swap exact in - reverse - tokenD-tokenA @@ -1 +1 @@ -558.9k \ No newline at end of file +559.0k \ No newline at end of file From 6cbd3900527673a06b2ce1f33a27d245666305fb Mon Sep 17 00:00:00 2001 From: Jeff Bennett Date: Mon, 30 Dec 2024 18:00:10 -0500 Subject: [PATCH 19/24] refactor: adjust to BaseVaultTest factoryMock changes --- .../ProtocolFeePercentagesProvider.t.sol | 45 +++++++++---------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/pkg/vault/test/foundry/ProtocolFeePercentagesProvider.t.sol b/pkg/vault/test/foundry/ProtocolFeePercentagesProvider.t.sol index d5ba6c537..9f8c608d6 100644 --- a/pkg/vault/test/foundry/ProtocolFeePercentagesProvider.t.sol +++ b/pkg/vault/test/foundry/ProtocolFeePercentagesProvider.t.sol @@ -19,6 +19,7 @@ import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol" import { ProtocolFeePercentagesProvider } from "../../contracts/ProtocolFeePercentagesProvider.sol"; import { BalancerContractRegistry } from "../../contracts/BalancerContractRegistry.sol"; +import { PoolFactoryMock } from "../../contracts/test/PoolFactoryMock.sol"; import { BaseVaultTest } from "./utils/BaseVaultTest.sol"; @@ -42,7 +43,7 @@ contract ProtocolFeePercentagesProviderTest is BaseVaultTest { trustedContractRegistry = new BalancerContractRegistry(vault); percentagesProvider = new ProtocolFeePercentagesProvider(vault, feeController, trustedContractRegistry); - // Mark the factoryMock as trusted, so that operations on it won't fail. + // Mark the poolFactory as trusted, so that operations on it won't fail. authorizer.grantRole( trustedContractRegistry.getActionId(BalancerContractRegistry.registerBalancerContract.selector), admin @@ -52,11 +53,7 @@ contract ProtocolFeePercentagesProviderTest is BaseVaultTest { admin ); vm.prank(admin); - trustedContractRegistry.registerBalancerContract( - ContractType.POOL_FACTORY, - "MockFactory", - address(factoryMock) - ); + trustedContractRegistry.registerBalancerContract(ContractType.POOL_FACTORY, "MockFactory", poolFactory); percentagesProviderAuth = IAuthentication(address(percentagesProvider)); feeControllerAuth = IAuthentication(address(feeController)); @@ -94,7 +91,7 @@ contract ProtocolFeePercentagesProviderTest is BaseVaultTest { function testSetFactorySpecificProtocolFeePercentageNoPermission() public { vm.expectRevert(IAuthentication.SenderNotAllowed.selector); percentagesProvider.setFactorySpecificProtocolFeePercentages( - address(factoryMock), + poolFactory, maxSwapFeePercentage, maxYieldFeePercentage ); @@ -115,17 +112,15 @@ contract ProtocolFeePercentagesProviderTest is BaseVaultTest { _grantPermissions(); // Cause `isPoolFromFactory` to return "true" for address(0). - factoryMock.manualSetPoolFromFactory(address(0)); + PoolFactoryMock(poolFactory).manualSetPoolFromFactory(address(0)); vm.prank(admin); - trustedContractRegistry.deprecateBalancerContract(address(factoryMock)); + trustedContractRegistry.deprecateBalancerContract(poolFactory); - vm.expectRevert( - abi.encodeWithSelector(IProtocolFeePercentagesProvider.UnknownFactory.selector, address(factoryMock)) - ); + vm.expectRevert(abi.encodeWithSelector(IProtocolFeePercentagesProvider.UnknownFactory.selector, poolFactory)); vm.prank(admin); percentagesProvider.setFactorySpecificProtocolFeePercentages( - address(factoryMock), + poolFactory, maxSwapFeePercentage, maxYieldFeePercentage ); @@ -137,7 +132,7 @@ contract ProtocolFeePercentagesProviderTest is BaseVaultTest { vm.expectRevert(IProtocolFeeController.ProtocolSwapFeePercentageTooHigh.selector); vm.prank(admin); percentagesProvider.setFactorySpecificProtocolFeePercentages( - address(factoryMock), + poolFactory, maxSwapFeePercentage + 1, maxYieldFeePercentage ); @@ -149,7 +144,7 @@ contract ProtocolFeePercentagesProviderTest is BaseVaultTest { vm.expectRevert(IVaultErrors.FeePrecisionTooHigh.selector); vm.prank(admin); percentagesProvider.setFactorySpecificProtocolFeePercentages( - address(factoryMock), + poolFactory, 1e16 + 234234234, maxYieldFeePercentage ); @@ -161,7 +156,7 @@ contract ProtocolFeePercentagesProviderTest is BaseVaultTest { vm.expectRevert(IProtocolFeeController.ProtocolYieldFeePercentageTooHigh.selector); vm.prank(admin); percentagesProvider.setFactorySpecificProtocolFeePercentages( - address(factoryMock), + poolFactory, maxSwapFeePercentage, maxYieldFeePercentage + 1 ); @@ -173,7 +168,7 @@ contract ProtocolFeePercentagesProviderTest is BaseVaultTest { vm.expectRevert(IProtocolFeeController.ProtocolYieldFeePercentageTooHigh.selector); vm.prank(admin); percentagesProvider.setFactorySpecificProtocolFeePercentages( - address(factoryMock), + poolFactory, 1e16 + 234234234, maxYieldFeePercentage + 1 ); @@ -187,20 +182,20 @@ contract ProtocolFeePercentagesProviderTest is BaseVaultTest { vm.expectEmit(); emit IProtocolFeePercentagesProvider.FactorySpecificProtocolFeePercentagesSet( - address(factoryMock), + poolFactory, maxSwapFeePercentage, yieldFeePercentage ); vm.prank(admin); percentagesProvider.setFactorySpecificProtocolFeePercentages( - address(factoryMock), + poolFactory, maxSwapFeePercentage, yieldFeePercentage ); (uint256 actualSwapFeePercentage, uint256 actualYieldFeePercentage) = percentagesProvider - .getFactorySpecificProtocolFeePercentages(address(factoryMock)); + .getFactorySpecificProtocolFeePercentages(poolFactory); assertEq(actualSwapFeePercentage, maxSwapFeePercentage, "Wrong factory swap fee percentage"); assertEq(actualYieldFeePercentage, yieldFeePercentage, "Wrong factory swap fee percentage"); } @@ -217,7 +212,7 @@ contract ProtocolFeePercentagesProviderTest is BaseVaultTest { vm.prank(admin); percentagesProvider.setFactorySpecificProtocolFeePercentages( - address(factoryMock), + poolFactory, maxSwapFeePercentage, maxYieldFeePercentage ); @@ -230,11 +225,11 @@ contract ProtocolFeePercentagesProviderTest is BaseVaultTest { abi.encodeWithSelector( IProtocolFeePercentagesProvider.PoolNotFromFactory.selector, INVALID_ADDRESS, - address(factoryMock) + poolFactory ) ); - percentagesProvider.setProtocolFeePercentagesForPools(address(factoryMock), pools); + percentagesProvider.setProtocolFeePercentagesForPools(poolFactory, pools); } function testSetProtocolFeePercentagesForPools() public { @@ -246,7 +241,7 @@ contract ProtocolFeePercentagesProviderTest is BaseVaultTest { vm.prank(admin); percentagesProvider.setFactorySpecificProtocolFeePercentages( - address(factoryMock), + poolFactory, expectedSwapFeePercentage, expectedYieldFeePercentage ); @@ -258,7 +253,7 @@ contract ProtocolFeePercentagesProviderTest is BaseVaultTest { assertEq(originalYieldFeePercentage, 0, "Non-zero original yield fee percentage"); // Permissionless call to set fee percentages by factory. - percentagesProvider.setProtocolFeePercentagesForPools(address(factoryMock), pools); + percentagesProvider.setProtocolFeePercentagesForPools(poolFactory, pools); (uint256 currentSwapFeePercentage, uint256 currentYieldFeePercentage) = IPoolInfo(pool) .getAggregateFeePercentages(); From bd967bd8c3a99e926413dcc953b10510f57dfb6f Mon Sep 17 00:00:00 2001 From: Jeff Bennett Date: Tue, 31 Dec 2024 16:18:28 -0500 Subject: [PATCH 20/24] feat: add BalancerContractRegistry --- .../vault/IBalancerContractRegistry.sol | 159 ++++++++++++++ .../contracts/BalancerContractRegistry.sol | 203 ++++++++++++++++++ 2 files changed, 362 insertions(+) create mode 100644 pkg/interfaces/contracts/vault/IBalancerContractRegistry.sol create mode 100644 pkg/vault/contracts/BalancerContractRegistry.sol diff --git a/pkg/interfaces/contracts/vault/IBalancerContractRegistry.sol b/pkg/interfaces/contracts/vault/IBalancerContractRegistry.sol new file mode 100644 index 000000000..ca9650a83 --- /dev/null +++ b/pkg/interfaces/contracts/vault/IBalancerContractRegistry.sol @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +pragma solidity ^0.8.24; + +/// @notice Registered contracts must be one of these types. +enum ContractType { + POOL_FACTORY, + ROUTER, + HOOK, + ERC4626 +} + +interface IBalancerContractRegistry { + /** + * @notice Contracts can be deprecated, so we store an active flag indicating the status. + * @dev With two flags, we can differentiate between deprecated and non-existent. + * @param exists This flag indicates whether there is an entry for the address or not + * @param active If there is an entry, this flag indicates whether it is active or deprecated + */ + struct ContractStatus { + bool exists; + bool active; + } + + /** + * @notice Emitted wen a new contract is registered. + * @param contractType The type of contract being registered + * @param contractName The name of the contract + * @param contractAddress The address of the contract being registered + */ + event BalancerContractRegistered( + ContractType indexed contractType, + string indexed contractName, + address indexed contractAddress + ); + + /** + * @notice Emitted when a new contract is deprecated. + * @dev This sets the `active` flag to false. + * @param contractAddress The address of the contract being deprecated + */ + event BalancerContractDeprecated(address indexed contractAddress); + + /** + * @notice Emitted when a new contract is deprecated. + * @dev This sets the `active` flag to false. + * @param contractType The type of contract being registered + * @param contractName The name of the contract + * @param existingContract The address of the old contract being replaced + * @param newContract The address of new contract + */ + event BalancerContractReplaced( + ContractType indexed contractType, + string indexed contractName, + address existingContract, + address newContract + ); + + /** + * @notice The given type and name have already been registered. + * @dev Note that the same address can be registered multiple times under different names. For instance, we might + * register an address as both "Factory/20241205-v3-weighted-pool" and "Factory/WeightedPool", or + * "Hook/StableSurgeHook" and "Router/StableSurgeHook". However, the combination of type and name must be unique. + * + * @param contractType The type of the contract + * @param contractName The name of the contract + */ + error ContractAlreadyRegistered(ContractType contractType, string contractName); + + /// @notice The contract being deprecated was never registered. + error ContractNotRegistered(); + + /** + * @notice The contract being deprecated was registered, but already deprecated. + * @param contractAddress The address of the contract to be deprecated + */ + error ContractAlreadyDeprecated(address contractAddress); + + /// @notice Registered contracts cannot have the zero address. + error ZeroContractAddress(); + + /// @notice Registered contract names cannot be blank. + error InvalidContractName(); + + /** + * @notice Register an official Balancer contract (e.g., a trusted router, standard pool factory, or hook). + * @dev This is a permissioned function, and does only basic validation of the address (non-zero) and the name + * (not blank). Governance must ensure this is called with valid information. Emits the + * `BalancerContractRegistered` event if successful. Reverts if the name or address is invalid, or the type/name + * combination has already been registered. + * + * @param contractType The type of contract being registered + * @param contractName A text description of the contract (e.g., "WeightedPool") + * @param contractAddress The address of the contract + */ + function registerBalancerContract( + ContractType contractType, + string memory contractName, + address contractAddress + ) external; + + /** + * @notice Deprecate an official Balancer contract. + * @dev This is a permissioned function that sets the `active` flag to false. The same address might be registered + * multiple times (i.e., unique combinations of types and names); deprecating the address will naturally apply to + * all of them. Emits an `BalancerContractDeprecated` event if successful. Reverts if the address has not been + * registered, or has already been deprecated. + * + * @param contractAddress The address of the contract being deregistered + */ + function deprecateBalancerContract(address contractAddress) external; + + /** + * @notice Migrate a named contract to a new address. + * @dev This is a permissioned function, intended to address one edge case and one feature. The edge case is + * handling mistakes. If an address is mistakenly registered (e.g., set to the address on a different chain), + * this allows correction. The feature is supporting querying for the "latest" contract (e.g., the latest version + * of `WeightedPoolFactory`), vs. having to know the exact version. If the "latest" contract address changes -- + * for instance, if we deprecated `v3-weighted-pool` and registered `v3-weighted-pool-v2`, we would need to + * update `WeightedPoolFactory` to point to the v2 address. Normal registration would fail, as that combination + * was already registered, pointing to v1. + * + * @param contractType The type of contract being replaced + * @param contractName The name of the contract being replaced + * @param newContractAddress The address of the contract that should replace the existing registration + */ + function replaceBalancerContract( + ContractType contractType, + string memory contractName, + address newContractAddress + ) external; + + /** + * @notice Determine whether an address is an official contract of the specified type. + * @dev This is a permissioned function. + * @param contractType The type of contract being renamed + * @param contractAddress The address of the contract + * @return success True if the given address is a registered and active contract of the specified type + */ + function isActiveBalancerContract( + ContractType contractType, + address contractAddress + ) external view returns (bool success); + + /** + * @notice Lookup a registered contract by type and name + * @dev This could target a particular version (e.g. `20241205-v3-weighted-pool`), or a contract name + * (e.g., `WeightedPool`), which could return the "latest" WeightedPool deployment. + * + * @param contractType The type of the contract + * @param contractName The name of the contract + * @return contractAddress The address of the associated contract, if registered, or zero + * @return active True if the address was registered and not deprecated + */ + function getBalancerContract( + ContractType contractType, + string memory contractName + ) external view returns (address contractAddress, bool active); +} diff --git a/pkg/vault/contracts/BalancerContractRegistry.sol b/pkg/vault/contracts/BalancerContractRegistry.sol new file mode 100644 index 000000000..3c48b59c3 --- /dev/null +++ b/pkg/vault/contracts/BalancerContractRegistry.sol @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +pragma solidity ^0.8.24; + +import { + IBalancerContractRegistry, + ContractType +} from "@balancer-labs/v3-interfaces/contracts/vault/IBalancerContractRegistry.sol"; +import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol"; + +import { SingletonAuthentication } from "./SingletonAuthentication.sol"; + +/** + * @notice On-chain registry of standard Balancer contracts. + * @dev Maintain a registry of official Balancer Factories, Routers, Hooks, and valid ERC4626 tokens, for two main + * purposes. The first is to support the many instances where we need to know that a contract is "trusted" (i.e., + * is safe and behaves in the required manner). For instance, some hooks depend critically on the identity of the + * msg.sender, which must be passed down through the Router. Since Routers are permissionless, a malicious one could + * spoof the sender and "fool" the hook. The hook must therefore "trust" the Router. + * + * It is also important for the front-end to know when a particular wrapped token should be used with buffers. Not all + * "ERC4626" wrapped tokens are fully conforming, and buffer operations with non-conforming tokens may fail in various + * unexpected ways. It is not enough to simply check whether a buffer exists (e.g., by calling `getBufferAsset`), + * since best practice is for the pool creator to initialize buffers for all such tokens regardless. They are + * permissionless, and could otherwise be initialized by anyone in unexpected ways. This registry could be used to + * keep track of "known good" buffers, such that `isActiveBalancerContract(ContractType.ERC4626,
)` returns + * true for fully-compliant tokens with properly initialized buffers. + * + * Current solutions involve passing in the address of the trusted Router on deployment: but what if it needs to + * support multiple Routers? Or if the Router is deprecated and replaced? Instead, we can pass the registry address, + * and query this contract to determine whether the Router is a "trusted" one. + * + * The second use case is for off-chain queries, or other protocols that need to easily determine, say, the "latest" + * Weighted Pool Factory. This contract provides `isActiveBalancerContract(type, address)` for the first case, and + * `getBalancerContract(type, name)` for the second. + * + * Note that the `SingletonAuthentication` base contract provides `getVault`, so it is also possible to ask this + * contract for the Vault address, so it doesn't need to be a type. + */ +contract BalancerContractRegistry is IBalancerContractRegistry, SingletonAuthentication { + // ContractId is the hash of ContractType + ContractName. + mapping(bytes32 contractId => address addr) private _contractRegistry; + + // Given an address, store the contract state (i.e., active or deprecated). + mapping(address addr => ContractStatus status) private _contractStatus; + + // We also need to look up address/type combinations (without the name). + mapping(address addr => mapping(ContractType contractType => bool exists)) private _contractTypes; + + constructor(IVault vault) SingletonAuthentication(vault) { + // solhint-disable-previous-line no-empty-blocks + } + + /* + * Example usage: + * + * // Register both the named version and the "latest" Weighted Pool Factory. + * registerBalancerContract( + * ContractType.POOL_FACTORY, '20241205-v3-weighted-pool', 0x201efd508c8DfE9DE1a13c2452863A78CB2a86Cc + * ); + * registerBalancerContract(ContractType.POOL_FACTORY, 'WeightedPool', 0x201efd508c8DfE9DE1a13c2452863A78CB2a86Cc); + * + * // Register the Routers (two of them anyway). + * registerBalancerContract(ContractType.ROUTER, '20241205-v3-router', 0x5C6fb490BDFD3246EB0bB062c168DeCAF4bD9FDd); + * registerBalancerContract( + * ContractType.ROUTER, '20241205-v3-batch-router', 0x136f1EFcC3f8f88516B9E94110D56FDBfB1778d1 + * ); + * + * // Now, hooks that require trusted routers can be deployed with the registry address, and query the router to + * // see whether it's "trusted" (i.e., registered by governance): + * + * isActiveBalancerContract(ContractType.ROUTER, 0x5C6fb490BDFD3246EB0bB062c168DeCAF4bD9FDd) would return true. + * + * Off-chain processes that wanted to know the current address of the Weighted Pool Factory could query by either + * name: + * + * (address, active) = getBalancerContract(ContractType.POOL_FACTORY, '20241205-v3-weighted-pool'); + * (address, active) = getBalancerContract(ContractType.POOL_FACTORY, 'WeightedPool'); + * + * These would return the same result. + * + * If we replaced `20241205-v3-weighted-pool` with `20250107-v3-weighted-pool-v2`, governance would call: + * + * deprecateBalancerContract(0x201efd508c8DfE9DE1a13c2452863A78CB2a86Cc); + * registerBalancerContract( + * ContractType.POOL_FACTORY, '20250107-v3-weighted-pool-v2', 0x9FC3da866e7DF3a1c57adE1a97c9f00a70f010c8) + * ); + * replaceBalancerContract(ContractType.POOL_FACTORY, 'WeightedPool', 0x9FC3da866e7DF3a1c57adE1a97c9f00a70f010c8); + * + * At that point, + * getBalancerContract(ContractType.POOL_FACTORY, '20241205-v3-weighted-pool') returns active=false, + * isActiveBalancerContract(ContractType.POOL_FACTORY, 0x201efd508c8DfE9DE1a13c2452863A78CB2a86Cc) returns false, + * getBalancerContract(ContractType.POOL_FACTORY, 'WeightedPool') returns the v2 address (and active=true). + */ + + /// @inheritdoc IBalancerContractRegistry + function registerBalancerContract( + ContractType contractType, + string memory contractName, + address contractAddress + ) external authenticate { + if (contractAddress == address(0)) { + revert ZeroContractAddress(); + } + + if (bytes(contractName).length == 0) { + revert InvalidContractName(); + } + + bytes32 contractId = _getContractId(contractType, contractName); + + if (_contractRegistry[contractId] != address(0)) { + revert ContractAlreadyRegistered(contractType, contractName); + } + + // Edge case: Cannot register a new "alias" (i.e., type/name) for an address if it's already been deprecated. + ContractStatus memory status = _contractStatus[contractAddress]; + if (status.exists && status.active == false) { + revert ContractAlreadyDeprecated(contractAddress); + } + + // Store the address in the registry, under the unique combination of type and name. + _contractRegistry[contractId] = contractAddress; + + // Record the address as active. The `exists` flag enables differentiating between unregistered and deprecated + // addresses. + _contractStatus[contractAddress] = ContractStatus({ exists: true, active: true }); + + // Enable querying by address + type (without the name, as a single address could be associated with multiple + // types and names). + _contractTypes[contractAddress][contractType] = true; + + emit BalancerContractRegistered(contractType, contractName, contractAddress); + } + + /// @inheritdoc IBalancerContractRegistry + function deprecateBalancerContract(address contractAddress) external authenticate { + ContractStatus memory status = _contractStatus[contractAddress]; + + // Check that the address has been registered. + if (status.exists == false) { + revert ContractNotRegistered(); + } + + // If it was registered, check that it has not already been deprecated. + if (status.active == false) { + revert ContractAlreadyDeprecated(contractAddress); + } + + // Set active to false to indicate that it's now deprecated. This is currently a one-way operation, since + // deprecation is considered permanent. For instance, calling `disable` to deprecate a factory (preventing + // new pool creation) is permanent. + status.active = false; + _contractStatus[contractAddress] = status; + + emit BalancerContractDeprecated(contractAddress); + } + + /// @inheritdoc IBalancerContractRegistry + function replaceBalancerContract( + ContractType contractType, + string memory contractName, + address newContract + ) external authenticate { + if (newContract == address(0)) { + revert ZeroContractAddress(); + } + + // Ensure the type/name combination was already registered. + bytes32 contractId = _getContractId(contractType, contractName); + address existingContract = _contractRegistry[contractId]; + if (existingContract == address(0)) { + revert ContractNotRegistered(); + } + + _contractRegistry[contractId] = newContract; + _contractStatus[newContract] = ContractStatus({ exists: true, active: true }); + _contractTypes[newContract][contractType] = true; + + emit BalancerContractReplaced(contractType, contractName, existingContract, newContract); + } + + /// @inheritdoc IBalancerContractRegistry + function isActiveBalancerContract(ContractType contractType, address contractAddress) external view returns (bool) { + // Ensure the address was registered as the given type - and that it's still active. + return _contractTypes[contractAddress][contractType] && _contractStatus[contractAddress].active; + } + + /// @inheritdoc IBalancerContractRegistry + function getBalancerContract( + ContractType contractType, + string memory contractName + ) external view returns (address contractAddress, bool active) { + bytes32 contractId = _getContractId(contractType, contractName); + + contractAddress = _contractRegistry[contractId]; + active = _contractStatus[contractAddress].active; + } + + function _getContractId(ContractType contractType, string memory contractName) internal pure returns (bytes32) { + return keccak256(abi.encodePacked(contractType, contractName)); + } +} From 7d20fefa2f5b6a0d6bf0bd74cfacb6fee165acdf Mon Sep 17 00:00:00 2001 From: Jeff Bennett Date: Tue, 31 Dec 2024 16:18:41 -0500 Subject: [PATCH 21/24] test: add tests for BalancerContractRegistry --- .../foundry/BalancerContractRegistry.t.sol | 258 ++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 pkg/vault/test/foundry/BalancerContractRegistry.t.sol diff --git a/pkg/vault/test/foundry/BalancerContractRegistry.t.sol b/pkg/vault/test/foundry/BalancerContractRegistry.t.sol new file mode 100644 index 000000000..e56f96d01 --- /dev/null +++ b/pkg/vault/test/foundry/BalancerContractRegistry.t.sol @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +pragma solidity ^0.8.24; + +import "forge-std/Test.sol"; + +import { IAuthentication } from "@balancer-labs/v3-interfaces/contracts/solidity-utils/helpers/IAuthentication.sol"; +import { + IBalancerContractRegistry, + ContractType +} from "@balancer-labs/v3-interfaces/contracts/vault/IBalancerContractRegistry.sol"; + +import { BaseVaultTest } from "@balancer-labs/v3-vault/test/foundry/utils/BaseVaultTest.sol"; + +import { BalancerContractRegistry } from "../../contracts/BalancerContractRegistry.sol"; + +contract BalancerContractRegistryTest is BaseVaultTest { + address private constant ANY_ADDRESS = 0x388C818CA8B9251b393131C08a736A67ccB19297; + address private constant SECOND_ADDRESS = 0x26c212f06675a0149909030D15dc46DAEE9A1f8a; + address private constant ZERO_ADDRESS = address(0); + string private constant DEFAULT_NAME = "Contract"; + + BalancerContractRegistry private registry; + + function setUp() public override { + BaseVaultTest.setUp(); + + registry = new BalancerContractRegistry(vault); + + // Grant permissions. + authorizer.grantRole(registry.getActionId(BalancerContractRegistry.registerBalancerContract.selector), admin); + authorizer.grantRole(registry.getActionId(BalancerContractRegistry.deprecateBalancerContract.selector), admin); + authorizer.grantRole(registry.getActionId(BalancerContractRegistry.replaceBalancerContract.selector), admin); + } + + function testGetVault() public view { + assertEq(address(registry.getVault()), address(vault), "Wrong Vault address"); + } + + function testRegisterWithoutPermission() public { + vm.expectRevert(IAuthentication.SenderNotAllowed.selector); + registry.registerBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME, ANY_ADDRESS); + } + + function testRegisterWithBadAddress() public { + vm.prank(admin); + + vm.expectRevert(IBalancerContractRegistry.ZeroContractAddress.selector); + registry.registerBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME, ZERO_ADDRESS); + } + + function testRegisterWithBadName() public { + vm.prank(admin); + + vm.expectRevert(IBalancerContractRegistry.InvalidContractName.selector); + registry.registerBalancerContract(ContractType.POOL_FACTORY, "", ANY_ADDRESS); + } + + function testValidRegistration() public { + vm.prank(admin); + registry.registerBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME, ANY_ADDRESS); + + // Should return the registered contract as active. + assertTrue( + registry.isActiveBalancerContract(ContractType.POOL_FACTORY, ANY_ADDRESS), + "ANY_ADDRESS is not active" + ); + // Zero address should not be active. + assertFalse( + registry.isActiveBalancerContract(ContractType.POOL_FACTORY, ZERO_ADDRESS), + "ZERO_ADDRESS is active" + ); + // Random address should not be active. + assertFalse( + registry.isActiveBalancerContract(ContractType.POOL_FACTORY, SECOND_ADDRESS), + "SECOND_ADDRESS is active" + ); + // Only active with the correct type. + assertFalse( + registry.isActiveBalancerContract(ContractType.ROUTER, ANY_ADDRESS), + "Address is active as a Router" + ); + + (address contractAddress, bool active) = registry.getBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME); + assertEq(contractAddress, ANY_ADDRESS, "Wrong contract address"); + assertTrue(active, "Contract not active"); + } + + function testBufferRegistration() public { + vm.prank(admin); + registry.registerBalancerContract(ContractType.ERC4626, DEFAULT_NAME, ANY_ADDRESS); + + // Should return the registered contract as active. + assertTrue(registry.isActiveBalancerContract(ContractType.ERC4626, ANY_ADDRESS), "ANY_ADDRESS is not active"); + } + + function testValidRegistrationEmitsEvent() public { + vm.prank(admin); + + vm.expectEmit(); + emit IBalancerContractRegistry.BalancerContractRegistered(ContractType.POOL_FACTORY, DEFAULT_NAME, ANY_ADDRESS); + + registry.registerBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME, ANY_ADDRESS); + } + + function testDeprecateNonExistentContract() public { + vm.prank(admin); + + vm.expectRevert(IBalancerContractRegistry.ContractNotRegistered.selector); + registry.deprecateBalancerContract(ZERO_ADDRESS); + } + + function testValidDeprecation() public { + vm.startPrank(admin); + registry.registerBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME, ANY_ADDRESS); + + (address contractAddress, bool active) = registry.getBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME); + assertEq(contractAddress, ANY_ADDRESS, "Wrong active contract address"); + assertTrue(active, "Contract is not active"); + + registry.deprecateBalancerContract(ANY_ADDRESS); + vm.stopPrank(); + + (contractAddress, active) = registry.getBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME); + assertEq(contractAddress, ANY_ADDRESS, "Wrong deprecated contract address"); + assertFalse(active, "Deprecated contract is active"); + } + + function testDeprecationEmitsEvent() public { + vm.startPrank(admin); + registry.registerBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME, ANY_ADDRESS); + + vm.expectEmit(); + emit IBalancerContractRegistry.BalancerContractDeprecated(ANY_ADDRESS); + + registry.deprecateBalancerContract(ANY_ADDRESS); + vm.stopPrank(); + } + + function testDoubleDeprecation() public { + vm.startPrank(admin); + registry.registerBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME, ANY_ADDRESS); + + registry.deprecateBalancerContract(ANY_ADDRESS); + + vm.expectRevert( + abi.encodeWithSelector(IBalancerContractRegistry.ContractAlreadyDeprecated.selector, ANY_ADDRESS) + ); + registry.deprecateBalancerContract(ANY_ADDRESS); + + vm.stopPrank(); + } + + function testDeprecationOfOverloadedNames() public { + vm.startPrank(admin); + registry.registerBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME, ANY_ADDRESS); + registry.registerBalancerContract(ContractType.POOL_FACTORY, "WeightedPool", ANY_ADDRESS); + + (address contractAddress, bool active) = registry.getBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME); + assertEq(contractAddress, ANY_ADDRESS, "Wrong default address"); + assertTrue(active, "Default contract is not active"); + + (contractAddress, active) = registry.getBalancerContract(ContractType.POOL_FACTORY, "WeightedPool"); + assertEq(contractAddress, ANY_ADDRESS, "Wrong WeightedPool address"); + assertTrue(active, "Canonical contract is not active"); + + registry.deprecateBalancerContract(ANY_ADDRESS); + vm.stopPrank(); + + // Deprecate the address, and all aliases show as inactive. + (contractAddress, active) = registry.getBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME); + assertEq(contractAddress, ANY_ADDRESS, "Wrong deprecated default address"); + assertFalse(active, "Deprecated default contract is active"); + + (contractAddress, active) = registry.getBalancerContract(ContractType.POOL_FACTORY, "WeightedPool"); + assertEq(contractAddress, ANY_ADDRESS, "Wrong deprecated WeightedPool address"); + assertFalse(active, "Deprecated canonical contract is active"); + } + + function testReplacementWithInvalidContract() public { + vm.prank(admin); + + vm.expectRevert(IBalancerContractRegistry.ZeroContractAddress.selector); + registry.replaceBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME, ZERO_ADDRESS); + } + + function testReplacementOfInvalidContract() public { + vm.prank(admin); + + vm.expectRevert(IBalancerContractRegistry.ContractNotRegistered.selector); + registry.replaceBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME, ANY_ADDRESS); + } + + function testValidSimpleReplacement() public { + vm.startPrank(admin); + registry.registerBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME, ANY_ADDRESS); + + registry.deprecateBalancerContract(ANY_ADDRESS); + registry.replaceBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME, SECOND_ADDRESS); + vm.stopPrank(); + + (address contractAddress, bool active) = registry.getBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME); + assertEq(contractAddress, SECOND_ADDRESS, "Wrong replaced address"); + assertTrue(active, "Replaced address is not active"); + + assertTrue( + registry.isActiveBalancerContract(ContractType.POOL_FACTORY, SECOND_ADDRESS), + "SECOND_ADDRESS is not active" + ); + assertFalse(registry.isActiveBalancerContract(ContractType.POOL_FACTORY, ANY_ADDRESS), "ANY_ADDRESS is active"); + } + + function testReplacementEmitsEvent() public { + vm.startPrank(admin); + registry.registerBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME, ANY_ADDRESS); + + vm.expectEmit(); + emit IBalancerContractRegistry.BalancerContractReplaced( + ContractType.POOL_FACTORY, + DEFAULT_NAME, + ANY_ADDRESS, + SECOND_ADDRESS + ); + + registry.replaceBalancerContract(ContractType.POOL_FACTORY, DEFAULT_NAME, SECOND_ADDRESS); + vm.stopPrank(); + } + + function testComplexReplacement() public { + vm.startPrank(admin); + // Register addr1 as both a named version and generic factory. + registry.registerBalancerContract(ContractType.POOL_FACTORY, "20241205-v3-weighted-pool", ANY_ADDRESS); + registry.registerBalancerContract(ContractType.POOL_FACTORY, "WeightedPool", ANY_ADDRESS); + + registry.deprecateBalancerContract(ANY_ADDRESS); + registry.registerBalancerContract(ContractType.POOL_FACTORY, "20250107-v3-weighted-pool-v2", SECOND_ADDRESS); + registry.replaceBalancerContract(ContractType.POOL_FACTORY, "WeightedPool", SECOND_ADDRESS); + vm.stopPrank(); + + (address contractAddress, bool active) = registry.getBalancerContract( + ContractType.POOL_FACTORY, + "20241205-v3-weighted-pool" + ); + assertEq(contractAddress, ANY_ADDRESS, "Wrong v1 pool address"); + assertFalse(active, "v1 pool address is active"); + + (contractAddress, active) = registry.getBalancerContract( + ContractType.POOL_FACTORY, + "20250107-v3-weighted-pool-v2" + ); + assertEq(contractAddress, SECOND_ADDRESS, "Wrong v2 pool address"); + assertTrue(active, "v2 pool is not active"); + + (contractAddress, active) = registry.getBalancerContract(ContractType.POOL_FACTORY, "WeightedPool"); + assertEq(contractAddress, SECOND_ADDRESS, "Wrong canonical pool address"); + assertTrue(active, "Canonical pool is not address"); + } +} From e334be3985bc5afc1fb762afcd7355a2585f9508 Mon Sep 17 00:00:00 2001 From: Jeff Bennett Date: Fri, 3 Jan 2025 22:27:03 -0500 Subject: [PATCH 22/24] feat: add OTHER category as a catch-all; rename success to isActive --- .../contracts/vault/IBalancerContractRegistry.sol | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/interfaces/contracts/vault/IBalancerContractRegistry.sol b/pkg/interfaces/contracts/vault/IBalancerContractRegistry.sol index ca9650a83..e19f41597 100644 --- a/pkg/interfaces/contracts/vault/IBalancerContractRegistry.sol +++ b/pkg/interfaces/contracts/vault/IBalancerContractRegistry.sol @@ -7,7 +7,8 @@ enum ContractType { POOL_FACTORY, ROUTER, HOOK, - ERC4626 + ERC4626, + OTHER } interface IBalancerContractRegistry { @@ -135,12 +136,12 @@ interface IBalancerContractRegistry { * @dev This is a permissioned function. * @param contractType The type of contract being renamed * @param contractAddress The address of the contract - * @return success True if the given address is a registered and active contract of the specified type + * @return isActive True if the given address is a registered and active contract of the specified type */ function isActiveBalancerContract( ContractType contractType, address contractAddress - ) external view returns (bool success); + ) external view returns (bool isActive); /** * @notice Lookup a registered contract by type and name From d52248e2240e7f245868952eb2ae65a9f95ec02e Mon Sep 17 00:00:00 2001 From: Jeff Bennett Date: Mon, 6 Jan 2025 14:58:40 -0500 Subject: [PATCH 23/24] refactor: simplify data structures --- .../vault/IBalancerContractRegistry.sol | 17 +++++--- .../contracts/BalancerContractRegistry.sol | 40 +++++++++---------- 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/pkg/interfaces/contracts/vault/IBalancerContractRegistry.sol b/pkg/interfaces/contracts/vault/IBalancerContractRegistry.sol index e19f41597..fda5a1844 100644 --- a/pkg/interfaces/contracts/vault/IBalancerContractRegistry.sol +++ b/pkg/interfaces/contracts/vault/IBalancerContractRegistry.sol @@ -14,13 +14,18 @@ enum ContractType { interface IBalancerContractRegistry { /** * @notice Contracts can be deprecated, so we store an active flag indicating the status. - * @dev With two flags, we can differentiate between deprecated and non-existent. - * @param exists This flag indicates whether there is an entry for the address or not - * @param active If there is an entry, this flag indicates whether it is active or deprecated + * @dev With two flags, we can differentiate between deprecated and non-existent. The same contract address + * can have multiple names, but only one type. If a contract is legitimately multiple types (e.g., a hook that + * also acts as a router), set the type to its "primary" function: hook, in this case. The "Other" type is + * intended as a catch-all for things that don't find into the standard types (e.g., helper contracts). + * + * @param isRegistered This flag indicates whether there is an entry for the address or not + * @param isActive If there is an entry, this flag indicates whether it is active or deprecated */ - struct ContractStatus { - bool exists; - bool active; + struct ContractInfo { + ContractType contractType; + bool isRegistered; + bool isActive; } /** diff --git a/pkg/vault/contracts/BalancerContractRegistry.sol b/pkg/vault/contracts/BalancerContractRegistry.sol index 3c48b59c3..16bc9aa25 100644 --- a/pkg/vault/contracts/BalancerContractRegistry.sol +++ b/pkg/vault/contracts/BalancerContractRegistry.sol @@ -42,10 +42,7 @@ contract BalancerContractRegistry is IBalancerContractRegistry, SingletonAuthent mapping(bytes32 contractId => address addr) private _contractRegistry; // Given an address, store the contract state (i.e., active or deprecated). - mapping(address addr => ContractStatus status) private _contractStatus; - - // We also need to look up address/type combinations (without the name). - mapping(address addr => mapping(ContractType contractType => bool exists)) private _contractTypes; + mapping(address addr => ContractInfo info) private _contractInfo; constructor(IVault vault) SingletonAuthentication(vault) { // solhint-disable-previous-line no-empty-blocks @@ -114,44 +111,44 @@ contract BalancerContractRegistry is IBalancerContractRegistry, SingletonAuthent } // Edge case: Cannot register a new "alias" (i.e., type/name) for an address if it's already been deprecated. - ContractStatus memory status = _contractStatus[contractAddress]; - if (status.exists && status.active == false) { + ContractInfo memory info = _contractInfo[contractAddress]; + if (info.isRegistered && info.isActive == false) { revert ContractAlreadyDeprecated(contractAddress); } // Store the address in the registry, under the unique combination of type and name. _contractRegistry[contractId] = contractAddress; - // Record the address as active. The `exists` flag enables differentiating between unregistered and deprecated + // Record the address as active. The `isActive` flag enables differentiating between unregistered and deprecated // addresses. - _contractStatus[contractAddress] = ContractStatus({ exists: true, active: true }); - - // Enable querying by address + type (without the name, as a single address could be associated with multiple - // types and names). - _contractTypes[contractAddress][contractType] = true; + _contractInfo[contractAddress] = ContractInfo({ + contractType: contractType, + isRegistered: true, + isActive: true + }); emit BalancerContractRegistered(contractType, contractName, contractAddress); } /// @inheritdoc IBalancerContractRegistry function deprecateBalancerContract(address contractAddress) external authenticate { - ContractStatus memory status = _contractStatus[contractAddress]; + ContractInfo memory info = _contractInfo[contractAddress]; // Check that the address has been registered. - if (status.exists == false) { + if (info.isRegistered == false) { revert ContractNotRegistered(); } // If it was registered, check that it has not already been deprecated. - if (status.active == false) { + if (info.isActive == false) { revert ContractAlreadyDeprecated(contractAddress); } // Set active to false to indicate that it's now deprecated. This is currently a one-way operation, since // deprecation is considered permanent. For instance, calling `disable` to deprecate a factory (preventing // new pool creation) is permanent. - status.active = false; - _contractStatus[contractAddress] = status; + info.isActive = false; + _contractInfo[contractAddress] = info; emit BalancerContractDeprecated(contractAddress); } @@ -174,16 +171,17 @@ contract BalancerContractRegistry is IBalancerContractRegistry, SingletonAuthent } _contractRegistry[contractId] = newContract; - _contractStatus[newContract] = ContractStatus({ exists: true, active: true }); - _contractTypes[newContract][contractType] = true; + _contractInfo[newContract] = ContractInfo({ contractType: contractType, isRegistered: true, isActive: true }); emit BalancerContractReplaced(contractType, contractName, existingContract, newContract); } /// @inheritdoc IBalancerContractRegistry function isActiveBalancerContract(ContractType contractType, address contractAddress) external view returns (bool) { + ContractInfo memory info = _contractInfo[contractAddress]; + // Ensure the address was registered as the given type - and that it's still active. - return _contractTypes[contractAddress][contractType] && _contractStatus[contractAddress].active; + return info.isActive && info.contractType == contractType; } /// @inheritdoc IBalancerContractRegistry @@ -194,7 +192,7 @@ contract BalancerContractRegistry is IBalancerContractRegistry, SingletonAuthent bytes32 contractId = _getContractId(contractType, contractName); contractAddress = _contractRegistry[contractId]; - active = _contractStatus[contractAddress].active; + active = _contractInfo[contractAddress].isActive; } function _getContractId(ContractType contractType, string memory contractName) internal pure returns (bytes32) { From 3cbdf935c704d6a313323899f1bf02d146841415 Mon Sep 17 00:00:00 2001 From: Jeff Bennett Date: Tue, 14 Jan 2025 11:14:48 -0500 Subject: [PATCH 24/24] chore: update gas --- ...atchRouter] swapExactIn - with buffer liquidity - warm slots | 2 +- ...BatchRouter] swapExactOut - no buffer liquidity - warm slots | 2 +- ...tchRouter] swapExactOut - with buffer liquidity - warm slots | 2 +- ...ter] add liquidity unbalanced using swapExactIn - warm slots | 2 +- ...- BatchRouter] add liquidity using swapExactOur - warm slots | 2 +- ...atchRouter] remove liquidity using swapExactOut - warm slots | 2 +- ...chRouter] swap exact in with one token and fees - cold slots | 2 +- .../[StablePool - Standard] add liquidity proportional | 2 +- ... Standard] add liquidity single token exact out - warm slots | 2 +- .../gas/.hardhat-snapshots/[StablePool - Standard] donation | 2 +- .../[StablePool - Standard] initialize with ETH | 2 +- .../[StablePool - Standard] initialize without ETH | 2 +- .../[StablePool - Standard] remove liquidity proportional | 2 +- ...tandard] remove liquidity single token exact in - warm slots | 2 +- ... Standard] swap single token exact in with fees - cold slots | 2 +- ... Standard] swap single token exact in with fees - warm slots | 2 +- ... WithNestedPool - BatchRouter] swap exact in - tokenA-tokenD | 2 +- ...ter] add liquidity unbalanced using swapExactIn - warm slots | 2 +- ...- BatchRouter] add liquidity using swapExactOur - warm slots | 2 +- ...BatchRouter] remove liquidity using swapExactIn - warm slots | 2 +- ...chRouter] swap exact in with one token and fees - cold slots | 2 +- .../[StablePool - WithRate] add liquidity proportional | 2 +- ... WithRate] add liquidity single token exact out - warm slots | 2 +- ...StablePool - WithRate] add liquidity unbalanced - warm slots | 2 +- .../[StablePool - WithRate] remove liquidity proportional | 2 +- ...ithRate] remove liquidity single token exact in - warm slots | 2 +- ...thRate] remove liquidity single token exact out - warm slots | 2 +- ... WithRate] swap single token exact in with fees - cold slots | 2 +- ... WithRate] swap single token exact in with fees - warm slots | 2 +- ...atchRouter] swapExactIn - with buffer liquidity - warm slots | 2 +- ...ter] add liquidity unbalanced using swapExactIn - warm slots | 2 +- .../[WeightedPool - WithRate] remove liquidity proportional | 2 +- 32 files changed, 32 insertions(+), 32 deletions(-) diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - ERC4626 - BatchRouter] swapExactIn - with buffer liquidity - warm slots b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - ERC4626 - BatchRouter] swapExactIn - with buffer liquidity - warm slots index 1afba7063..3c6ece1ca 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - ERC4626 - BatchRouter] swapExactIn - with buffer liquidity - warm slots +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - ERC4626 - BatchRouter] swapExactIn - with buffer liquidity - warm slots @@ -1 +1 @@ -245.0k \ No newline at end of file +245.1k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - ERC4626 - BatchRouter] swapExactOut - no buffer liquidity - warm slots b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - ERC4626 - BatchRouter] swapExactOut - no buffer liquidity - warm slots index cba6a2769..24bbfa4e3 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - ERC4626 - BatchRouter] swapExactOut - no buffer liquidity - warm slots +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - ERC4626 - BatchRouter] swapExactOut - no buffer liquidity - warm slots @@ -1 +1 @@ -334.6k \ No newline at end of file +334.7k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - ERC4626 - BatchRouter] swapExactOut - with buffer liquidity - warm slots b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - ERC4626 - BatchRouter] swapExactOut - with buffer liquidity - warm slots index 8f7230cba..c1495e78b 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - ERC4626 - BatchRouter] swapExactOut - with buffer liquidity - warm slots +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - ERC4626 - BatchRouter] swapExactOut - with buffer liquidity - warm slots @@ -1 +1 @@ -258.4k \ No newline at end of file +258.5k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard - BatchRouter] add liquidity unbalanced using swapExactIn - warm slots b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard - BatchRouter] add liquidity unbalanced using swapExactIn - warm slots index 9dd99b8b4..dbba43d6f 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard - BatchRouter] add liquidity unbalanced using swapExactIn - warm slots +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard - BatchRouter] add liquidity unbalanced using swapExactIn - warm slots @@ -1 +1 @@ -196.3k \ No newline at end of file +196.4k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard - BatchRouter] add liquidity using swapExactOur - warm slots b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard - BatchRouter] add liquidity using swapExactOur - warm slots index cc6877811..9f03a77e5 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard - BatchRouter] add liquidity using swapExactOur - warm slots +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard - BatchRouter] add liquidity using swapExactOur - warm slots @@ -1 +1 @@ -180.8k \ No newline at end of file +180.9k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard - BatchRouter] remove liquidity using swapExactOut - warm slots b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard - BatchRouter] remove liquidity using swapExactOut - warm slots index 8d0a3504f..2e853f7ae 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard - BatchRouter] remove liquidity using swapExactOut - warm slots +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard - BatchRouter] remove liquidity using swapExactOut - warm slots @@ -1 +1 @@ -209.8k \ No newline at end of file +209.9k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard - BatchRouter] swap exact in with one token and fees - cold slots b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard - BatchRouter] swap exact in with one token and fees - cold slots index 850b54988..cf1fe3b12 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard - BatchRouter] swap exact in with one token and fees - cold slots +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard - BatchRouter] swap exact in with one token and fees - cold slots @@ -1 +1 @@ -194.5k \ No newline at end of file +194.6k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] add liquidity proportional b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] add liquidity proportional index 32a6c77e7..184d05cb6 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] add liquidity proportional +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] add liquidity proportional @@ -1 +1 @@ -179.2k \ No newline at end of file +179.3k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] add liquidity single token exact out - warm slots b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] add liquidity single token exact out - warm slots index eec4e69d4..43e844943 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] add liquidity single token exact out - warm slots +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] add liquidity single token exact out - warm slots @@ -1 +1 @@ -172.1k \ No newline at end of file +172.2k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] donation b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] donation index 5f4b8010a..e1c39f087 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] donation +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] donation @@ -1 +1 @@ -171.0k \ No newline at end of file +171.1k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] initialize with ETH b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] initialize with ETH index 7faabe4fc..d5ce4e317 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] initialize with ETH +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] initialize with ETH @@ -1 +1 @@ -347.9k \ No newline at end of file +348.3k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] initialize without ETH b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] initialize without ETH index 02b7f4b6d..8d41f35e7 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] initialize without ETH +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] initialize without ETH @@ -1 +1 @@ -335.4k \ No newline at end of file +335.8k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] remove liquidity proportional b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] remove liquidity proportional index 60bcf4864..abc6469ef 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] remove liquidity proportional +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] remove liquidity proportional @@ -1 +1 @@ -167.1k \ No newline at end of file +167.2k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] remove liquidity single token exact in - warm slots b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] remove liquidity single token exact in - warm slots index 0be376f1e..c7e7b4544 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] remove liquidity single token exact in - warm slots +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] remove liquidity single token exact in - warm slots @@ -1 +1 @@ -166.4k \ No newline at end of file +166.5k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] swap single token exact in with fees - cold slots b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] swap single token exact in with fees - cold slots index 68b670a11..34e80eef2 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] swap single token exact in with fees - cold slots +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] swap single token exact in with fees - cold slots @@ -1 +1 @@ -178.8k \ No newline at end of file +178.9k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] swap single token exact in with fees - warm slots b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] swap single token exact in with fees - warm slots index 77bc6d810..28fbaf434 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] swap single token exact in with fees - warm slots +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - Standard] swap single token exact in with fees - warm slots @@ -1 +1 @@ -164.9k \ No newline at end of file +165.0k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithNestedPool - BatchRouter] swap exact in - tokenA-tokenD b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithNestedPool - BatchRouter] swap exact in - tokenA-tokenD index 722192bb8..4aa3cb8e1 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithNestedPool - BatchRouter] swap exact in - tokenA-tokenD +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithNestedPool - BatchRouter] swap exact in - tokenA-tokenD @@ -1 +1 @@ -558.6k \ No newline at end of file +558.7k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate - BatchRouter] add liquidity unbalanced using swapExactIn - warm slots b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate - BatchRouter] add liquidity unbalanced using swapExactIn - warm slots index beb043c93..007fed429 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate - BatchRouter] add liquidity unbalanced using swapExactIn - warm slots +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate - BatchRouter] add liquidity unbalanced using swapExactIn - warm slots @@ -1 +1 @@ -217.7k \ No newline at end of file +217.8k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate - BatchRouter] add liquidity using swapExactOur - warm slots b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate - BatchRouter] add liquidity using swapExactOur - warm slots index 4b78b5ecf..804746be2 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate - BatchRouter] add liquidity using swapExactOur - warm slots +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate - BatchRouter] add liquidity using swapExactOur - warm slots @@ -1 +1 @@ -202.2k \ No newline at end of file +202.3k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate - BatchRouter] remove liquidity using swapExactIn - warm slots b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate - BatchRouter] remove liquidity using swapExactIn - warm slots index 11eae2457..6a6d4e231 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate - BatchRouter] remove liquidity using swapExactIn - warm slots +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate - BatchRouter] remove liquidity using swapExactIn - warm slots @@ -1 +1 @@ -211.9k \ No newline at end of file +212.0k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate - BatchRouter] swap exact in with one token and fees - cold slots b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate - BatchRouter] swap exact in with one token and fees - cold slots index 5b0408bcc..4c4616edc 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate - BatchRouter] swap exact in with one token and fees - cold slots +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate - BatchRouter] swap exact in with one token and fees - cold slots @@ -1 +1 @@ -227.6k \ No newline at end of file +227.7k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] add liquidity proportional b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] add liquidity proportional index d4f0a3a17..0718ec32c 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] add liquidity proportional +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] add liquidity proportional @@ -1 +1 @@ -234.3k \ No newline at end of file +234.4k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] add liquidity single token exact out - warm slots b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] add liquidity single token exact out - warm slots index ad087e174..7c6a6731a 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] add liquidity single token exact out - warm slots +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] add liquidity single token exact out - warm slots @@ -1 +1 @@ -193.5k \ No newline at end of file +193.6k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] add liquidity unbalanced - warm slots b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] add liquidity unbalanced - warm slots index 69b363ada..43f1f83db 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] add liquidity unbalanced - warm slots +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] add liquidity unbalanced - warm slots @@ -1 +1 @@ -230.5k \ No newline at end of file +230.6k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] remove liquidity proportional b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] remove liquidity proportional index c4f48aed1..01db6fe9f 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] remove liquidity proportional +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] remove liquidity proportional @@ -1 +1 @@ -222.1k \ No newline at end of file +222.2k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] remove liquidity single token exact in - warm slots b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] remove liquidity single token exact in - warm slots index 0d3d71a36..a1a745b5f 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] remove liquidity single token exact in - warm slots +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] remove liquidity single token exact in - warm slots @@ -1 +1 @@ -187.7k \ No newline at end of file +187.8k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] remove liquidity single token exact out - warm slots b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] remove liquidity single token exact out - warm slots index 47f39a685..04c51970a 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] remove liquidity single token exact out - warm slots +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] remove liquidity single token exact out - warm slots @@ -1 +1 @@ -198.1k \ No newline at end of file +198.2k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] swap single token exact in with fees - cold slots b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] swap single token exact in with fees - cold slots index 11eae2457..6a6d4e231 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] swap single token exact in with fees - cold slots +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] swap single token exact in with fees - cold slots @@ -1 +1 @@ -211.9k \ No newline at end of file +212.0k \ No newline at end of file diff --git a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] swap single token exact in with fees - warm slots b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] swap single token exact in with fees - warm slots index 9f03a77e5..bba55110e 100644 --- a/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] swap single token exact in with fees - warm slots +++ b/pkg/pool-stable/test/gas/.hardhat-snapshots/[StablePool - WithRate] swap single token exact in with fees - warm slots @@ -1 +1 @@ -180.9k \ No newline at end of file +181.0k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - ERC4626 - BatchRouter] swapExactIn - with buffer liquidity - warm slots b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - ERC4626 - BatchRouter] swapExactIn - with buffer liquidity - warm slots index 15aa845bf..1e422055c 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - ERC4626 - BatchRouter] swapExactIn - with buffer liquidity - warm slots +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - ERC4626 - BatchRouter] swapExactIn - with buffer liquidity - warm slots @@ -1 +1 @@ -231.4k \ No newline at end of file +231.3k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard - BatchRouter] add liquidity unbalanced using swapExactIn - warm slots b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard - BatchRouter] add liquidity unbalanced using swapExactIn - warm slots index afdce9710..bfb6c3c2f 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard - BatchRouter] add liquidity unbalanced using swapExactIn - warm slots +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard - BatchRouter] add liquidity unbalanced using swapExactIn - warm slots @@ -1 +1 @@ -189.4k \ No newline at end of file +189.3k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - WithRate] remove liquidity proportional b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - WithRate] remove liquidity proportional index 01db6fe9f..c4f48aed1 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - WithRate] remove liquidity proportional +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - WithRate] remove liquidity proportional @@ -1 +1 @@ -222.2k \ No newline at end of file +222.1k \ No newline at end of file