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

Implement Curve Pool Booster. #2327

Open
wants to merge 65 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
510755d
feat: Implement Curve Pool Booster.
clement-ux Dec 16, 2024
ca8e2ff
feat: Add proxy for Curve Pool Booster.
clement-ux Dec 17, 2024
d391fce
feaat: prepare deployment file for Curve Pool Booster.
clement-ux Dec 17, 2024
b74ed90
feat: add deployment file for Curve Pool Booster.
clement-ux Dec 17, 2024
e65f962
feat: Add more test.
clement-ux Dec 18, 2024
b3de3b0
Merge remote-tracking branch 'origin/master' into clement/pool-booste…
clement-ux Dec 18, 2024
3ede65d
Revert "Merge remote-tracking branch 'origin/master' into clement/poo…
clement-ux Dec 18, 2024
fe87911
fix: bring back latest changes on master.
clement-ux Dec 18, 2024
3adc712
Fix tests
shahthepro Dec 18, 2024
b3ed12d
feat: send ETH back.
clement-ux Dec 18, 2024
330ca5d
feat: Add events.
clement-ux Dec 19, 2024
de03aa9
feat: rescue tokens.
clement-ux Dec 19, 2024
a4c63d8
feat: add possiblity to blacklist users.
clement-ux Dec 19, 2024
22b9e69
feat: add convex vecrv voter.
clement-ux Dec 19, 2024
03eb2d6
prettier
clement-ux Dec 19, 2024
8aeab0e
feat: Add fee.
clement-ux Dec 20, 2024
566f543
Merge branch 'master' of https://github.com/OriginProtocol/origin-dol…
clement-ux Dec 30, 2024
85ebeb1
Merge branch 'master' into clement/pool-booster-curve
clement-ux Jan 9, 2025
9d90521
fix: adjust deploy number.
clement-ux Jan 9, 2025
38d6dd6
Merge branch 'clement/adjust_deploy_number' into clement/pool-booster…
clement-ux Jan 9, 2025
2c31f26
Merge branch 'master' into clement/pool-booster-curve
clement-ux Jan 9, 2025
fc82bcb
Merge branch 'master' into clement/pool-booster-curve
clement-ux Jan 9, 2025
94ee9f8
fix: adjust deployment file with new Curve Pool.
clement-ux Jan 9, 2025
345438e
feat: add new curve pool/gauge.
clement-ux Jan 9, 2025
0aed8a9
fix: use `Strategizable`instead of operator.
clement-ux Jan 9, 2025
18dc920
prettier.
clement-ux Jan 9, 2025
e8445ce
fix: adjust event names.
clement-ux Jan 10, 2025
7a32e47
docs: add natspec to functions.
clement-ux Jan 13, 2025
05ca2c0
fix: prevent `feeCollector` to be address(0).
clement-ux Jan 13, 2025
f2dcaec
fix: use `call` instead of `transfer` for ETH.
clement-ux Jan 13, 2025
4de93ff
fix: use internal logic for setters.
clement-ux Jan 13, 2025
e98d5a2
Merge branch 'master' into clement/pool-booster-curve
clement-ux Jan 13, 2025
4ec38c8
fix: adjust event name.
clement-ux Jan 13, 2025
2b8e459
fix: cache balance for gas.
clement-ux Jan 13, 2025
345cbb9
fix: ensure receiver is not address(0).
clement-ux Jan 13, 2025
efc34e7
fix: type adjustment.
clement-ux Jan 13, 2025
8f14224
fix: emit event when fees are collected.
clement-ux Jan 13, 2025
aeeac7c
fix: add onlyGovernor for initialization.
clement-ux Jan 13, 2025
4cb5e60
fix: use safeApprove.
clement-ux Jan 13, 2025
3bc438c
fix: add natspec.
clement-ux Jan 17, 2025
f0b9cb4
fix: group logic.
clement-ux Jan 17, 2025
c75f256
fix: use multichain strategist.
clement-ux Jan 21, 2025
523b927
Merge remote-tracking branch 'origin/master' into clement/pool-booste…
clement-ux Jan 21, 2025
56e36aa
prettier.
clement-ux Jan 21, 2025
1a80b6d
fix: adjust deployment number.
clement-ux Jan 21, 2025
c4c2c1e
Merge remote-tracking branch 'origin/master' into clement/pool-booste…
sparrowDom Jan 23, 2025
49a88dd
fix: adjust comments.
clement-ux Jan 23, 2025
f9d4d9b
fix: change from BASE_FEE to FEE_BASE.
clement-ux Jan 23, 2025
8d33a72
fix: add reentrancy blocker.
clement-ux Jan 23, 2025
82770e6
fix: handle rebasing token for fees.
clement-ux Jan 23, 2025
b7baf9d
linter + prettier.
clement-ux Jan 23, 2025
6627d35
Add `closeCampaign()` to CurvePoolBooster. (#2360)
clement-ux Jan 23, 2025
5d19df6
feat: add extra test for Curve Pool Booster.
clement-ux Jan 23, 2025
92c2daa
prettier.
clement-ux Jan 23, 2025
7baaa08
fix: add missing`nonReentrant`.
clement-ux Jan 23, 2025
4ddbbb2
fix: approve 0 before approving.
clement-ux Jan 24, 2025
fa1af7f
fix: gas optimization.
clement-ux Jan 24, 2025
7a85a76
fix: adjust salt.
clement-ux Jan 27, 2025
4ca8b45
Ran prettier for js files
naddison36 Jan 28, 2025
bfab1e2
Fixed deployName in Arb deploy of Pool Booster
naddison36 Jan 28, 2025
33cebda
Deploy 119 and 120 - Curve Pool Booster Mainnet and Arbitrum (#2367)
clement-ux Jan 30, 2025
1248396
Merge remote-tracking branch 'origin/master' into clement/pool-booste…
naddison36 Jan 30, 2025
6997f35
feat: add safeTransfer for USDT.
clement-ux Jan 30, 2025
8647364
fix: adjust deployed address for CurvePoolBooster.
clement-ux Jan 30, 2025
a4d8f77
fix: simplidy tests.
clement-ux Jan 30, 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
37 changes: 37 additions & 0 deletions contracts/contracts/interfaces/ICampaignRemoteManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface ICampaignRemoteManager {
function createCampaign(
CampaignCreationParams memory params,
uint256 destinationChainId,
uint256 additionalGasLimit
) external payable;

function manageCampaign(
CampaignManagementParams memory params,
uint256 destinationChainId,
uint256 additionalGasLimit
) external payable;

struct CampaignCreationParams {
uint256 chainId;
address gauge;
address manager;
address rewardToken;
uint8 numberOfPeriods;
uint256 maxRewardPerVote;
uint256 totalRewardAmount;
address[] addresses;
address hook;
bool isWhitelist;
}

struct CampaignManagementParams {
uint256 campaignId;
address rewardToken;
uint8 numberOfPeriods;
uint256 totalRewardAmount;
uint256 maxRewardPerVote;
}
}
7 changes: 7 additions & 0 deletions contracts/contracts/proxies/Proxies.sol
Original file line number Diff line number Diff line change
Expand Up @@ -358,3 +358,10 @@ contract MorphoGauntletPrimeUSDTStrategyProxy is
{

}

/**
* @notice CurvePoolBoosterProxy delegates calls to a CurvePoolBooster implementation
*/
contract CurvePoolBoosterProxy is InitializeGovernedUpgradeabilityProxy {

}
318 changes: 318 additions & 0 deletions contracts/contracts/strategies/CurvePoolBooster.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,318 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { Initializable } from "../utils/Initializable.sol";
import { Strategizable } from "../governance/Strategizable.sol";
import { ICampaignRemoteManager } from "../interfaces/ICampaignRemoteManager.sol";

contract CurvePoolBooster is Initializable, Strategizable {
sparrowDom marked this conversation as resolved.
Show resolved Hide resolved
using SafeERC20 for IERC20;

////////////////////////////////////////////////////
/// --- CONSTANTS && IMMUTABLES
////////////////////////////////////////////////////
uint16 public constant BASE_FEE = 10_000; // 100%
sparrowDom marked this conversation as resolved.
Show resolved Hide resolved
address public immutable gauge;
address public immutable rewardToken;
address public immutable campaignRemoteManager;
uint256 public immutable targetChainId;

////////////////////////////////////////////////////
/// --- STORAGE
////////////////////////////////////////////////////
uint16 public fee;
address public feeCollector;
uint256 public campaignId;

////////////////////////////////////////////////////
/// --- EVENTS
////////////////////////////////////////////////////
event FeeUpdated(uint16 _newFee);
event FeeCollected(address _feeCollector, uint256 _feeAmount);
event FeeCollectorUpdated(address _newFeeCollector);
event CampaignIdUpdated(uint256 _newId);
event BribeCreated(
address gauge,
address rewardToken,
uint256 maxRewardPerVote,
uint256 totalRewardAmount
);
event TotalRewardAmountUpdated(uint256 extraTotalRewardAmount);
event NumberOfPeriodsUpdated(uint8 extraNumberOfPeriods);
event RewardPerVoteUpdated(uint256 newMaxRewardPerVote);
event TokensRescued(address token, uint256 amount, address receiver);

////////////////////////////////////////////////////
/// --- CONSTRUCTOR && INITIALIZATION
////////////////////////////////////////////////////
constructor(
uint256 _targetChainId,
address _campaignRemoteManager,
address _rewardToken,
address _gauge
) {
targetChainId = _targetChainId;
campaignRemoteManager = _campaignRemoteManager;
rewardToken = _rewardToken;
gauge = _gauge;

// Prevent implementation contract to be governed
_setGovernor(address(0));
}

function initialize(
address _strategist,
uint16 _fee,
address _feeCollector
) external onlyGovernor initializer {
_setStrategistAddr(_strategist);
_setFee(_fee);
_setFeeCollector(_feeCollector);
}

////////////////////////////////////////////////////
/// --- MUTATIVE FUNCTIONS
////////////////////////////////////////////////////
/// @notice Create a new campaign on VotemarketV2
/// @dev This will use all token available in this contract
/// @param numberOfPeriods Duration of the campaign
sparrowDom marked this conversation as resolved.
Show resolved Hide resolved
/// @param maxRewardPerVote Maximum reward per vote
sparrowDom marked this conversation as resolved.
Show resolved Hide resolved
/// @param blacklist List of addresses to exclude from the campaign
/// @param bridgeFee Fee to pay for the bridge
/// @param additionalGasLimit Additional gas limit for the bridge
function createCampaign(
sparrowDom marked this conversation as resolved.
Show resolved Hide resolved
sparrowDom marked this conversation as resolved.
Show resolved Hide resolved
uint8 numberOfPeriods,
uint256 maxRewardPerVote,
address[] calldata blacklist,
uint256 bridgeFee,
uint256 additionalGasLimit
) external onlyGovernorOrStrategist {
shahthepro marked this conversation as resolved.
Show resolved Hide resolved
require(campaignId == 0, "Campaign already created");
require(numberOfPeriods > 1, "Invalid number of periods");
require(maxRewardPerVote > 0, "Invalid reward per vote");

// Cache current rewardToken balance
sparrowDom marked this conversation as resolved.
Show resolved Hide resolved
uint256 balance = IERC20(rewardToken).balanceOf(address(this));
require(balance > 0, "No reward to manage");

// Handle fee (if any)
balance = _handleFee(balance);

// Approve the balance to the campaign manager
IERC20(rewardToken).safeApprove(campaignRemoteManager, balance);

// Create a new campaign
ICampaignRemoteManager(campaignRemoteManager).createCampaign{
value: bridgeFee
}(
ICampaignRemoteManager.CampaignCreationParams({
chainId: targetChainId,
gauge: gauge,
manager: address(this),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this the manager address on mainnet or on L2? I tried looking at the docs and couldn't find anything relevant

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The last time I discussed with the StakeDAO team was on the mainnet and L2. Let me ask again.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Confirmed by StakeDAO team, both mainnet and L2 👍

Copy link
Collaborator

@shahthepro shahthepro Jan 13, 2025

Choose a reason for hiding this comment

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

So, we need the contract at same address on both chains?

Edit: I know we won't be deploying it on L2 but will we ever need that access to that address there?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

From what I understand, no. The only reason we might need it could be to close a campaign, so 24 weeks after the campaign ends. However, as this implies token bridging and so on, that is something that's not available on the ICampaignRemoteManager.
As it should not happen really often, we can ask the StakeDAO team to close it for us.

rewardToken: rewardToken,
numberOfPeriods: numberOfPeriods,
maxRewardPerVote: maxRewardPerVote,
totalRewardAmount: balance,
addresses: blacklist,
hook: address(0),
isWhitelist: false
}),
targetChainId,
additionalGasLimit
);

emit BribeCreated(gauge, rewardToken, maxRewardPerVote, balance);
}

/// @notice Manage the total reward amount of the campaign
/// @dev This function should be called after the campaign is created
/// @dev This will use all the token available in this contract
/// @param bridgeFee Fee to pay for the bridge
/// @param additionalGasLimit Additional gas limit for the bridge
function manageTotalRewardAmount(
sparrowDom marked this conversation as resolved.
Show resolved Hide resolved
uint256 bridgeFee,
uint256 additionalGasLimit
) external onlyGovernorOrStrategist {
require(campaignId != 0, "Campaign not created");

// Cache current rewardToken balance
uint256 balance = IERC20(rewardToken).balanceOf(address(this));
require(balance > 0, "No reward to manage");

// Handle fee (if any)
balance = _handleFee(balance);

// Approve the total reward amount to the campaign manager
IERC20(rewardToken).safeApprove(campaignRemoteManager, balance);

// Manage the campaign
ICampaignRemoteManager(campaignRemoteManager).manageCampaign{
sparrowDom marked this conversation as resolved.
Show resolved Hide resolved
value: bridgeFee
}(
ICampaignRemoteManager.CampaignManagementParams({
campaignId: campaignId,
rewardToken: rewardToken,
numberOfPeriods: 0,
naddison36 marked this conversation as resolved.
Show resolved Hide resolved
totalRewardAmount: balance,
maxRewardPerVote: 0
naddison36 marked this conversation as resolved.
Show resolved Hide resolved
}),
targetChainId,
additionalGasLimit
);

emit TotalRewardAmountUpdated(balance);
}

/// @notice Manage the number of periods of the campaign
/// @dev This function should be called after the campaign is created
/// @param extraNumberOfPeriods Number of additional periods (cannot be 0)
sparrowDom marked this conversation as resolved.
Show resolved Hide resolved
/// @param bridgeFee Fee to pay for the bridge
/// @param additionalGasLimit Additional gas limit for the bridge
function manageNumberOfPeriods(
sparrowDom marked this conversation as resolved.
Show resolved Hide resolved
uint8 extraNumberOfPeriods,
uint256 bridgeFee,
uint256 additionalGasLimit
) external onlyGovernorOrStrategist {
require(campaignId != 0, "Campaign not created");
require(extraNumberOfPeriods > 0, "Invalid number of periods");

// Manage the campaign
ICampaignRemoteManager(campaignRemoteManager).manageCampaign{
value: bridgeFee
}(
ICampaignRemoteManager.CampaignManagementParams({
campaignId: campaignId,
rewardToken: rewardToken,
numberOfPeriods: extraNumberOfPeriods,
totalRewardAmount: 0,
maxRewardPerVote: 0
}),
targetChainId,
additionalGasLimit
);

emit NumberOfPeriodsUpdated(extraNumberOfPeriods);
}

/// @notice Manage the reward per vote of the campaign
/// @dev This function should be called after the campaign is created
/// @param newMaxRewardPerVote New maximum reward per vote
/// @param bridgeFee Fee to pay for the bridge
/// @param additionalGasLimit Additional gas limit for the bridge
function manageRewardPerVote(
sparrowDom marked this conversation as resolved.
Show resolved Hide resolved
uint256 newMaxRewardPerVote,
uint256 bridgeFee,
uint256 additionalGasLimit
) external onlyGovernorOrStrategist {
require(campaignId != 0, "Campaign not created");
require(newMaxRewardPerVote > 0, "Invalid reward per vote");

// Manage the campaign
ICampaignRemoteManager(campaignRemoteManager).manageCampaign{
value: bridgeFee
}(
ICampaignRemoteManager.CampaignManagementParams({
campaignId: campaignId,
rewardToken: rewardToken,
numberOfPeriods: 0,
totalRewardAmount: 0,
maxRewardPerVote: newMaxRewardPerVote
}),
targetChainId,
additionalGasLimit
);

emit RewardPerVoteUpdated(newMaxRewardPerVote);
}

/// @notice calculate the fee amount and transfer it to the feeCollector
sparrowDom marked this conversation as resolved.
Show resolved Hide resolved
/// @param amount Amount to calculate the fee
/// @return Amount after fee
function _handleFee(uint256 amount) internal returns (uint256) {
uint256 feeAmount = (amount * fee) / BASE_FEE;

// If there is a fee, transfer it to the feeCollector
if (feeAmount > 0) {
// Transfer the fee to the feeCollector
IERC20(rewardToken).transfer(feeCollector, feeAmount);
shahthepro marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Collaborator

Choose a reason for hiding this comment

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

Will we ever bribe with USDT? If so, we'll need to use safeTransfer.
If not, the Slither error can be fixed by adding this before the transfer
// slither-disable-next-line unchecked-transfer unused-return

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in this commit: 6997f35.


emit FeeCollected(feeCollector, feeAmount);

// Return the amount after fee
return amount - feeAmount;
}

// If there is no fee, return the original amount
return amount;
}

////////////////////////////////////////////////////
/// --- GOVERNANCE && OPERATION
////////////////////////////////////////////////////
/// @notice Set the campaign id
/// @dev Only callable by the governor or strategist
/// @param _campaignId New campaign id
function setCampaignId(uint256 _campaignId)
external
onlyGovernorOrStrategist
{
campaignId = _campaignId;
sparrowDom marked this conversation as resolved.
Show resolved Hide resolved
emit CampaignIdUpdated(_campaignId);
}

/// @notice Rescue ETH from the contract
/// @dev Only callable by the governor or strategist
/// @param receiver Address to receive the ETH
function rescueETH(address receiver) external onlyGovernorOrStrategist {
shahthepro marked this conversation as resolved.
Show resolved Hide resolved
sparrowDom marked this conversation as resolved.
Show resolved Hide resolved
require(receiver != address(0), "Invalid receiver");
uint256 balance = address(this).balance;
(bool success, ) = receiver.call{ value: balance }("");
require(success, "Transfer failed");
emit TokensRescued(address(0), balance, receiver);
}

/// @notice Rescue ERC20 tokens from the contract
/// @dev Only callable by the governor or strategist
/// @param token Address of the token to rescue
function rescueToken(address token, address receiver)

Check warning on line 279 in contracts/contracts/strategies/CurvePoolBooster.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/strategies/CurvePoolBooster.sol#L279

Added line #L279 was not covered by tests
sparrowDom marked this conversation as resolved.
Show resolved Hide resolved
sparrowDom marked this conversation as resolved.
Show resolved Hide resolved
external
onlyGovernor
{
require(receiver != address(0), "Invalid receiver");
uint256 balance = IERC20(token).balanceOf(address(this));
IERC20(token).transfer(receiver, balance);
emit TokensRescued(token, balance, receiver);

Check warning on line 286 in contracts/contracts/strategies/CurvePoolBooster.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/strategies/CurvePoolBooster.sol#L284-L286

Added lines #L284 - L286 were not covered by tests
}

/// @notice Set the fee
/// @dev Only callable by the governor
/// @param _fee New fee
function setFee(uint16 _fee) external onlyGovernor {
_setFee(_fee);
}

/// @notice Internal logic to set the fee
function _setFee(uint16 _fee) internal {
require(_fee <= BASE_FEE / 2, "Fee too high");
fee = _fee;
emit FeeUpdated(_fee);
}

/// @notice Set the fee collector
/// @dev Only callable by the governor
/// @param _feeCollector New fee collector
function setFeeCollector(address _feeCollector) external onlyGovernor {
_setFeeCollector(_feeCollector);
}

/// @notice Internal logic to set the fee collector
function _setFeeCollector(address _feeCollector) internal {
require(_feeCollector != address(0), "Invalid fee collector");
feeCollector = _feeCollector;
emit FeeCollectorUpdated(_feeCollector);
}

receive() external payable {}
}
Loading
Loading