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 60 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
60 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
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 ICampaingRemoteManager {
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 {

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

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Governable } from "../governance/Governable.sol";
import { Initializable } from "../utils/Initializable.sol";
import { ICampaingRemoteManager } from "../interfaces/ICampaignRemoteManager.sol";
shahthepro marked this conversation as resolved.
Show resolved Hide resolved

contract CurvePoolBooster is Initializable, Governable {
address public immutable gauge;
address public immutable rewardToken;
address public immutable campaignRemoteManager;
uint256 public immutable targetChainId;

address public operator;
uint256 public campaignId;

modifier onlyOperator() {
require(
msg.sender == operator || isGovernor(),
"Only Operator or Governor"
);
_;
}

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 _operator) external initializer {
operator = _operator;
}

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,
uint256 bridgeFee,
uint256 additionalGasLimit
) external onlyOperator {
require(campaignId == 0, "Campaign already created");

// Cache current rewardToken balance
sparrowDom marked this conversation as resolved.
Show resolved Hide resolved
uint256 totalRewardAmount = IERC20(rewardToken).balanceOf(
address(this)
);

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

// Create a new campaign
ICampaingRemoteManager(campaignRemoteManager).createCampaign{
value: bridgeFee
}(
ICampaingRemoteManager.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: totalRewardAmount,
addresses: new address[](0),
hook: address(0),
isWhitelist: false
}),
targetChainId,
additionalGasLimit
);
}

function manageTotalRewardAmount(
sparrowDom marked this conversation as resolved.
Show resolved Hide resolved
uint256 bridgeFee,
uint256 additionalGasLimit
) external onlyOperator {
require(campaignId != 0, "Campaign not created");

// Cache current rewardToken balance
uint256 extraTotalRewardAmount = IERC20(rewardToken).balanceOf(
address(this)
);

// Approve the total reward amount to the campaign manager
require(extraTotalRewardAmount > 0, "No reward to manage");

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

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

function manageNumberOfPeriods(
sparrowDom marked this conversation as resolved.
Show resolved Hide resolved
uint8 extraNumberOfPeriods,
uint256 bridgeFee,
uint256 additionalGasLimit
) external onlyOperator {
require(campaignId != 0, "Campaign not created");

ICampaingRemoteManager(campaignRemoteManager).manageCampaign{
value: bridgeFee
}(
ICampaingRemoteManager.CampaignManagementParams({
campaignId: campaignId,
rewardToken: rewardToken,
numberOfPeriods: extraNumberOfPeriods,
totalRewardAmount: 0,
maxRewardPerVote: 0
}),
targetChainId,
additionalGasLimit
);
}

function manageRewardPerVote(
sparrowDom marked this conversation as resolved.
Show resolved Hide resolved
uint256 newMaxRewardPerVote,
uint256 bridgeFee,
uint256 additionalGasLimit
) external onlyOperator {
require(campaignId != 0, "Campaign not created");

ICampaingRemoteManager(campaignRemoteManager).manageCampaign{
value: bridgeFee
}(
ICampaingRemoteManager.CampaignManagementParams({
campaignId: campaignId,
rewardToken: rewardToken,
numberOfPeriods: 0,
totalRewardAmount: 0,
maxRewardPerVote: newMaxRewardPerVote
}),
targetChainId,
additionalGasLimit
);
}

function setCampaignId(uint256 _campaignId) external onlyOperator {
campaignId = _campaignId;
sparrowDom marked this conversation as resolved.
Show resolved Hide resolved
}

function setOperator(address _newOperator) external onlyGovernor {
operator = _newOperator;

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

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/strategies/CurvePoolBooster.sol#L167

Added line #L167 was not covered by tests
sparrowDom marked this conversation as resolved.
Show resolved Hide resolved
}

function sendETH(address receiver) external onlyOperator {
payable(receiver).transfer(address(this).balance);

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

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/strategies/CurvePoolBooster.sol#L171

Added line #L171 was not covered by tests
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is this necessary? to recover any accidentally sent tokens? If yes, can you change it to onlyGovernor? Also, we typically have a transferTokens method for ERC20 tokens as well

Copy link
Contributor Author

@clement-ux clement-ux Dec 19, 2024

Choose a reason for hiding this comment

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

to recover any accidentally sent tokens?

Not really. This contract will hold a bit of ETH, as we need to pay a bit when calling the CampaignRemoteManager (CCIP fees). So it is in the situation where the pool booster will not be used anymore (end of services) and the operator what to claim the remaining ETH.

And using a payable function is not the best (imo) because a surplus of ETH is sent back to this contract, not to the sender.

What do you think of keeping onlyOperator modifier then?

Copy link
Member

Choose a reason for hiding this comment

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

IMO it is fine, as there will only be small amount of ETH on the contract whose purpose is to fuel the gas for transactions and that is not part of the user funds.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, exactly, only to fuel gas transactions, nothing more.

}

receive() external payable {}
}
1 change: 0 additions & 1 deletion contracts/contracts/strategies/Generalized4626Strategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { IERC4626 } from "../../lib/openzeppelin/interfaces/IERC4626.sol";
import { IERC20, InitializableAbstractStrategy } from "../utils/InitializableAbstractStrategy.sol";

contract Generalized4626Strategy is InitializableAbstractStrategy {

/// @dev Replaced with an immutable variable
// slither-disable-next-line constable-states
address private _deprecate_shareToken;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity ^0.8.0;
interface IUSDT {
// Tether's approve does not return a bool like standard IERC20 contracts
// slither-disable-next-line erc20-interface
function approve(address _spender, uint _value) external;
function approve(address _spender, uint256 _value) external;
}

/**
Expand All @@ -20,10 +20,9 @@ contract Generalized4626USDTStrategy is Generalized4626Strategy {
* and vaultAddress (OToken Vault contract), eg VaultProxy or OETHVaultProxy
* @param _assetToken Address of the ERC-4626 asset token. eg frxETH or DAI
*/
constructor(
BaseStrategyConfig memory _baseConfig,
address _assetToken
) Generalized4626Strategy(_baseConfig, _assetToken) {}
constructor(BaseStrategyConfig memory _baseConfig, address _assetToken)
Generalized4626Strategy(_baseConfig, _assetToken)
{}

/// @dev Override for Tether as USDT does not return a bool on approve.
/// Using assetToken.approve will fail as it expects a bool return value
Expand Down
2 changes: 1 addition & 1 deletion contracts/deploy/mainnet/112_ousd_morpho_gauntlet_usdc.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module.exports = deploymentWithGovernanceProposal(
deployName: "112_ousd_morpho_gauntlet_usdc",
forceDeploy: false,
// forceSkip: true,
// reduceQueueTime: true,
reduceQueueTime: true,
deployerIsProposer: false,
// proposalId: "",
},
Expand Down
2 changes: 1 addition & 1 deletion contracts/deploy/mainnet/113_ousd_morpho_gauntlet_usdt.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module.exports = deploymentWithGovernanceProposal(
deployName: "113_ousd_morpho_gauntlet_usdt",
forceDeploy: false,
// forceSkip: true,
// reduceQueueTime: true,
reduceQueueTime: true,
deployerIsProposer: false,
// proposalId: "",
},
Expand Down
59 changes: 59 additions & 0 deletions contracts/deploy/mainnet/114_pool_booster_curve.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
const addresses = require("../../utils/addresses");
const { deploymentWithGovernanceProposal } = require("../../utils/deploy");

module.exports = deploymentWithGovernanceProposal(
{
deployName: "114_pool_booster_curve",
forceDeploy: false,
// forceSkip: true,
reduceQueueTime: true,
deployerIsProposer: false,
proposalId: "",
},
async ({ deployWithConfirmation, withConfirmation }) => {
const { deployerAddr } = await getNamedAccounts();
const sDeployer = await ethers.provider.getSigner(deployerAddr);
console.log(`Using deployer account: ${deployerAddr}`);
// 1. Deploy proxy
const dCurvePoolBoosterProxy = await deployWithConfirmation(
"CurvePoolBoosterProxy"
);
const cCurvePoolBoosterProxy = await ethers.getContract(
"CurvePoolBoosterProxy"
);

// 2. Deploy implementation
const dCurvePoolBoosterImpl = await deployWithConfirmation(
"CurvePoolBooster",
[
42161, // Arbitrum chain id
addresses.mainnet.CampaignRemoteManager, // Campaign Remote Manager (VotemarketV2 entry point)
addresses.mainnet.OETHProxy, // To be modified with desired bribe token
addresses.mainnet.CurveOETHGauge, // To be modified with desired gauge
]
);
console.log("dCurvePoolBoosterImpl: ", dCurvePoolBoosterImpl.address);
const cCurvePoolBooster = await ethers.getContractAt(
"CurvePoolBooster",
dCurvePoolBoosterProxy.address
);

// 3. Initialize
const initData = cCurvePoolBooster.interface.encodeFunctionData(
"initialize(address)",
[deployerAddr]
);

// 4. Initialize proxy
const initFunction = "initialize(address,address,bytes)";
await withConfirmation(
cCurvePoolBoosterProxy.connect(sDeployer)[initFunction](
dCurvePoolBoosterImpl.address,
addresses.mainnet.Timelock, // governor
initData // data for delegate call to the initialize function on the strategy
)
);

return {};
}
);
12 changes: 12 additions & 0 deletions contracts/test/_fixture.js
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,17 @@ const defaultFixture = deployments.createFixture(async () => {
morphoGauntletPrimeUSDTStrategyProxy.address
);

const curvePoolBoosterProxy = isFork
? await ethers.getContract("CurvePoolBoosterProxy")
: undefined;

const curvePoolBooster = isFork
? await ethers.getContractAt(
"CurvePoolBooster",
curvePoolBoosterProxy.address
)
: undefined;

let usdt,
dai,
tusd,
Expand Down Expand Up @@ -805,6 +816,7 @@ const defaultFixture = deployments.createFixture(async () => {
morphoGauntletPrimeUSDCVault,
morphoGauntletPrimeUSDTStrategy,
morphoGauntletPrimeUSDTVault,
curvePoolBooster,

// Flux strategy
fluxStrategy,
Expand Down
Loading
Loading