Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Protocol Fee Controller migration contract #1299

Merged
merged 57 commits into from
Mar 8, 2025
Merged
Changes from 1 commit
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
a062f77
feat: add getters for pool creator data, and explicit pool registrati…
EndymionJkb Feb 16, 2025
529ac79
feat: add governance-scripts package
EndymionJkb Feb 16, 2025
b058b23
feat: add protocol fee controller migration
EndymionJkb Feb 16, 2025
b30b537
refactor: don't pass in pool creator to initialize
EndymionJkb Feb 16, 2025
eed79d8
refactor: remove directory that generates strange errors
EndymionJkb Feb 17, 2025
33ce5e6
refactor: remove redundant (and problematic) `_poolCreators` storage
EndymionJkb Feb 17, 2025
ab3df83
refactor: remove unused error
EndymionJkb Feb 17, 2025
395c7bc
refactor: don't collect and withdraw fees
EndymionJkb Feb 17, 2025
8d4409d
feat: add explicit pool registration and new pool creator event
EndymionJkb Feb 17, 2025
71b466b
test: add tests for new features
EndymionJkb Feb 17, 2025
42be536
docs: update migration comments for simple one
EndymionJkb Feb 17, 2025
ccae6fd
feat: add registerInMigration helper
EndymionJkb Feb 17, 2025
22102e1
test: add tests for new migration function
EndymionJkb Feb 17, 2025
01a081c
feat: add level 2 migration contract
EndymionJkb Feb 17, 2025
7ebb82f
refactor: simplify to pass old fee controller instead of the values (…
EndymionJkb Feb 18, 2025
a7999dd
test: new migration interface
EndymionJkb Feb 18, 2025
746a210
refactor: reuse original migration functions
EndymionJkb Feb 18, 2025
dff7d50
refactor: rename migration function
EndymionJkb Feb 18, 2025
f8e9652
refactor: add migration role
EndymionJkb Feb 18, 2025
57d48d2
refactor: simplify migrations
EndymionJkb Feb 18, 2025
da0e2b9
refactor: simplify migratePool
EndymionJkb Feb 18, 2025
c00dccc
lint
EndymionJkb Feb 18, 2025
90adc71
refactor: don't need FixedPoint
EndymionJkb Feb 18, 2025
570a075
test: adjust to new migration signature
EndymionJkb Feb 18, 2025
56d791d
test: remove unnecessary pool creator storage from fee controller
EndymionJkb Feb 18, 2025
25b8162
refactor: make virtual in case we need a v3 someday
EndymionJkb Feb 18, 2025
ecb07f0
Merge branch 'main' into fee-controller-script
EndymionJkb Feb 18, 2025
e7c6149
refactor: move basic authorizer
EndymionJkb Feb 18, 2025
c735ec2
refactor: complete move of basic authorizer
EndymionJkb Feb 18, 2025
3b04170
Merge branch 'main' into fee-controller-script
EndymionJkb Feb 18, 2025
c4639be
feat: new protocol fee controller, suitable for migrations
EndymionJkb Feb 19, 2025
4da42c5
test: update tests for new functions
EndymionJkb Feb 19, 2025
c4324fb
refactor: remove pool creator from ProtocolFeeControllerMock
EndymionJkb Feb 19, 2025
0a5c484
chore: update bytecode
EndymionJkb Feb 19, 2025
ebae2ab
chore: update gas
EndymionJkb Feb 19, 2025
7793e6d
docs: fix comments and decrease diffs
EndymionJkb Feb 19, 2025
c1be315
Merge branch 'protocol-fee-controller-v2' into fee-controller-script
EndymionJkb Feb 19, 2025
0e8bd5a
test: adjust tests for fee controller pool creator
EndymionJkb Feb 19, 2025
84f0b1e
Merge branch 'protocol-fee-controller-v2' into fee-controller-script
EndymionJkb Feb 19, 2025
cde1d1e
refactor: remove unnecessary import
EndymionJkb Feb 19, 2025
2f14d8b
Merge branch 'protocol-fee-controller-v2' into fee-controller-script
EndymionJkb Feb 19, 2025
27da6d2
Merge branch 'main' into protocol-fee-controller-v2
EndymionJkb Feb 19, 2025
d52eb97
Merge branch 'protocol-fee-controller-v2' into fee-controller-script
EndymionJkb Feb 19, 2025
af318e8
Merge branch 'main' into fee-controller-script
EndymionJkb Feb 20, 2025
b62843d
refactor: migratePools is now permissionless
EndymionJkb Feb 20, 2025
1510a2d
Merge branch 'main' into fee-controller-script
EndymionJkb Feb 21, 2025
9eff06c
temp: to accommodate fork tests, allow the fee controller to be set l…
EndymionJkb Feb 21, 2025
2086a6b
refactor: reduce to a single migration script
EndymionJkb Feb 22, 2025
e3dae99
refactor: tolerate missing pool creator fee percentage getters
EndymionJkb Feb 22, 2025
7a33dd2
Merge branch 'protocol-fee-controller-v3' into fee-controller-script
EndymionJkb Feb 22, 2025
940dcaa
docs: clarify comments
EndymionJkb Feb 22, 2025
b1edecc
Merge branch 'main' into fee-controller-script
EndymionJkb Feb 28, 2025
991f2d7
remove temporary debugging code
EndymionJkb Feb 28, 2025
a18f9c3
Merge branch 'main' into fee-controller-script
EndymionJkb Mar 6, 2025
b4120b8
Merge branch 'main' into fee-controller-script
EndymionJkb Mar 7, 2025
329d986
docs: remove blank line
EndymionJkb Mar 8, 2025
db892cb
docs: fix comment
EndymionJkb Mar 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
refactor: remove directory that generates strange errors
EndymionJkb committed Feb 17, 2025
commit eed79d8a89698730eea10db70284b4276f84f987
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ pragma solidity ^0.8.24;
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 { PoolRoleAccounts, PoolConfig } from "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol";
import { IVaultAdmin } from "@balancer-labs/v3-interfaces/contracts/vault/IVaultAdmin.sol";
import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol";

import { SingletonAuthentication } from "@balancer-labs/v3-vault/contracts/SingletonAuthentication.sol";
@@ -14,7 +15,7 @@ import {
} from "@balancer-labs/v3-solidity-utils/contracts/openzeppelin/ReentrancyGuardTransient.sol";
import { FixedPoint } from "@balancer-labs/v3-solidity-utils/contracts/math/FixedPoint.sol";

import { IBasicAuthorizer } from "../IBasicAuthorizer.sol";
import { IBasicAuthorizer } from "./IBasicAuthorizer.sol";

/**
* @notice Migrate from the original ProtocolFeeController to one with extra events.
@@ -32,6 +33,8 @@ import { IBasicAuthorizer } from "../IBasicAuthorizer.sol";
* When all pools have been migrated, call `finalizeMigration` to disable further migration and renounce all
* permissions. While `migratePools` is permissionless, this call must be permissioned to prevent premature
* termination in case multiple transactions are required to migrate all the pools.
*
* Associated with `20250221-protocol-fee-controller-migration`.
*/
contract ProtocolFeeControllerMigration is SingletonAuthentication, ReentrancyGuardTransient {
using FixedPoint for uint256;
@@ -47,14 +50,6 @@ contract ProtocolFeeControllerMigration is SingletonAuthentication, ReentrancyGu
// IAuthorizer with interface for granting/revoking roles.
IBasicAuthorizer internal immutable _authorizer;

// ActionId for withdrawing protocol fees.
bytes32 internal immutable _withdrawProtocolFeesRole;
// ActionId for initializing pool creator data for pools.
bytes32 internal immutable _initializePoolRole;

// Set when the global (pool-independent) data have been migrated.
bool private _percentagesMigrated;

// Set when the operation is complete and all permissions have been renounced.
bool private _finalized;

@@ -72,68 +67,76 @@ contract ProtocolFeeControllerMigration is SingletonAuthentication, ReentrancyGu
error InvalidFeeRecipient();

constructor(
IVault _vault,
IProtocolFeeController _oldFeeController,
IProtocolFeeController _newFeeController,
address _feeRecipient
) SingletonAuthentication(_oldFeeController.vault()) {
vault = _oldFeeController.vault();
IVault oldControllerVault = _oldFeeController.vault();

// Ensure valid fee controllers.
if (_newFeeController.vault() != _oldFeeController.vault()) {
if (_newFeeController.vault() != oldControllerVault || _vault != oldControllerVault) {
revert InvalidFeeController();
}

if (_feeRecipient == address(0)) {
revert InvalidFeeRecipient();
}

vault = _vault;
oldFeeController = _oldFeeController;
newFeeController = _newFeeController;
feeRecipient = _feeRecipient;

_authorizer = IBasicAuthorizer(address(vault.getAuthorizer()));

// Allow withdrawing protocol fees from the old controller to the new controller (during pool migrations).
_withdrawProtocolFeesRole = IAuthentication(address(oldFeeController)).getActionId(
IProtocolFeeController.withdrawProtocolFees.selector
);

// This function is not in the public interface.
_initializePoolRole = IAuthentication(address(newFeeController)).getActionId(
ProtocolFeeController.initializePool.selector
);

_authorizer.grantRole(_withdrawProtocolFeesRole, address(this));
_authorizer.grantRole(_initializePoolRole, address(this));
}

function migratePools(address[] memory pools) external nonReentrant {
function migrateFeeController(address[] memory pools) external nonReentrant {
if (_finalized) {
revert AlreadyMigrated();
}

// Do this first (and only once).
if (_percentagesMigrated == false) {
_percentagesMigrated = true;
_finalized = true;

_migrateGlobalPercentages();
}
_migrateGlobalPercentages();

// Allow withdrawing protocol fees from the old controller to the new controller (during pool migrations).
bytes32 withdrawProtocolFeesRole = IAuthentication(address(oldFeeController)).getActionId(
IProtocolFeeController.withdrawProtocolFees.selector
);

_authorizer.grantRole(withdrawProtocolFeesRole, address(this));

// This simple migration assumes that:
// 1) There are no pool creators, so no state related to pool creator fees (and no fees to be withdrawn).
// 2) There are no protocol fee exempt pools or governance overrides
// (i.e., all override flags are false, and all pool fees match current global values).
//
// At the end of this process, since there are no pool creators, token balances should all be zero.
// This allows for some fee collection after deployment of this contract and before migration, which is
// why the migrator forces collection and withdraws them, so that no further interaction with the old
// fee controller is necessary after migration.
//
// For future migrations, when we might have pool creator fees, the pool creators would still need to withdraw
// them from the old controller themselves.
for (uint256 i = 0; i < pools.length; ++i) {
_migratePool(pools[i]);
}
}
address pool = pools[i];

function finalizeMigration() external authenticate {
// Check for consistency, though finalizing multiple times shouldn't hurt anything.
if (_finalized) {
revert AlreadyMigrated();
// Force collection of fees, and withdraw them to the fee recipient.
// Pool creators will still need to withdraw from the old pool controller.
oldFeeController.collectAggregateFees(pool);
oldFeeController.withdrawProtocolFees(pool, feeRecipient);

// Set pool-specific values. This assumes there are no fee exempt pools or overrides.
newFeeController.updateProtocolSwapFeePercentage(pool);
newFeeController.updateProtocolYieldFeePercentage(pool);
}

_finalized = true;
// Remove all permissions.
_authorizer.renounceRole(withdrawProtocolFeesRole, address(this));

_authorizer.renounceRole(_withdrawProtocolFeesRole, address(this));
_authorizer.renounceRole(_initializePoolRole, address(this));
// Update the fee controller in the Vault.
_migrateFeeController();

_authorizer.renounceRole(_authorizer.DEFAULT_ADMIN_ROLE(), address(this));
}
@@ -163,50 +166,15 @@ contract ProtocolFeeControllerMigration is SingletonAuthentication, ReentrancyGu
_authorizer.renounceRole(yieldFeeRole, address(this));
}

function _migratePool(address pool) private {
// Force collection of fees, and withdraw them to the fee recipient.
// Pool creators will still need to withdraw from the old pool controller.
oldFeeController.collectAggregateFees(pool);
oldFeeController.withdrawProtocolFees(pool, feeRecipient);

// Copy pool-specific fee percentages.
// If we know for sure there are no pool creators, we can use the setProtocolFee messages and not have to do
// the wacky initialization thing. However, what will we do in the future? I guess we still need it.
(uint256 protocolSwapFeePercentage, bool swapFeeIsOverride) = oldFeeController.getPoolProtocolSwapFeeInfo(pool);
(uint256 protocolYieldFeePercentage, bool yieldFeeIsOverride) = oldFeeController.getPoolProtocolYieldFeeInfo(pool);

PoolRoleAccounts memory roleAccounts = vault.getPoolRoleAccounts(pool);
uint256 poolCreatorSwapFeePercentage;
uint256 poolCreatorYieldFeePercentage;

if (roleAccounts.poolCreator != address(0)) {
// There is no getter in the original protocol fee controller for the pool creator fees, so we must reverse
// engineer them. Future versions will be able to read the values directly (e.g., from
// `getPoolCreatorSwapFeePercentage` instead of needing to calculate them.
//
// aggregateFeePercentage =
// protocolFeePercentage +
// protocolFeePercentage.complement() * poolCreatorFeePercentage;
//
// So, poolCreatorFeePercentage = (aggregateFeePercentage - protocolFeePercentage) / protocolFeePercentage.complement()
PoolConfig memory poolConfig = vault.getPoolConfig(pool);

poolCreatorSwapFeePercentage = (poolConfig.aggregateSwapFeePercentage - protocolSwapFeePercentage)
.divDown(protocolSwapFeePercentage.complement());
poolCreatorYieldFeePercentage = (poolConfig.aggregateYieldFeePercentage - protocolYieldFeePercentage)
.divDown(protocolYieldFeePercentage.complement());
}

// Alternative registration required for migration, since there is normally no way to set pool creator
// information after registration by the Vault during pool deployment.
ProtocolFeeController(address(newFeeController)).initializePool(
pool,
protocolSwapFeePercentage,
protocolYieldFeePercentage,
swapFeeIsOverride,
yieldFeeIsOverride,
poolCreatorSwapFeePercentage,
poolCreatorYieldFeePercentage
function _migrateFeeController() internal {
bytes32 setFeeControllerRole = IAuthentication(address(vault)).getActionId(
IVaultAdmin.setProtocolFeeController.selector
);

_authorizer.grantRole(setFeeControllerRole, address(this));

vault.setProtocolFeeController(newFeeController);

_authorizer.renounceRole(setFeeControllerRole, address(this));
}
}