diff --git a/contracts/abi/createx.json b/contracts/abi/createx.json new file mode 100644 index 0000000000..9e30b0e694 --- /dev/null +++ b/contracts/abi/createx.json @@ -0,0 +1,77 @@ +[ + { + "inputs": [ + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "initCode", + "type": "bytes" + } + ], + "name": "deployCreate2", + "outputs": [ + { + "internalType": "address", + "name": "newContract", + "type": "address" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "initCode", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "constructorAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "initCallAmount", + "type": "uint256" + } + ], + "internalType": "struct CreateX.Values", + "name": "values", + "type": "tuple" + }, + { + "internalType": "address", + "name": "refundAddress", + "type": "address" + } + ], + "name": "deployCreate2AndInit", + "outputs": [ + { + "internalType": "address", + "name": "newContract", + "type": "address" + } + ], + "stateMutability": "payable", + "type": "function" + } +] \ No newline at end of file diff --git a/contracts/contracts/interfaces/ICampaignRemoteManager.sol b/contracts/contracts/interfaces/ICampaignRemoteManager.sol index f2db979ea8..d4823f3692 100644 --- a/contracts/contracts/interfaces/ICampaignRemoteManager.sol +++ b/contracts/contracts/interfaces/ICampaignRemoteManager.sol @@ -5,13 +5,22 @@ interface ICampaignRemoteManager { function createCampaign( CampaignCreationParams memory params, uint256 destinationChainId, - uint256 additionalGasLimit + uint256 additionalGasLimit, + address votemarket ) external payable; function manageCampaign( CampaignManagementParams memory params, uint256 destinationChainId, - uint256 additionalGasLimit + uint256 additionalGasLimit, + address votemarket + ) external payable; + + function closeCampaign( + CampaignClosingParams memory params, + uint256 destinationChainId, + uint256 additionalGasLimit, + address votemarket ) external payable; struct CampaignCreationParams { @@ -34,4 +43,8 @@ interface ICampaignRemoteManager { uint256 totalRewardAmount; uint256 maxRewardPerVote; } + + struct CampaignClosingParams { + uint256 campaignId; + } } diff --git a/contracts/contracts/strategies/CurvePoolBooster.sol b/contracts/contracts/strategies/CurvePoolBooster.sol index 40494cd769..6e21d6b5fc 100644 --- a/contracts/contracts/strategies/CurvePoolBooster.sol +++ b/contracts/contracts/strategies/CurvePoolBooster.sol @@ -25,9 +25,6 @@ contract CurvePoolBooster is Initializable, Strategizable { /// @notice Address of the reward token address public immutable rewardToken; - /// @notice Address of the campaignRemoteManager linked to VotemarketV2 - address public immutable campaignRemoteManager; - /// @notice Chain id of the target chain uint256 public immutable targetChainId; @@ -40,22 +37,31 @@ contract CurvePoolBooster is Initializable, Strategizable { /// @notice Address of the fee collector address public feeCollector; + /// @notice Address of the campaignRemoteManager linked to VotemarketV2 + address public campaignRemoteManager; + + /// @notice Address of votemarket in L2 + address public votemarket; + /// @notice Id of the campaign created uint256 public campaignId; //////////////////////////////////////////////////// /// --- EVENTS //////////////////////////////////////////////////// - event FeeUpdated(uint16 _newFee); - event FeeCollected(address _feeCollector, uint256 _feeAmount); - event FeeCollectorUpdated(address _newFeeCollector); - event CampaignIdUpdated(uint256 _newId); - event BribeCreated( + event FeeUpdated(uint16 newFee); + event FeeCollected(address feeCollector, uint256 feeAmount); + event FeeCollectorUpdated(address newFeeCollector); + event VotemarketUpdated(address newVotemarket); + event CampaignRemoteManagerUpdated(address newCampaignRemoteManager); + event CampaignCreated( address gauge, address rewardToken, uint256 maxRewardPerVote, uint256 totalRewardAmount ); + event CampaignIdUpdated(uint256 newId); + event CampaignClosed(uint256 campaignId); event TotalRewardAmountUpdated(uint256 extraTotalRewardAmount); event NumberOfPeriodsUpdated(uint8 extraNumberOfPeriods); event RewardPerVoteUpdated(uint256 newMaxRewardPerVote); @@ -66,12 +72,10 @@ contract CurvePoolBooster is Initializable, Strategizable { //////////////////////////////////////////////////// constructor( uint256 _targetChainId, - address _campaignRemoteManager, address _rewardToken, address _gauge ) { targetChainId = _targetChainId; - campaignRemoteManager = _campaignRemoteManager; rewardToken = _rewardToken; gauge = _gauge; @@ -86,11 +90,15 @@ contract CurvePoolBooster is Initializable, Strategizable { function initialize( address _strategist, uint16 _fee, - address _feeCollector + address _feeCollector, + address _campaignRemoteManager, + address _votemarket ) external onlyGovernor initializer { _setStrategistAddr(_strategist); _setFee(_fee); _setFeeCollector(_feeCollector); + _setCampaignRemoteManager(_campaignRemoteManager); + _setVotemarket(_votemarket); } //////////////////////////////////////////////////// @@ -137,10 +145,16 @@ contract CurvePoolBooster is Initializable, Strategizable { isWhitelist: false }), targetChainId, - additionalGasLimit + additionalGasLimit, + votemarket ); - emit BribeCreated(gauge, rewardToken, maxRewardPerVote, balanceSubFee); + emit CampaignCreated( + gauge, + rewardToken, + maxRewardPerVote, + balanceSubFee + ); } /// @notice Manage the total reward amount of the campaign @@ -173,7 +187,8 @@ contract CurvePoolBooster is Initializable, Strategizable { maxRewardPerVote: 0 }), targetChainId, - additionalGasLimit + additionalGasLimit, + votemarket ); emit TotalRewardAmountUpdated(balanceSubFee); @@ -205,7 +220,8 @@ contract CurvePoolBooster is Initializable, Strategizable { maxRewardPerVote: 0 }), targetChainId, - additionalGasLimit + additionalGasLimit, + votemarket ); emit NumberOfPeriodsUpdated(extraNumberOfPeriods); @@ -236,15 +252,37 @@ contract CurvePoolBooster is Initializable, Strategizable { maxRewardPerVote: newMaxRewardPerVote }), targetChainId, - additionalGasLimit + additionalGasLimit, + votemarket ); emit RewardPerVoteUpdated(newMaxRewardPerVote); } - /// @notice Take the balance of rewards tokens owned by this contract and calculate the fee amount. - /// Transfer the fee to the feeCollector. - /// @return balance remaining balance of reward token + /// @notice Close the campaign. + /// @dev This function only work on the L2 chain. Not on mainnet. + /// @dev The _campaignId parameter is not related to the campaignId of this contract, allowing greater flexibility. + /// @param _campaignId Id of the campaign to close + function closeCampaign( + uint256 _campaignId, + uint256 bridgeFee, + uint256 additionalGasLimit + ) external onlyGovernorOrStrategist { + ICampaignRemoteManager(campaignRemoteManager).closeCampaign{ + value: bridgeFee + }( + ICampaignRemoteManager.CampaignClosingParams({ + campaignId: campaignId + }), + targetChainId, + additionalGasLimit, + votemarket + ); + emit CampaignClosed(_campaignId); + } + + /// @notice calculate the fee amount and transfer it to the feeCollector + /// @return Balance after fee function _handleFee() internal returns (uint256) { // Cache current rewardToken balance uint256 balance = IERC20(rewardToken).balanceOf(address(this)); @@ -334,5 +372,40 @@ contract CurvePoolBooster is Initializable, Strategizable { emit FeeCollectorUpdated(_feeCollector); } + /// @notice Set the campaignRemoteManager + /// @param _campaignRemoteManager New campaignRemoteManager address + function setCampaignRemoteManager(address _campaignRemoteManager) + external + onlyGovernor + { + _setCampaignRemoteManager(_campaignRemoteManager); + } + + /// @notice Internal logic to set the campaignRemoteManager + /// @param _campaignRemoteManager New campaignRemoteManager address + function _setCampaignRemoteManager(address _campaignRemoteManager) + internal + { + require( + _campaignRemoteManager != address(0), + "Invalid campaignRemoteManager" + ); + campaignRemoteManager = _campaignRemoteManager; + emit CampaignRemoteManagerUpdated(_campaignRemoteManager); + } + + /// @notice Set the votemarket address + /// @param _votemarket New votemarket address + function setVotemarket(address _votemarket) external onlyGovernor { + _setVotemarket(_votemarket); + } + + /// @notice Internal logic to set the votemarket address + function _setVotemarket(address _votemarket) internal onlyGovernor { + require(_votemarket != address(0), "Invalid votemarket"); + votemarket = _votemarket; + emit VotemarketUpdated(_votemarket); + } + receive() external payable {} } diff --git a/contracts/deploy/mainnet/119_pool_booster_curve.js b/contracts/deploy/mainnet/119_pool_booster_curve.js index 69650d1668..90f194a2cb 100644 --- a/contracts/deploy/mainnet/119_pool_booster_curve.js +++ b/contracts/deploy/mainnet/119_pool_booster_curve.js @@ -1,5 +1,11 @@ const addresses = require("../../utils/addresses"); -const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); +const { + deploymentWithGovernanceProposal, + encodeSaltForCreateX, +} = require("../../utils/deploy"); +const createxAbi = require("../../abi/createx.json"); +const PoolBoosterBytecode = require("../../artifacts/contracts/strategies/CurvePoolBooster.sol/CurvePoolBooster.json"); +const ProxyBytecode = require("../../artifacts/contracts/proxies/Proxies.sol/CurvePoolBoosterProxy.json"); module.exports = deploymentWithGovernanceProposal( { @@ -10,54 +16,107 @@ module.exports = deploymentWithGovernanceProposal( deployerIsProposer: false, proposalId: "", }, - async ({ deployWithConfirmation, withConfirmation }) => { + async ({ 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" + console.log(`Deployer address: ${deployerAddr}`); + + const cCurvePoolBooster = await ethers.getContractAt( + "CurvePoolBooster", + addresses.zero ); - const cCurvePoolBoosterProxy = await ethers.getContract( - "CurvePoolBoosterProxy" + const cCurvePoolBoosterProxy = await ethers.getContractAt( + "CurvePoolBoosterProxy", + addresses.zero ); - // 2. Deploy implementation - const dCurvePoolBoosterImpl = await deployWithConfirmation( - "CurvePoolBooster", - [ - 42161, // Arbitrum chain id - addresses.mainnet.CampaignRemoteManager, // Campaign Remote Manager (VotemarketV2 entry point) - addresses.mainnet.OUSDProxy, // Bribe token - addresses.mainnet.CurveOUSDUSDTGauge, // Gauge - ] + console.log("\nStarting deployment using CreateX"); + const rewardToken = addresses.mainnet.OUSDProxy; + const gauge = addresses.mainnet.CurveOUSDUSDTGauge; + const targetedChainId = 42161; // arbitrum + const fee = 0; + + // Get CreateX contract + const cCreateX = await ethers.getContractAt(createxAbi, addresses.createX); + + // Generate salt + const salt = ethers.utils.keccak256( + ethers.utils.concat([ + ethers.utils.arrayify(rewardToken), + ethers.utils.arrayify(gauge), + ]) ); - console.log("dCurvePoolBoosterImpl: ", dCurvePoolBoosterImpl.address); - const cCurvePoolBooster = await ethers.getContractAt( - "CurvePoolBooster", - dCurvePoolBoosterProxy.address + + const encodedSalt = encodeSaltForCreateX(deployerAddr, false, salt); + console.log(`Encoded salt: ${encodedSalt}`); + + // --- Deploy implementation --- // + const cachedInitCodeImpl = ethers.utils.concat([ + PoolBoosterBytecode.bytecode, + ethers.utils.defaultAbiCoder.encode( + ["uint256", "address", "address"], + [targetedChainId, rewardToken, gauge] + ), + ]); + const txResponse = await withConfirmation( + cCreateX.connect(sDeployer).deployCreate2(encodedSalt, cachedInitCodeImpl) + ); + const txReceipt = await txResponse.wait(); + // event 0 is GovernorshipTransferred + // event 1 is ContractCreation, topics[1] is the address of the deployed contract, topics[2] is the salt + const implementationAddress = ethers.utils.getAddress( + `0x${txReceipt.events[1].topics[1].slice(26)}` + ); + console.log( + `Curve Booster Implementation deployed at: ${implementationAddress}` ); - // 3. Initialize - const initData = cCurvePoolBooster.interface.encodeFunctionData( - "initialize(address,uint16,address)", + // --- Deploy and init proxy --- // + const cachedInitCodeProxy = ProxyBytecode.bytecode; // No constructor arguments + const initializeImplem = cCurvePoolBooster.interface.encodeFunctionData( + "initialize(address,uint16,address,address,address)", [ - addresses.base.multichainStrategist, - 0, - addresses.base.multichainStrategist, + addresses.multichainStrategist, // strategist + fee, // fee + addresses.multichainStrategist, // feeCollector + addresses.mainnet.CampaignRemoteManager, // campaignRemoteManager + addresses.votemarket, // votemarket ] ); - - // 4. Initialize proxy - const initFunction = "initialize(address,address,bytes)"; - await withConfirmation( - cCurvePoolBoosterProxy.connect(sDeployer)[initFunction]( - dCurvePoolBoosterImpl.address, + const initializeProxy = cCurvePoolBoosterProxy.interface.encodeFunctionData( + "initialize(address,address,bytes)", + [ + implementationAddress, // implementation addresses.mainnet.Timelock, // governor - initData // data for delegate call to the initialize function on the strategy - ) + initializeImplem, // init data + ] + ); + const txResponseProxy = await withConfirmation( + cCreateX + .connect(sDeployer) + .deployCreate2AndInit( + encodedSalt, + cachedInitCodeProxy, + initializeProxy, + ["0x00", "0x00"], + deployerAddr + ) + ); + const txReceiptProxy = await txResponseProxy.wait(); + // event 0 is GovernorshipTransferred + // event 1 is ContractCreation, topics[1] is the address of the deployed contract, topics[2] is the salt + // event 2 is StrategistUpdated + // event 3 is FeeUpdated + // event 4 is FeeCollectorUpdated + // event 5 is CampaignRemoteManagerUpdated + // event 6 is GovernorshipTransferred + const proxyAddress = ethers.utils.getAddress( + `0x${txReceiptProxy.events[1].topics[1].slice(26)}` ); + console.log(`Curve Booster Proxy deployed at: ${proxyAddress}`); + await ethers.getContractAt("CurvePoolBooster", proxyAddress); + await ethers.getContractAt("CurvePoolBoosterProxy", proxyAddress); return {}; } ); diff --git a/contracts/deploy/mainnet/120_pool_booster_curve_arb.js b/contracts/deploy/mainnet/120_pool_booster_curve_arb.js new file mode 100644 index 0000000000..87b14c1716 --- /dev/null +++ b/contracts/deploy/mainnet/120_pool_booster_curve_arb.js @@ -0,0 +1,125 @@ +const addresses = require("../../utils/addresses"); +const { deployOnArb } = require("../../utils/deploy-l2"); +const { encodeSaltForCreateX } = require("../../utils/deploy"); + +const createxAbi = require("../../abi/createx.json"); +const PoolBoosterBytecode = require("../../artifacts/contracts/strategies/CurvePoolBooster.sol/CurvePoolBooster.json"); +const ProxyBytecode = require("../../artifacts/contracts/proxies/Proxies.sol/CurvePoolBoosterProxy.json"); + +// --------------------------------!!! / WARNING \ !!!----------------------------------------- +// +// `encodedSalt`, ProxyBytecode and PoolBoosterBytecode shoudl be EXACTLY the same as the 119 !!! +// It is using createX to deploy contract at the SAME address as the one deployed in 119. +// +// -------------------------------------------------------------------------------------------- + +module.exports = deployOnArb( + { + deployName: "119_pool_booster_curve", + forceDeploy: false, + //forceSkip: true, + reduceQueueTime: true, + deployerIsProposer: false, + proposalId: "", + }, + async ({ withConfirmation }) => { + const { deployerAddr } = await getNamedAccounts(); + const sDeployer = await ethers.provider.getSigner(deployerAddr); + console.log(`Deployer address: ${deployerAddr}`); + + const cCurvePoolBooster = await ethers.getContractAt( + "CurvePoolBooster", + addresses.zero + ); + const cCurvePoolBoosterProxy = await ethers.getContractAt( + "CurvePoolBoosterProxy", + addresses.zero + ); + + console.log("\nStarting deployment using CreateX"); + const rewardToken = addresses.mainnet.OUSDProxy; + const gauge = addresses.mainnet.CurveOUSDUSDTGauge; + const targetedChainId = 42161; // arbitrum + const fee = 0; + + // Get CreateX contract + const cCreateX = await ethers.getContractAt(createxAbi, addresses.createX); + + // Generate salt + const salt = ethers.utils.keccak256( + ethers.utils.concat([ + ethers.utils.arrayify(rewardToken), + ethers.utils.arrayify(gauge), + ]) + ); + + const encodedSalt = encodeSaltForCreateX(deployerAddr, false, salt); + console.log(`Encoded salt: ${encodedSalt}`); + + // --- Deploy implementation --- // + const cachedInitCodeImpl = ethers.utils.concat([ + PoolBoosterBytecode.bytecode, + ethers.utils.defaultAbiCoder.encode( + ["uint256", "address", "address"], + [targetedChainId, rewardToken, gauge] + ), + ]); + const txResponse = await withConfirmation( + cCreateX.connect(sDeployer).deployCreate2(encodedSalt, cachedInitCodeImpl) + ); + const txReceipt = await txResponse.wait(); + // event 0 is GovernorshipTransferred + // event 1 is ContractCreation, topics[1] is the address of the deployed contract, topics[2] is the salt + const implementationAddress = ethers.utils.getAddress( + `0x${txReceipt.events[1].topics[1].slice(26)}` + ); + console.log( + `Curve Booster Implementation deployed at: ${implementationAddress}` + ); + + // --- Deploy and init proxy --- // + const cachedInitCodeProxy = ProxyBytecode.bytecode; // No constructor arguments + const initializeImplem = cCurvePoolBooster.interface.encodeFunctionData( + "initialize(address,uint16,address,address,address)", + [ + addresses.multichainStrategist, // strategist + fee, // fee + addresses.multichainStrategist, // feeCollector + addresses.mainnet.CampaignRemoteManager, // campaignRemoteManager + addresses.votemarket, // votemarket + ] + ); + const initializeProxy = cCurvePoolBoosterProxy.interface.encodeFunctionData( + "initialize(address,address,bytes)", + [ + implementationAddress, // implementation + addresses.arbitrumOne.admin, // governor + initializeImplem, // init data + ] + ); + const txResponseProxy = await withConfirmation( + cCreateX + .connect(sDeployer) + .deployCreate2AndInit( + encodedSalt, + cachedInitCodeProxy, + initializeProxy, + ["0x00", "0x00"], + deployerAddr + ) + ); + const txReceiptProxy = await txResponseProxy.wait(); + // event 0 is GovernorshipTransferred + // event 1 is ContractCreation, topics[1] is the address of the deployed contract, topics[2] is the salt + // event 2 is StrategistUpdated + // event 3 is FeeUpdated + // event 4 is FeeCollectorUpdated + // event 5 is CampaignRemoteManagerUpdated + // event 6 is GovernorshipTransferred + const proxyAddress = ethers.utils.getAddress( + `0x${txReceiptProxy.events[1].topics[1].slice(26)}` + ); + console.log(`Curve Booster Proxy deployed at: ${proxyAddress}`); + return {}; + } +); diff --git a/contracts/test/_fixture.js b/contracts/test/_fixture.js index ef083049b8..86ae0aef26 100644 --- a/contracts/test/_fixture.js +++ b/contracts/test/_fixture.js @@ -646,14 +646,10 @@ const defaultFixture = deployments.createFixture(async () => { morphoGauntletPrimeUSDTStrategyProxy.address ); - const curvePoolBoosterProxy = isFork - ? await ethers.getContract("CurvePoolBoosterProxy") - : undefined; - const curvePoolBooster = isFork ? await ethers.getContractAt( "CurvePoolBooster", - curvePoolBoosterProxy.address + "0xb8221A6271fc9eB8cEdd02955e4CB9557E902af1" // hardcoded as generated with CreateX. ) : undefined; diff --git a/contracts/test/strategies/curvePoolBooster.mainnet.fork-test.js b/contracts/test/strategies/curvePoolBooster.mainnet.fork-test.js index d56253b1d8..4b929db688 100644 --- a/contracts/test/strategies/curvePoolBooster.mainnet.fork-test.js +++ b/contracts/test/strategies/curvePoolBooster.mainnet.fork-test.js @@ -18,43 +18,29 @@ describe("ForkTest: CurvePoolBooster", function () { fixture = await loadDefaultFixture(); }); - it("Should have correct params", async () => { - const { curvePoolBooster } = fixture; - const { strategistAddr } = await getNamedAccounts(); - expect(await curvePoolBooster.gauge()).to.equal( - addresses.mainnet.CurveOUSDUSDTGauge - ); - expect(await curvePoolBooster.campaignRemoteManager()).to.equal( - addresses.mainnet.CampaignRemoteManager - ); - expect(await curvePoolBooster.rewardToken()).to.equal( - addresses.mainnet.OUSDProxy - ); - expect(await curvePoolBooster.targetChainId()).to.equal(42161); - expect(await curvePoolBooster.strategistAddr()).to.equal(strategistAddr); - expect(await curvePoolBooster.governor()).to.equal( - addresses.mainnet.Timelock - ); - }); - - it("Should Create a campaign", async () => { + async function dealOETHAndCreateCampaign() { const { curvePoolBooster, ousd, wousd } = fixture; - const { strategistAddr } = await getNamedAccounts(); + const { multichainStrategistAddr } = await getNamedAccounts(); const woethSigner = await impersonateAndFund(wousd.address); - const sStrategist = await ethers.provider.getSigner(strategistAddr); + const sStrategist = await ethers.provider.getSigner( + multichainStrategistAddr + ); - // Deal OETH and ETH to pool booster + // Deal OETH to pool booster await ousd .connect(woethSigner) .transfer(curvePoolBooster.address, parseUnits("10")); expect(await ousd.balanceOf(curvePoolBooster.address)).to.equal( parseUnits("10") ); + + // Deal ETH to pool booster await sStrategist.sendTransaction({ to: curvePoolBooster.address, value: parseUnits("1"), }); + // Create campaign await curvePoolBooster .connect(sStrategist) .createCampaign( @@ -64,6 +50,36 @@ describe("ForkTest: CurvePoolBooster", function () { parseUnits("0.1"), 0 ); + } + + it("Should have correct params", async () => { + const { curvePoolBooster } = fixture; + const { multichainStrategistAddr } = await getNamedAccounts(); + expect(await curvePoolBooster.gauge()).to.equal( + addresses.mainnet.CurveOUSDUSDTGauge + ); + expect(await curvePoolBooster.campaignRemoteManager()).to.equal( + addresses.mainnet.CampaignRemoteManager + ); + expect(await curvePoolBooster.rewardToken()).to.equal( + addresses.mainnet.OUSDProxy + ); + expect(await curvePoolBooster.targetChainId()).to.equal(42161); + expect(await curvePoolBooster.strategistAddr()).to.equal( + multichainStrategistAddr + ); + expect(await curvePoolBooster.governor()).to.equal( + addresses.mainnet.Timelock + ); + expect(await curvePoolBooster.campaignRemoteManager()).to.equal( + addresses.mainnet.CampaignRemoteManager + ); + }); + + it("Should Create a campaign", async () => { + const { curvePoolBooster, ousd } = fixture; + + await dealOETHAndCreateCampaign(); expect(await ousd.balanceOf(curvePoolBooster.address)).to.equal( parseUnits("0") @@ -71,40 +87,17 @@ describe("ForkTest: CurvePoolBooster", function () { }); it("Should Create a campaign with fee", async () => { - const { curvePoolBooster, ousd, wousd, josh } = fixture; - const { strategistAddr } = await getNamedAccounts(); - const woethSigner = await impersonateAndFund(wousd.address); - const sStrategist = await ethers.provider.getSigner(strategistAddr); + const { curvePoolBooster, ousd, josh } = fixture; const gov = await curvePoolBooster.governor(); const sGov = await ethers.provider.getSigner(gov); - // Deal OETH and ETH to pool booster - await ousd - .connect(woethSigner) - .transfer(curvePoolBooster.address, parseUnits("10")); - expect(await ousd.balanceOf(curvePoolBooster.address)).to.equal( - parseUnits("10") - ); - await sStrategist.sendTransaction({ - to: curvePoolBooster.address, - value: parseUnits("1"), - }); - // Set fee and fee collector await curvePoolBooster.connect(sGov).setFee(1000); // 10% await curvePoolBooster.connect(sGov).setFeeCollector(josh.address); expect(await ousd.balanceOf(josh.address)).to.equal(0); - // Create campaign - await curvePoolBooster - .connect(sStrategist) - .createCampaign( - 4, - 10, - [addresses.mainnet.ConvexVoter], - parseUnits("0.1"), - 100 - ); + // Deal OETH and create campaign + await dealOETHAndCreateCampaign(); // Ensure fee is collected expect(await ousd.balanceOf(josh.address)).to.equal(parseUnits("1")); @@ -112,8 +105,10 @@ describe("ForkTest: CurvePoolBooster", function () { it("Should set campaign id", async () => { const { curvePoolBooster } = fixture; - const { strategistAddr } = await getNamedAccounts(); - const sStrategist = await ethers.provider.getSigner(strategistAddr); + const { multichainStrategistAddr } = await getNamedAccounts(); + const sStrategist = await ethers.provider.getSigner( + multichainStrategistAddr + ); expect(await curvePoolBooster.campaignId()).to.equal(0); await curvePoolBooster.connect(sStrategist).setCampaignId(12); @@ -136,31 +131,13 @@ describe("ForkTest: CurvePoolBooster", function () { it("Should manage total rewards", async () => { const { curvePoolBooster, ousd, wousd } = fixture; - const { strategistAddr } = await getNamedAccounts(); + const { multichainStrategistAddr } = await getNamedAccounts(); const woethSigner = await impersonateAndFund(wousd.address); - const sStrategist = await ethers.provider.getSigner(strategistAddr); - - // Deal OETH and ETH to pool booster - await ousd - .connect(woethSigner) - .transfer(curvePoolBooster.address, parseUnits("10")); - expect(await ousd.balanceOf(curvePoolBooster.address)).to.equal( - parseUnits("10") + const sStrategist = await ethers.provider.getSigner( + multichainStrategistAddr ); - await sStrategist.sendTransaction({ - to: curvePoolBooster.address, - value: parseUnits("1"), - }); - await curvePoolBooster - .connect(sStrategist) - .createCampaign( - 4, - 10, - [addresses.mainnet.ConvexVoter], - parseUnits("0.1"), - 0 - ); + await dealOETHAndCreateCampaign(); // Deal new OETH to pool booster await ousd @@ -169,9 +146,6 @@ describe("ForkTest: CurvePoolBooster", function () { expect(await ousd.balanceOf(curvePoolBooster.address)).to.equal( parseUnits("13") ); - expect(await ousd.balanceOf(curvePoolBooster.address)).to.equal( - parseUnits("13") - ); await curvePoolBooster.connect(sStrategist).setCampaignId(12); @@ -184,43 +158,12 @@ describe("ForkTest: CurvePoolBooster", function () { }); it("Should manage number of periods", async () => { - const { curvePoolBooster, ousd, wousd } = fixture; - const { strategistAddr } = await getNamedAccounts(); - const woethSigner = await impersonateAndFund(wousd.address); - const sStrategist = await ethers.provider.getSigner(strategistAddr); - - // Deal OETH and ETH to pool booster - await ousd - .connect(woethSigner) - .transfer(curvePoolBooster.address, parseUnits("10")); - expect(await ousd.balanceOf(curvePoolBooster.address)).to.equal( - parseUnits("10") - ); - await sStrategist.sendTransaction({ - to: curvePoolBooster.address, - value: parseUnits("1"), - }); - - await curvePoolBooster - .connect(sStrategist) - .createCampaign( - 4, - 10, - [addresses.mainnet.ConvexVoter], - parseUnits("0.1"), - 0 - ); - - // Deal new OETH to pool booster - await ousd - .connect(woethSigner) - .transfer(curvePoolBooster.address, parseUnits("13")); - expect(await ousd.balanceOf(curvePoolBooster.address)).to.equal( - parseUnits("13") - ); - expect(await ousd.balanceOf(curvePoolBooster.address)).to.equal( - parseUnits("13") + const { curvePoolBooster } = fixture; + const { multichainStrategistAddr } = await getNamedAccounts(); + const sStrategist = await ethers.provider.getSigner( + multichainStrategistAddr ); + await dealOETHAndCreateCampaign(); await curvePoolBooster.connect(sStrategist).setCampaignId(12); @@ -229,33 +172,16 @@ describe("ForkTest: CurvePoolBooster", function () { .manageNumberOfPeriods(2, parseUnits("0.1"), 0); }); + // J'en suis ici! + it("Should manage reward per voter", async () => { const { curvePoolBooster, ousd, wousd } = fixture; - const { strategistAddr } = await getNamedAccounts(); + const { multichainStrategistAddr } = await getNamedAccounts(); const woethSigner = await impersonateAndFund(wousd.address); - const sStrategist = await ethers.provider.getSigner(strategistAddr); - - // Deal OETH and ETH to pool booster - await ousd - .connect(woethSigner) - .transfer(curvePoolBooster.address, parseUnits("10")); - expect(await ousd.balanceOf(curvePoolBooster.address)).to.equal( - parseUnits("10") + const sStrategist = await ethers.provider.getSigner( + multichainStrategistAddr ); - await sStrategist.sendTransaction({ - to: curvePoolBooster.address, - value: parseUnits("1"), - }); - - await curvePoolBooster - .connect(sStrategist) - .createCampaign( - 4, - 10, - [addresses.mainnet.ConvexVoter], - parseUnits("0.1"), - 0 - ); + await dealOETHAndCreateCampaign(); // Deal new OETH to pool booster await ousd @@ -264,9 +190,6 @@ describe("ForkTest: CurvePoolBooster", function () { expect(await ousd.balanceOf(curvePoolBooster.address)).to.equal( parseUnits("13") ); - expect(await ousd.balanceOf(curvePoolBooster.address)).to.equal( - parseUnits("13") - ); await curvePoolBooster.connect(sStrategist).setCampaignId(12); @@ -277,8 +200,10 @@ describe("ForkTest: CurvePoolBooster", function () { it("Should rescue ETH", async () => { const { curvePoolBooster } = fixture; - const { strategistAddr } = await getNamedAccounts(); - const sStrategist = await ethers.provider.getSigner(strategistAddr); + const { multichainStrategistAddr } = await getNamedAccounts(); + const sStrategist = await ethers.provider.getSigner( + multichainStrategistAddr + ); // Deal ETH to pool booster await sStrategist.sendTransaction({ @@ -289,7 +214,9 @@ describe("ForkTest: CurvePoolBooster", function () { const balanceBefore = await ethers.provider.getBalance( curvePoolBooster.address ); - await curvePoolBooster.connect(sStrategist).rescueETH(strategistAddr); + await curvePoolBooster + .connect(sStrategist) + .rescueETH(multichainStrategistAddr); const balanceAfter = await ethers.provider.getBalance( curvePoolBooster.address ); @@ -324,8 +251,10 @@ describe("ForkTest: CurvePoolBooster", function () { it("Should revert if campaign is already created", async () => { const { curvePoolBooster } = fixture; - const { strategistAddr } = await getNamedAccounts(); - const sStrategist = await ethers.provider.getSigner(strategistAddr); + const { multichainStrategistAddr } = await getNamedAccounts(); + const sStrategist = await ethers.provider.getSigner( + multichainStrategistAddr + ); await curvePoolBooster.connect(sStrategist).setCampaignId(12); await expect( @@ -343,8 +272,10 @@ describe("ForkTest: CurvePoolBooster", function () { it("Should revert if campaign is not created", async () => { const { curvePoolBooster } = fixture; - const { strategistAddr } = await getNamedAccounts(); - const sStrategist = await ethers.provider.getSigner(strategistAddr); + const { multichainStrategistAddr } = await getNamedAccounts(); + const sStrategist = await ethers.provider.getSigner( + multichainStrategistAddr + ); await expect( curvePoolBooster @@ -365,8 +296,22 @@ describe("ForkTest: CurvePoolBooster", function () { it("Should revert if Invalid number of periods", async () => { const { curvePoolBooster } = fixture; - const { strategistAddr } = await getNamedAccounts(); - const sStrategist = await ethers.provider.getSigner(strategistAddr); + const { multichainStrategistAddr } = await getNamedAccounts(); + const sStrategist = await ethers.provider.getSigner( + multichainStrategistAddr + ); + + await expect( + curvePoolBooster + .connect(sStrategist) + .createCampaign( + 0, + 10, + [addresses.mainnet.ConvexVoter], + parseUnits("0.1"), + 0 + ) + ).to.be.revertedWith("Invalid number of periods"); await curvePoolBooster.connect(sStrategist).setCampaignId(12); @@ -379,8 +324,22 @@ describe("ForkTest: CurvePoolBooster", function () { it("Should revert if Invalid reward per vote", async () => { const { curvePoolBooster } = fixture; - const { strategistAddr } = await getNamedAccounts(); - const sStrategist = await ethers.provider.getSigner(strategistAddr); + const { multichainStrategistAddr } = await getNamedAccounts(); + const sStrategist = await ethers.provider.getSigner( + multichainStrategistAddr + ); + + await expect( + curvePoolBooster + .connect(sStrategist) + .createCampaign( + 4, + 0, + [addresses.mainnet.ConvexVoter], + parseUnits("0.1"), + 0 + ) + ).to.be.revertedWith("Invalid reward per vote"); await curvePoolBooster.connect(sStrategist).setCampaignId(12); @@ -393,8 +352,10 @@ describe("ForkTest: CurvePoolBooster", function () { it("Should revert if No reward to manage", async () => { const { curvePoolBooster } = fixture; - const { strategistAddr } = await getNamedAccounts(); - const sStrategist = await ethers.provider.getSigner(strategistAddr); + const { multichainStrategistAddr } = await getNamedAccounts(); + const sStrategist = await ethers.provider.getSigner( + multichainStrategistAddr + ); await expect( curvePoolBooster diff --git a/contracts/utils/addresses.js b/contracts/utils/addresses.js index 4a2326a0fa..06b3a0a046 100644 --- a/contracts/utils/addresses.js +++ b/contracts/utils/addresses.js @@ -4,6 +4,9 @@ const addresses = {}; addresses.zero = "0x0000000000000000000000000000000000000000"; addresses.dead = "0x0000000000000000000000000000000000000001"; addresses.ETH = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; +addresses.createX = "0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed"; +addresses.multichainStrategist = "0x4FF1b9D9ba8558F5EAfCec096318eA0d8b541971"; +addresses.votemarket = "0x5e5C922a5Eeab508486eB906ebE7bDFFB05D81e5"; addresses.mainnet = {}; addresses.holesky = {}; @@ -172,7 +175,7 @@ addresses.mainnet.CVXETHRewardsPool = // Votemarket - StakeDAO addresses.mainnet.CampaignRemoteManager = - "0xd1f0101Df22Cb7447F486Da5784237AB7a55eB4e"; + "0x000000009dF57105d76B059178989E01356e4b45"; // Morpho addresses.mainnet.MorphoStrategyProxy = @@ -296,6 +299,7 @@ addresses.mainnet.LidoWithdrawalQueue = // Arbitrum One addresses.arbitrumOne = {}; addresses.arbitrumOne.WOETHProxy = "0xD8724322f44E5c58D7A815F542036fb17DbbF839"; +addresses.arbitrumOne.admin = "0xfD1383fb4eE74ED9D83F2cbC67507bA6Eac2896a"; // Base addresses.base = {}; diff --git a/contracts/utils/deploy.js b/contracts/utils/deploy.js index 46a2d65c87..ed8155a93d 100644 --- a/contracts/utils/deploy.js +++ b/contracts/utils/deploy.js @@ -1608,6 +1608,34 @@ function deploymentWithGuardianGovernor(opts, fn) { return main; } +function encodeSaltForCreateX(deployer, crossChainProtectionFlag, salt) { + // Generate encoded salt (deployer address || crossChainProtectionFlag || bytes11(keccak256(rewardToken, gauge))) + + // convert deployer address to bytes20 + const addressDeployerBytes20 = ethers.utils.hexlify( + ethers.utils.zeroPad(deployer, 20) + ); + + // convert crossChainProtectionFlag to bytes1 + const crossChainProtectionFlagBytes1 = crossChainProtectionFlag + ? "0x01" + : "0x00"; + + // convert salt to bytes11 + const saltBytes11 = "0x" + salt.slice(2, 24); + + // concat all bytes into a bytes32 + const encodedSalt = ethers.utils.hexlify( + ethers.utils.concat([ + addressDeployerBytes20, + crossChainProtectionFlagBytes1, + saltBytes11, + ]) + ); + + return encodedSalt; +} + module.exports = { log, sleep, @@ -1622,4 +1650,5 @@ module.exports = { deploymentWithGuardianGovernor, handleTransitionGovernance, + encodeSaltForCreateX, };