Skip to content

Commit a854682

Browse files
authored
[CurvePB] Improve CurvePoolBoosterBribesModule (#2789)
* [CurvePB] Enhance pool reward calculation by adding gauge and LP token details * [CurvePB] Improve CurvePoolBoosterBribesModule - Use single `manageCampaign` call instead of 3 separate calls - Make bridge fee configurable instead of hardcoded - Send ETH from Safe via execTransactionFromModule instead of requiring pool boosters to hold ETH - Add NatSpec documentation * [CurvePB] Make manageBribes fully configurable per pool Add totalRewardAmounts and extraDuration parameters to the parameterized manageBribes overload, allowing per-pool control over reward amounts and campaign duration. The no-arg version retains the previous defaults (use all rewards, +1 period, no maxRewardPerVote update). Remove redundant onlyOperator modifier from internal _manageBribes. Update NatSpec. * [CurvePB] Rename POOLS to pools Follow Solidity naming convention: all-caps is reserved for constants and immutables. Rename mutable storage array to lowercase. Prefix function parameters with underscore to avoid shadowing. * [CurvePB] Revert when removing a pool that does not exist _removePoolBoosterAddress previously did a silent no-op when the address was not found. Now it reverts with "Pool not found" to prevent masking bugs in the caller. * [CurvePB] Add duplicate check and use calldata for pool lists Refactor _addPoolBoosterAddress to accept a single address and revert with "Pool already added" if it already exists, preventing double processing and double bridge fees. Switch addPoolBoosterAddress parameter to calldata for gas efficiency, consistent with removePoolBoosterAddress. * [CurvePB] Make additionalGasLimit configurable Replace the hardcoded 1000000 gas limit with a storage variable that can be set at construction and updated via setAdditionalGasLimit. Add corresponding event. * [CurvePB] Use calldata for manageBribes parameters Switch the parameterized manageBribes overload from memory to calldata for gas efficiency, avoiding unnecessary memory copies. * [CurvePB] Add additionalGasLimit to deployment script Pass the new constructor parameter (1000000) to match the updated CurvePoolBoosterBribesModule constructor signature. * [CurvePB] Fix lint: shorten inline comment exceeding max line length * [CurvePB] Update poolBooster task for new manageBribes signature Update the ABI and manageBribes call to match the new contract interface. Use the no-arg manageBribes() when skipRewardPerVote is true, and the 3-param overload with default totalRewardAmounts (max) and extraDuration (1) otherwise. * [CurvePB] Add zero address check in _addPoolBoosterAddress * [CurvePB] Add max value check for bridge fee * [CurvePB] Add max value check for additional gas limit * [CurvePB] Rename length to pbCount in _manageBribes * [CurvePB] Rename pools to poolBoosters to avoid confusion with AMM pools * Reorder deployment number
1 parent 423b63c commit a854682

File tree

3 files changed

+289
-115
lines changed

3 files changed

+289
-115
lines changed

contracts/contracts/automation/CurvePoolBoosterBribesModule.sol

Lines changed: 189 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -4,151 +4,241 @@ pragma solidity ^0.8.0;
44
import { AbstractSafeModule } from "./AbstractSafeModule.sol";
55

66
interface ICurvePoolBooster {
7-
function manageTotalRewardAmount(
8-
uint256 bridgeFee,
7+
function manageCampaign(
8+
uint256 totalRewardAmount,
9+
uint8 numberOfPeriods,
10+
uint256 maxRewardPerVote,
911
uint256 additionalGasLimit
10-
) external;
11-
12-
function manageNumberOfPeriods(
13-
uint8 extraNumberOfPeriods,
14-
uint256 bridgeFee,
15-
uint256 additionalGasLimit
16-
) external;
17-
18-
function manageRewardPerVote(
19-
uint256 newMaxRewardPerVote,
20-
uint256 bridgeFee,
21-
uint256 additionalGasLimit
22-
) external;
12+
) external payable;
2313
}
2414

15+
/// @title CurvePoolBoosterBribesModule
16+
/// @author Origin Protocol
17+
/// @notice Gnosis Safe module that automates the management of VotemarketV2 bribe campaigns
18+
/// across multiple CurvePoolBooster contracts. It instructs the Safe to call `manageCampaign`
19+
/// on each registered pool booster, forwarding ETH from the Safe's balance to cover
20+
/// bridge fees. Campaign parameters (reward amount, duration, reward rate) can be
21+
/// configured per pool or left to sensible defaults.
2522
contract CurvePoolBoosterBribesModule is AbstractSafeModule {
26-
address[] public POOLS;
23+
////////////////////////////////////////////////////
24+
/// --- Storage
25+
////////////////////////////////////////////////////
26+
27+
/// @notice List of CurvePoolBooster addresses managed by this module
28+
address[] public poolBoosters;
29+
30+
/// @notice ETH amount sent per pool booster to cover the L1 -> L2 bridge fee
31+
uint256 public bridgeFee;
32+
33+
/// @notice Gas limit passed to manageCampaign for cross-chain execution
34+
uint256 public additionalGasLimit;
35+
36+
////////////////////////////////////////////////////
37+
/// --- Events
38+
////////////////////////////////////////////////////
2739

40+
event BridgeFeeUpdated(uint256 newFee);
41+
event AdditionalGasLimitUpdated(uint256 newGasLimit);
2842
event PoolBoosterAddressAdded(address pool);
2943
event PoolBoosterAddressRemoved(address pool);
3044

45+
////////////////////////////////////////////////////
46+
/// --- Constructor
47+
////////////////////////////////////////////////////
48+
49+
/// @param _safeContract Address of the Gnosis Safe this module is attached to
50+
/// @param _operator Address authorized to call operator-restricted functions
51+
/// @param _poolBoosters Initial list of CurvePoolBooster addresses to manage
52+
/// @param _bridgeFee ETH amount to send per pool booster for bridge fees
53+
/// @param _additionalGasLimit Gas limit for cross-chain execution in manageCampaign
3154
constructor(
3255
address _safeContract,
3356
address _operator,
34-
address[] memory _pools
57+
address[] memory _poolBoosters,
58+
uint256 _bridgeFee,
59+
uint256 _additionalGasLimit
3560
) AbstractSafeModule(_safeContract) {
3661
_grantRole(OPERATOR_ROLE, _operator);
37-
_addPoolBoosterAddress(_pools);
62+
for (uint256 i = 0; i < _poolBoosters.length; i++) {
63+
_addPoolBoosterAddress(_poolBoosters[i]);
64+
}
65+
_setBridgeFee(_bridgeFee);
66+
_setAdditionalGasLimit(_additionalGasLimit);
3867
}
3968

40-
function addPoolBoosterAddress(address[] memory pools)
69+
////////////////////////////////////////////////////
70+
/// --- External Mutative Functions
71+
////////////////////////////////////////////////////
72+
73+
/// @notice Add new CurvePoolBooster addresses to the managed list
74+
/// @param _poolBoosters Addresses to add
75+
function addPoolBoosterAddress(address[] calldata _poolBoosters)
4176
external
4277
onlyOperator
4378
{
44-
_addPoolBoosterAddress(pools);
45-
}
46-
47-
function _addPoolBoosterAddress(address[] memory pools) internal {
48-
for (uint256 i = 0; i < pools.length; i++) {
49-
POOLS.push(pools[i]);
50-
emit PoolBoosterAddressAdded(pools[i]);
79+
for (uint256 i = 0; i < _poolBoosters.length; i++) {
80+
_addPoolBoosterAddress(_poolBoosters[i]);
5181
}
5282
}
5383

54-
function removePoolBoosterAddress(address[] calldata pools)
84+
/// @notice Remove CurvePoolBooster addresses from the managed list
85+
/// @param _poolBoosters Addresses to remove
86+
function removePoolBoosterAddress(address[] calldata _poolBoosters)
5587
external
5688
onlyOperator
5789
{
58-
for (uint256 i = 0; i < pools.length; i++) {
59-
_removePoolBoosterAddress(pools[i]);
90+
for (uint256 i = 0; i < _poolBoosters.length; i++) {
91+
_removePoolBoosterAddress(_poolBoosters[i]);
6092
}
6193
}
6294

63-
function _removePoolBoosterAddress(address pool) internal {
64-
uint256 length = POOLS.length;
65-
for (uint256 i = 0; i < length; i++) {
66-
if (POOLS[i] == pool) {
67-
POOLS[i] = POOLS[length - 1];
68-
POOLS.pop();
69-
emit PoolBoosterAddressRemoved(pool);
70-
break;
71-
}
72-
}
95+
/// @notice Update the ETH bridge fee sent per pool booster
96+
/// @param newFee New bridge fee amount in wei
97+
function setBridgeFee(uint256 newFee) external onlyOperator {
98+
_setBridgeFee(newFee);
99+
}
100+
101+
/// @notice Update the additional gas limit for cross-chain execution
102+
/// @param newGasLimit New gas limit value
103+
function setAdditionalGasLimit(uint256 newGasLimit) external onlyOperator {
104+
_setAdditionalGasLimit(newGasLimit);
73105
}
74106

107+
/// @notice Default entry point to manage bribe campaigns for all registered pool boosters.
108+
/// Applies the same behavior to every pool:
109+
/// - totalRewardAmount = type(uint256).max → use all available reward tokens
110+
/// - numberOfPeriods = 1 → extend by one period (week)
111+
/// - maxRewardPerVote = 0 → no update
112+
/// @dev Calls `manageCampaign` on each pool booster via the Safe. The Safe must hold
113+
/// enough ETH to cover `bridgeFee * poolBoosters.length`.
75114
function manageBribes() external onlyOperator {
76-
uint256[] memory rewardsPerVote = new uint256[](POOLS.length);
77-
_manageBribes(rewardsPerVote);
115+
uint256[] memory totalRewardAmounts = new uint256[](
116+
poolBoosters.length
117+
);
118+
uint8[] memory extraDuration = new uint8[](poolBoosters.length);
119+
uint256[] memory rewardsPerVote = new uint256[](poolBoosters.length);
120+
for (uint256 i = 0; i < poolBoosters.length; i++) {
121+
totalRewardAmounts[i] = type(uint256).max; // use all available rewards
122+
extraDuration[i] = 1; // extend by 1 period (week)
123+
rewardsPerVote[i] = 0; // no update to maxRewardPerVote
124+
}
125+
_manageBribes(totalRewardAmounts, extraDuration, rewardsPerVote);
78126
}
79127

80-
function manageBribes(uint256[] memory rewardsPerVote)
81-
external
82-
onlyOperator
83-
{
84-
require(POOLS.length == rewardsPerVote.length, "Length mismatch");
85-
_manageBribes(rewardsPerVote);
128+
/// @notice Fully configurable entry point to manage bribe campaigns. Allows setting
129+
/// reward amounts, durations, and reward rates individually for each pool.
130+
/// Each array must have the same length as the poolBoosters array.
131+
/// @param totalRewardAmounts Total reward amount per pool (0 = no update, type(uint256).max = use all available)
132+
/// @param extraDuration Number of periods to extend per pool (0 = no update, 1 = +1 week)
133+
/// @param rewardsPerVote Max reward per vote per pool (0 = no update)
134+
function manageBribes(
135+
uint256[] calldata totalRewardAmounts,
136+
uint8[] calldata extraDuration,
137+
uint256[] calldata rewardsPerVote
138+
) external onlyOperator {
139+
require(
140+
poolBoosters.length == totalRewardAmounts.length,
141+
"Length mismatch"
142+
);
143+
require(poolBoosters.length == extraDuration.length, "Length mismatch");
144+
require(
145+
poolBoosters.length == rewardsPerVote.length,
146+
"Length mismatch"
147+
);
148+
_manageBribes(totalRewardAmounts, extraDuration, rewardsPerVote);
86149
}
87150

88-
function _manageBribes(uint256[] memory rewardsPerVote)
89-
internal
90-
onlyOperator
91-
{
92-
uint256 length = POOLS.length;
93-
for (uint256 i = 0; i < length; i++) {
94-
address poolBoosterAddress = POOLS[i];
151+
////////////////////////////////////////////////////
152+
/// --- External View Functions
153+
////////////////////////////////////////////////////
95154

96-
// PoolBooster need to have a balance of at least 0.003 ether to operate
97-
// 0.001 ether are used for the bridge fee
98-
require(
99-
poolBoosterAddress.balance > 0.003 ether,
100-
"Insufficient balance for bribes"
101-
);
155+
/// @notice Get the full list of managed CurvePoolBooster addresses
156+
/// @return Array of pool booster addresses
157+
function getPoolBoosters() external view returns (address[] memory) {
158+
return poolBoosters;
159+
}
102160

103-
require(
104-
safeContract.execTransactionFromModule(
105-
poolBoosterAddress,
106-
0, // Value
107-
abi.encodeWithSelector(
108-
ICurvePoolBooster.manageNumberOfPeriods.selector,
109-
1, // extraNumberOfPeriods
110-
0.001 ether, // bridgeFee
111-
1000000 // additionalGasLimit
112-
),
113-
0
114-
),
115-
"Manage number of periods failed"
116-
);
161+
////////////////////////////////////////////////////
162+
/// --- Internal Functions
163+
////////////////////////////////////////////////////
164+
165+
/// @notice Internal logic to add a single pool booster address
166+
/// @dev Reverts if the address is already in the poolBoosters array
167+
/// @param _pool Address to append to the poolBoosters array
168+
function _addPoolBoosterAddress(address _pool) internal {
169+
require(_pool != address(0), "Zero address");
170+
for (uint256 j = 0; j < poolBoosters.length; j++) {
171+
require(poolBoosters[j] != _pool, "Pool already added");
172+
}
173+
poolBoosters.push(_pool);
174+
emit PoolBoosterAddressAdded(_pool);
175+
}
117176

118-
require(
119-
safeContract.execTransactionFromModule(
120-
poolBoosterAddress,
121-
0, // Value
122-
abi.encodeWithSelector(
123-
ICurvePoolBooster.manageTotalRewardAmount.selector,
124-
0.001 ether, // bridgeFee
125-
1000000 // additionalGasLimit
126-
),
127-
0
128-
),
129-
"Manage total reward failed"
130-
);
177+
/// @notice Internal logic to remove a pool booster address
178+
/// @dev Swaps the target with the last element and pops to avoid gaps
179+
/// @param pool Address to remove from the poolBoosters array
180+
function _removePoolBoosterAddress(address pool) internal {
181+
uint256 length = poolBoosters.length;
182+
for (uint256 i = 0; i < length; i++) {
183+
if (poolBoosters[i] == pool) {
184+
poolBoosters[i] = poolBoosters[length - 1];
185+
poolBoosters.pop();
186+
emit PoolBoosterAddressRemoved(pool);
187+
return;
188+
}
189+
}
190+
revert("Pool not found");
191+
}
192+
193+
/// @notice Internal logic to set the bridge fee
194+
/// @param newFee New bridge fee amount in wei
195+
function _setBridgeFee(uint256 newFee) internal {
196+
require(newFee <= 0.01 ether, "Bridge fee too high");
197+
bridgeFee = newFee;
198+
emit BridgeFeeUpdated(newFee);
199+
}
131200

132-
// Skip setting reward per vote if it's zero
133-
if (rewardsPerVote[i] == 0) continue;
201+
/// @notice Internal logic to set the additional gas limit
202+
/// @param newGasLimit New gas limit value
203+
function _setAdditionalGasLimit(uint256 newGasLimit) internal {
204+
require(newGasLimit <= 10_000_000, "Gas limit too high");
205+
additionalGasLimit = newGasLimit;
206+
emit AdditionalGasLimitUpdated(newGasLimit);
207+
}
208+
209+
/// @notice Internal logic to manage bribe campaigns for all registered pool boosters
210+
/// @dev Iterates over all pool boosters and instructs the Safe to call `manageCampaign`
211+
/// on each one, sending `bridgeFee` ETH from the Safe's balance per call.
212+
/// @param totalRewardAmounts Total reward amount per pool (0 = no update, type(uint256).max = use all available)
213+
/// @param extraDuration Number of periods to extend per pool (0 = no update)
214+
/// @param rewardsPerVote Max reward per vote per pool (0 = no update)
215+
function _manageBribes(
216+
uint256[] memory totalRewardAmounts,
217+
uint8[] memory extraDuration,
218+
uint256[] memory rewardsPerVote
219+
) internal {
220+
uint256 pbCount = poolBoosters.length;
221+
require(
222+
address(safeContract).balance >= bridgeFee * pbCount,
223+
"Not enough ETH for bridge fees"
224+
);
225+
for (uint256 i = 0; i < pbCount; i++) {
226+
address poolBoosterAddress = poolBoosters[i];
134227
require(
135228
safeContract.execTransactionFromModule(
136229
poolBoosterAddress,
137-
0, // Value
230+
bridgeFee, // ETH value to cover bridge fee
138231
abi.encodeWithSelector(
139-
ICurvePoolBooster.manageRewardPerVote.selector,
140-
rewardsPerVote[i], // newMaxRewardPerVote
141-
0.001 ether, // bridgeFee
142-
1000000 // additionalGasLimit
232+
ICurvePoolBooster.manageCampaign.selector,
233+
totalRewardAmounts[i], // 0 = no update, max = use all
234+
extraDuration[i], // numberOfPeriods, 0 = no update, 1 = +1 period (week)
235+
rewardsPerVote[i], // maxRewardPerVote, 0 = no update
236+
additionalGasLimit
143237
),
144238
0
145239
),
146-
"Set reward per vote failed"
240+
"Manage campaign failed"
147241
);
148242
}
149243
}
150-
151-
function getPools() external view returns (address[] memory) {
152-
return POOLS;
153-
}
154244
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
const addresses = require("../../utils/addresses");
2+
const { deploymentWithGovernanceProposal } = require("../../utils/deploy");
3+
4+
module.exports = deploymentWithGovernanceProposal(
5+
{
6+
deployName: "173_improve_curve_pb_module",
7+
forceDeploy: false,
8+
reduceQueueTime: true,
9+
deployerIsProposer: false,
10+
proposalId: "",
11+
},
12+
async ({ deployWithConfirmation }) => {
13+
const safeAddress = addresses.multichainStrategist;
14+
15+
const moduleName = `CurvePoolBoosterBribesModule`;
16+
await deployWithConfirmation(
17+
moduleName,
18+
[
19+
safeAddress,
20+
// Defender Relayer
21+
addresses.mainnet.validatorRegistrator,
22+
[
23+
"0xFc87E0ABe3592945Ad7587F99161dBb340faa767",
24+
"0x1A43D2F1bb24aC262D1d7ac05D16823E526FcA32",
25+
"0x028C6f98C20094367F7b048F0aFA1E11ce0A8DBd",
26+
"0xc835BcA1378acb32C522f3831b8dba161a763FBE",
27+
],
28+
ethers.utils.parseEther("0.001"), // Bridge fee
29+
1000000, // Additional gas limit for cross-chain execution
30+
],
31+
"CurvePoolBoosterBribesModule"
32+
);
33+
const cCurvePoolBoosterBribesModule = await ethers.getContract(moduleName);
34+
35+
console.log(
36+
`${moduleName} (for ${safeAddress}) deployed to`,
37+
cCurvePoolBoosterBribesModule.address
38+
);
39+
40+
return {
41+
actions: [],
42+
};
43+
}
44+
);

0 commit comments

Comments
 (0)