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

Balancer pause helper #1211

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
f890de9
add first iteration
elshan-eth Jan 6, 2025
9755e8b
add tests
elshan-eth Jan 6, 2025
ddc05ff
add tests
elshan-eth Jan 7, 2025
dfa1ba3
fix codestyle
elshan-eth Jan 7, 2025
604ec24
remove safe
elshan-eth Jan 8, 2025
4792b3b
Update pkg/standalone-utils/contracts/PauseHelper.sol
elshan-eth Jan 14, 2025
eaa8665
Update pkg/standalone-utils/contracts/PauseHelper.sol
elshan-eth Jan 14, 2025
b87af61
Update pkg/standalone-utils/contracts/PauseHelper.sol
elshan-eth Jan 14, 2025
3adf961
Update pkg/standalone-utils/contracts/PauseHelper.sol
elshan-eth Jan 14, 2025
a2dd9dd
Update pkg/standalone-utils/contracts/PauseHelper.sol
elshan-eth Jan 14, 2025
ebabee2
Update pkg/standalone-utils/contracts/PauseHelper.sol
elshan-eth Jan 14, 2025
7f330c4
Update pkg/standalone-utils/contracts/PauseHelper.sol
elshan-eth Jan 14, 2025
8fe6eaa
fist part of fixes
elshan-eth Jan 14, 2025
afe4dbd
Merge branch 'balancer-pause-helper' of https://github.com/balancer/b…
elshan-eth Jan 14, 2025
f180ea6
fixes
elshan-eth Jan 14, 2025
fa109d8
Update pkg/standalone-utils/contracts/PauseHelper.sol
elshan-eth Jan 20, 2025
3ac8cd7
Update pkg/standalone-utils/contracts/PauseHelper.sol
elshan-eth Jan 20, 2025
6ddf620
Update pkg/standalone-utils/contracts/PauseHelper.sol
elshan-eth Jan 20, 2025
9216f1c
Update pkg/standalone-utils/contracts/PauseHelper.sol
elshan-eth Jan 20, 2025
487fb9f
Update pkg/standalone-utils/contracts/PauseHelper.sol
elshan-eth Jan 20, 2025
b6b7bff
Update pkg/standalone-utils/contracts/PauseHelper.sol
elshan-eth Jan 20, 2025
ae91018
Update pkg/standalone-utils/contracts/PauseHelper.sol
elshan-eth Jan 20, 2025
ed2717b
small fixes
elshan-eth Jan 20, 2025
493cc3e
Merge branch 'balancer-pause-helper' of https://github.com/balancer/b…
elshan-eth Jan 20, 2025
2e31465
small fixes
elshan-eth Jan 20, 2025
82a8b59
Fix rounding of 2CLP pools (#1193)
joaobrunoah Jan 8, 2025
5d379f3
Medusa swap tests (#1167)
elshan-eth Jan 8, 2025
5330ae6
Alternative LBP initialization (#1210)
jubeira Jan 8, 2025
05008fa
Restructuring LiquidityApproximation and E2ESwap tests (#1080)
joaobrunoah Jan 10, 2025
4431f7a
Merge branch 'main' into balancer-pause-helper
EndymionJkb Jan 29, 2025
a3ecf67
fix: merge conflict
EndymionJkb Jan 29, 2025
7d430ee
small fixes
elshan-eth Feb 10, 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
98 changes: 98 additions & 0 deletions pkg/standalone-utils/contracts/PauseHelper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// SPDX-License-Identifier: GPL-3.0-or-later

pragma solidity ^0.8.24;

import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

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

contract PauseHelper is SingletonAuthentication {
using EnumerableSet for EnumerableSet.AddressSet;

event PoolAdded(address pool);
event PoolRemoved(address pool);

EnumerableSet.AddressSet private _poolsSet;

constructor(IVault vault) SingletonAuthentication(vault) {}

// -------------------------- Manage Pools --------------------------

/**
* @notice Add pools to the list of pools that can be paused
* @param newPools List of pools to add
*/
function addPools(address[] calldata newPools) external authenticate {
uint256 length = newPools.length;

for (uint256 i = 0; i < length; i++) {
_poolsSet.add(newPools[i]);

emit PoolAdded(newPools[i]);
}
}

/**
* @notice Remove pools from the list of pools that can be paused
* @param pools List of pools to remove
*/
function removePools(address[] memory pools) public authenticate {
uint256 length = pools.length;
for (uint256 i = 0; i < length; i++) {
_poolsSet.remove(pools[i]);

emit PoolRemoved(pools[i]);
}
}

/**
* @notice Pause pools
* @param pools List of pools to pause
*/
function pause(address[] memory pools) public authenticate {
uint256 length = pools.length;
for (uint256 i = 0; i < length; i++) {
require(_poolsSet.contains(pools[i]), "Pool is not in the list of pools");

getVault().pausePool(pools[i]);
}
}

// -------------------------- Getters --------------------------
/**
* @notice Get the number of pools
* @return Number of pools
*/
function getPoolsCount() external view returns (uint256) {
return _poolsSet.length();
}

/**
* @notice Check if a pool is in the list of pools
* @param pool Pool to check
* @return True if the pool is in the list, false otherwise
*/
function hasPool(address pool) external view returns (bool) {
return _poolsSet.contains(pool);
}

/**
* @notice Get a range of pools
* @param from Start index
* @param to End index
* @return pools List of pools
*/
function getPools(uint256 from, uint256 to) public view returns (address[] memory pools) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we also want a getPoolAt(uint256 index)?
Could also have a getPools() returns (address[] memory pools) that just returns them all.

That way it supports 3 ways of using it:

  1. simple getPools() if you know there isn't a pagination issue;
  2. generic iteration: for(i = 0; i < getPoolsCount(); ++i) { address pool = getPoolAt(i); }
  3. pagination if needed, using getPools(from, to);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't come up with a reason why getPoolAt(uint256 index) might be needed. It seems unnecessary to me.

As for getPools(), I think that if it can potentially break at some point, it's better not to implement such a function.

uint256 poolLength = _poolsSet.length();
require(from <= to, "'From' must be less than 'to'");
require(to <= poolLength, "'To' must be less than or eq the number of pools");
require(from < poolLength, "'From' must be less than the number of pools");

pools = new address[](to - from);
for (uint256 i = from; i < to; i++) {
pools[i - from] = _poolsSet.at(i);
}
}
}
3 changes: 2 additions & 1 deletion pkg/standalone-utils/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ remappings = [
'@openzeppelin/=../../node_modules/@openzeppelin/',
'permit2/=../../node_modules/permit2/',
'@balancer-labs/=../../node_modules/@balancer-labs/',
'forge-gas-snapshot/=../../node_modules/forge-gas-snapshot/src/'
'forge-gas-snapshot/=../../node_modules/forge-gas-snapshot/src/',
'@safe-global/safe-contracts/=../../node_modules/@safe-global/safe-contracts/'
]
optimizer = true
optimizer_runs = 999
Expand Down
134 changes: 134 additions & 0 deletions pkg/standalone-utils/test/foundry/PauseHelper.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// SPDX-License-Identifier: GPL-2.0-or-later

pragma solidity ^0.8.24;

import { PoolMock } from "@balancer-labs/v3-vault/contracts/test/PoolMock.sol";
import { PoolFactoryMock } from "@balancer-labs/v3-vault/contracts/test/PoolFactoryMock.sol";
import { BaseVaultTest } from "@balancer-labs/v3-vault/test/foundry/utils/BaseVaultTest.sol";
import { IAuthentication } from "@balancer-labs/v3-interfaces/contracts/solidity-utils/helpers/IAuthentication.sol";

import { PauseHelper } from "../../contracts/PauseHelper.sol";

contract PauseHelperTest is BaseVaultTest {
PauseHelper pauseHelper;

function setUp() public virtual override {
BaseVaultTest.setUp();

address[] memory owners = new address[](1);
owners[0] = address(this);

pauseHelper = new PauseHelper(vault);

authorizer.grantRole(pauseHelper.getActionId(pauseHelper.addPools.selector), address(this));
authorizer.grantRole(pauseHelper.getActionId(pauseHelper.removePools.selector), address(this));
authorizer.grantRole(pauseHelper.getActionId(pauseHelper.pause.selector), address(this));

authorizer.grantRole(vault.getActionId(vault.pausePool.selector), address(pauseHelper));
}

function testAddPoolsWithTwoBatches() public {
address[] memory firstPools = _addPools(10);
assertEq(pauseHelper.getPoolsCount(), firstPools.length, "Pools count should be 10");

address[] memory secondPools = _addPools(10);
assertEq(pauseHelper.getPoolsCount(), firstPools.length + secondPools.length, "Pools count should be 20");

for (uint256 i = 0; i < firstPools.length; i++) {
assertTrue(pauseHelper.hasPool(firstPools[i]));
}

for (uint256 i = 0; i < secondPools.length; i++) {
assertTrue(pauseHelper.hasPool(secondPools[i]));
}
}

function testAddPoolWithoutPermission() public {
authorizer.revokeRole(pauseHelper.getActionId(pauseHelper.addPools.selector), address(this));

vm.expectRevert(IAuthentication.SenderNotAllowed.selector);
pauseHelper.addPools(new address[](0));
}

function testRemovePools() public {
address[] memory pools = _addPools(10);
assertEq(pauseHelper.getPoolsCount(), 10, "Pools count should be 10");

for (uint256 i = 0; i < pools.length; i++) {
vm.expectEmit();
emit PauseHelper.PoolRemoved(pools[i]);
}

pauseHelper.removePools(pools);

assertEq(pauseHelper.getPoolsCount(), 0, "Pools count should be 0");

for (uint256 i = 0; i < pools.length; i++) {
assertFalse(pauseHelper.hasPool(pools[i]));
}
}

function testRemovePoolWithoutPermission() public {
address[] memory pools = _addPools(10);

authorizer.revokeRole(pauseHelper.getActionId(pauseHelper.removePools.selector), address(this));

vm.expectRevert(IAuthentication.SenderNotAllowed.selector);
pauseHelper.removePools(pools);
}

function testPause() public {
address[] memory pools = _addPools(10);

pauseHelper.pause(pools);

for (uint256 i = 0; i < pools.length; i++) {
assertTrue(vault.isPoolPaused(pools[i]), "Pool should be paused");
}
}

function testPauseWithoutPermission() public {
address[] memory pools = _addPools(10);

authorizer.revokeRole(pauseHelper.getActionId(pauseHelper.pause.selector), address(this));

vm.expectRevert(IAuthentication.SenderNotAllowed.selector);
pauseHelper.pause(pools);
}

function testPauseIfPoolIsNotInList() public {
_addPools(10);

vm.expectRevert("Pool is not in the list of pools");
pauseHelper.pause(new address[](1));
}

function testGetPools() public {
address[] memory pools = _addPools(10);
address[] memory storedPools = pauseHelper.getPools(0, 10);

for (uint256 i = 0; i < pools.length; i++) {
assertEq(pools[i], storedPools[i], "Stored pool should be the same as the added pool");
}
}

function _addPools(uint256 length) internal returns (address[] memory pools) {
pools = new address[](length);
for (uint256 i = 0; i < length; i++) {
pools[i] = PoolFactoryMock(poolFactory).createPool("Test", "TEST");
PoolFactoryMock(poolFactory).registerTestPool(
pools[i],
vault.buildTokenConfig(tokens),
poolHooksContract,
lp
);
}

for (uint256 i = 0; i < pools.length; i++) {
vm.expectEmit();
emit PauseHelper.PoolAdded(pools[i]);
}

pauseHelper.addPools(pools);
}
}
Loading