@@ -4,151 +4,241 @@ pragma solidity ^0.8.0;
44import { AbstractSafeModule } from "./AbstractSafeModule.sol " ;
55
66interface 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.
2522contract 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}
0 commit comments