From bf43cc908704c6e515afa59cc1b32eb78f6f01cd Mon Sep 17 00:00:00 2001 From: Tom French <15848336+TomAFrench@users.noreply.github.com> Date: Tue, 22 Mar 2022 17:59:16 +0000 Subject: [PATCH] veBALDeploymentCoordinator gauge deployment (#1187) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: fill out MockGaugeController to satisfy IGaugeController * feat: deploy Ethereum gauges and add them to GaugeController * feat: add a check to prevent attempting to create 2 gauges for a single pool * feat: add deployments for single recipient gauges * docs: update comment on deployment of single recipient gauges * style: linting * style: linting fixes * Apply suggestions from code review Co-authored-by: Nicolás Venturo Co-authored-by: Nicolás Venturo --- .../veBALDeploymentCoordinator.sol | 107 ++++++++++++++++-- .../contracts/test/MockGaugeController.sol | 9 +- pkg/liquidity-mining/test/GaugeAdder.test.ts | 2 +- 3 files changed, 103 insertions(+), 15 deletions(-) diff --git a/pkg/governance-scripts/contracts/20220322-veBAL-activation/veBALDeploymentCoordinator.sol b/pkg/governance-scripts/contracts/20220322-veBAL-activation/veBALDeploymentCoordinator.sol index 251dccd409..25403b761c 100644 --- a/pkg/governance-scripts/contracts/20220322-veBAL-activation/veBALDeploymentCoordinator.sol +++ b/pkg/governance-scripts/contracts/20220322-veBAL-activation/veBALDeploymentCoordinator.sol @@ -38,6 +38,14 @@ interface ICurrentAuthorizer is IAuthorizer { function revokeRole(bytes32 role, address account) external; } +interface IEthereumLiquidityGaugeFactory is ILiquidityGaugeFactory { + function deploy(address pool) external returns (ILiquidityGauge); +} + +interface ISingleRecipientLiquidityGaugeFactory is ILiquidityGaugeFactory { + function deploy(address recipient) external returns (ILiquidityGauge); +} + // https://vote.balancer.fi/#/proposal/0x9fe19c491cf90ed2e3ed9c15761c43d39fd1fb732a940aba8058ff69787ee90a // solhint-disable-next-line contract-name-camelcase contract veBALDeploymentCoordinator is ReentrancyGuard { @@ -48,6 +56,12 @@ contract veBALDeploymentCoordinator is ReentrancyGuard { IBalancerToken private immutable _balancerToken; IBalancerMinter private immutable _balancerMinter; IGaugeController private immutable _gaugeController; + IGaugeAdder private immutable _gaugeAdder; + IEthereumLiquidityGaugeFactory private immutable _ethereumGaugeFactory; + ISingleRecipientLiquidityGaugeFactory private immutable _singleRecipientGaugeFactory; + + address[] private _initialPools; + address[4] private _recipients; enum DeploymentStage { PENDING, FIRST_STAGE_DONE, SECOND_STAGE_DONE } @@ -67,9 +81,23 @@ contract veBALDeploymentCoordinator is ReentrancyGuard { constructor( IBalancerMinter balancerMinter, IAuthorizerAdaptor authorizerAdaptor, + IGaugeAdder gaugeAdder, + IEthereumLiquidityGaugeFactory ethereumGaugeFactory, + ISingleRecipientLiquidityGaugeFactory singleRecipientGaugeFactory, + address[] memory initialPools, + address[4] memory recipients, uint256 activationScheduledTime, uint256 secondStageDelay ) { + // Only a single gauge may exist for a given pool so repeated pool addresses + // will cause the activation to fail + uint256 poolsLength = initialPools.length; + for (uint256 i = 1; i < poolsLength; i++) { + _require(initialPools[i - 1] < initialPools[i], Errors.UNSORTED_ARRAY); + } + // We do not apply a similar protection for `recipients` as they must be sorted + // to match the desired gauge types (LMCommittee, veBAL, Polygon, Arbitrum) + _currentDeploymentStage = DeploymentStage.PENDING; IBalancerTokenAdmin balancerTokenAdmin = balancerMinter.getBalancerTokenAdmin(); @@ -80,6 +108,12 @@ contract veBALDeploymentCoordinator is ReentrancyGuard { _balancerToken = balancerTokenAdmin.getBalancerToken(); _balancerMinter = balancerMinter; _gaugeController = IGaugeController(balancerMinter.getGaugeController()); + _gaugeAdder = gaugeAdder; + _ethereumGaugeFactory = ethereumGaugeFactory; + _singleRecipientGaugeFactory = singleRecipientGaugeFactory; + + _initialPools = initialPools; + _recipients = recipients; _activationScheduledTime = activationScheduledTime; _secondStageDelay = secondStageDelay; @@ -187,19 +221,67 @@ contract veBALDeploymentCoordinator is ReentrancyGuard { authorizer.revokeRole(authorizerAdaptor.getActionId(IGaugeController.add_type.selector), address(this)); } - // Step 4: create gauges for the single-gauge gauge types + // Step 4: setup the GaugeAdder contract to be in charge of adding gauges to the Gauge Controller. + // + // The GaugeAdder contract performs checks on addresses being added to the Gauge Controller to ensure + // that they have been deployed by a factory contract which has been associated with the gauge type + // to which the proposed gauge is being added. This is intended to prevent common mistakes when adding gauges. + + authorizer.grantRole(authorizerAdaptor.getActionId(IGaugeController.add_gauge.selector), address(_gaugeAdder)); + + // Step 5: create gauges for a preselected list of pools on Ethereum. + + // Allowlist the provided LiquidityGaugeFactory on the GaugeAdder + // so its gauges may be added to the "Ethereum" gauge type. + { + authorizer.grantRole(_gaugeAdder.getActionId(IGaugeAdder.addGaugeFactory.selector), address(this)); + + _gaugeAdder.addGaugeFactory(_ethereumGaugeFactory, IGaugeAdder.GaugeType.Ethereum); + + authorizer.revokeRole(_gaugeAdder.getActionId(IGaugeAdder.addGaugeFactory.selector), address(this)); + } + + // Deploy initial gauges and add them to the Gauge Controller + { + authorizer.grantRole(_gaugeAdder.getActionId(IGaugeAdder.addEthereumGauge.selector), address(this)); + + uint256 poolsLength = _initialPools.length; + for (uint256 i = 0; i < poolsLength; i++) { + ILiquidityGauge gauge = _ethereumGaugeFactory.deploy(_initialPools[i]); + _gaugeAdder.addEthereumGauge(address(gauge)); + } + + authorizer.revokeRole(_gaugeAdder.getActionId(IGaugeAdder.addEthereumGauge.selector), address(this)); + } + + // Step 6: create gauges for the single-recipient gauge types // - // The LM committee and veBAL gauge types will have a single gauge of the single-recipient gauge kind + // The LM committee gauge will be remain as a SingleRecipientGauge permanently, + // however the gauges for veBAL, Polygon and Arbitrum types are temporary pending an automated solution. + // These three gauges will in time be retired (killed) and replaced with new gauge implementations + // which automate the distribution of BAL to BPT stakers on other networks and veBAL holders. + { + authorizer.grantRole(authorizerAdaptor.getActionId(IGaugeController.add_gauge.selector), address(this)); + + // Permanent + ILiquidityGauge LMCommitteeGauge = _singleRecipientGaugeFactory.deploy(_recipients[0]); + _addGauge(LMCommitteeGauge, IGaugeAdder.GaugeType.LiquidityMiningCommittee); - // grant self power to add gauges - // deploy lm, vebal, merkle gauges. add gauges to gauge controller + // Temporary + ILiquidityGauge tempVeBALGauge = _singleRecipientGaugeFactory.deploy(_recipients[1]); + _addGauge(tempVeBALGauge, IGaugeAdder.GaugeType.veBAL); - // grant gauge adder authority over controller - // grant self power over guage adder - // add l1 and l2 factories to gauge adder - // deploy l1 and l2 gauges, add to controller via gauge adder + // Temporary + ILiquidityGauge tempPolygonGauge = _singleRecipientGaugeFactory.deploy(_recipients[2]); + _addGauge(tempPolygonGauge, IGaugeAdder.GaugeType.Polygon); + + // Temporary + ILiquidityGauge tempArbitrumGauge = _singleRecipientGaugeFactory.deploy(_recipients[3]); + _addGauge(tempArbitrumGauge, IGaugeAdder.GaugeType.Arbitrum); + + authorizer.revokeRole(authorizerAdaptor.getActionId(IGaugeController.add_gauge.selector), address(this)); + } - // add tokens to l1 and l2 gauges // grant batch relayer permissions firstStageActivationTime = block.timestamp; @@ -242,6 +324,13 @@ contract veBALDeploymentCoordinator is ReentrancyGuard { _currentDeploymentStage = DeploymentStage.SECOND_STAGE_DONE; } + function _addGauge(ILiquidityGauge gauge, IGaugeAdder.GaugeType gaugeType) private { + getAuthorizerAdaptor().performAction( + address(_gaugeController), + abi.encodeWithSelector(IGaugeController.add_gauge.selector, gauge, gaugeType) + ); + } + function _addGaugeType(string memory name) private { getAuthorizerAdaptor().performAction( address(_gaugeController), diff --git a/pkg/liquidity-mining/contracts/test/MockGaugeController.sol b/pkg/liquidity-mining/contracts/test/MockGaugeController.sol index 34eaf1cf45..39d8987566 100644 --- a/pkg/liquidity-mining/contracts/test/MockGaugeController.sol +++ b/pkg/liquidity-mining/contracts/test/MockGaugeController.sol @@ -26,11 +26,14 @@ contract MockGaugeController is IGaugeController { mapping(address => int128) private _gaugeType; IAuthorizerAdaptor public override admin; + // solhint-disable-next-line var-name-mixedcase + IVotingEscrow public override voting_escrow; // solhint-disable-next-line func-param-name-mixedcase, var-name-mixedcase event NewGauge(address addr, int128 gauge_type, uint256 weight); - constructor(IAuthorizerAdaptor authorizerAdaptor) { + constructor(IVotingEscrow votingEscrow, IAuthorizerAdaptor authorizerAdaptor) { + voting_escrow = votingEscrow; admin = authorizerAdaptor; } @@ -54,10 +57,6 @@ contract MockGaugeController is IGaugeController { _numGaugeTypes += 1; } - function voting_escrow() external view override returns (IVotingEscrow) { - // solhint-disable-previous-line no-empty-blocks - } - function checkpoint_gauge(address) external override { // solhint-disable-previous-line no-empty-blocks } diff --git a/pkg/liquidity-mining/test/GaugeAdder.test.ts b/pkg/liquidity-mining/test/GaugeAdder.test.ts index d665c81b14..e74519ead1 100644 --- a/pkg/liquidity-mining/test/GaugeAdder.test.ts +++ b/pkg/liquidity-mining/test/GaugeAdder.test.ts @@ -37,7 +37,7 @@ describe('GaugeAdder', () => { authorizer = vault.authorizer; adaptor = await deploy('AuthorizerAdaptor', { args: [vault.address] }); - gaugeController = await deploy('MockGaugeController', { args: [adaptor.address] }); + gaugeController = await deploy('MockGaugeController', { args: [ZERO_ADDRESS, adaptor.address] }); gaugeFactory = await deploy('MockLiquidityGaugeFactory'); gaugeAdder = await deploy('GaugeAdder', { args: [gaugeController.address] });