Skip to content

Commit

Permalink
feat merkl V2 iterations
Browse files Browse the repository at this point in the history
  • Loading branch information
sogipec committed Jan 31, 2024
1 parent 0313109 commit ebca692
Show file tree
Hide file tree
Showing 5 changed files with 458 additions and 284 deletions.
63 changes: 33 additions & 30 deletions contracts/DistributionCreator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -190,13 +190,13 @@ contract DistributionCreator is UUPSHelper, ReentrancyGuardUpgradeable {
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/

/// @notice Creates a `campaign` to incentivize a given pool for a specific period of time
/// @return campaignAmount How many reward tokens are actually taken into consideration in the contract
/// @return The campaignId of the new campaign
/// @dev If the campaign is badly specified, it will not be handled by the campaign script and rewards may be lost
/// @dev Reward tokens sent as part of campaigns must have been whitelisted before and amounts
/// sent should be bigger than a minimum amount specific to each token
/// @dev This function reverts if the sender has not accepted the terms and conditions
function createCampaign(CampaignParameters memory campaign) external nonReentrant hasSigned returns (bytes32) {
return _createCampaign(campaign);
function createCampaign(CampaignParameters memory newCampaign) external nonReentrant hasSigned returns (bytes32) {
return _createCampaign(newCampaign);
}

/// @notice Same as the function above but for multiple campaigns at once
Expand Down Expand Up @@ -231,11 +231,11 @@ contract DistributionCreator is UUPSHelper, ReentrancyGuardUpgradeable {

/// @notice Combines signing the message and creating a campaign
function signAndCreateCampaign(
CampaignParameters memory campaign,
CampaignParameters memory newCampaign,
bytes calldata signature
) external returns (bytes32) {
_sign(signature);
return _createCampaign(campaign);
return _createCampaign(newCampaign);
}

/// @notice Creates a `distribution` to incentivize a given pool for a specific period of time
Expand Down Expand Up @@ -286,17 +286,17 @@ contract DistributionCreator is UUPSHelper, ReentrancyGuardUpgradeable {
/// - `campaign.campaignData`
/// This prevents the creation by the same account of two campaigns with the same parameters
/// which is not a huge issue
function campaignId(CampaignParameters memory campaign) public pure returns (bytes32) {
function campaignId(CampaignParameters memory campaignData) public pure returns (bytes32) {
return
bytes32(
keccak256(
abi.encodePacked(
campaign.creator,
campaign.rewardToken,
campaign.campaignType,
campaign.startTimestamp,
campaign.duration,
campaign.campaignData
campaignData.creator,
campaignData.rewardToken,
campaignData.campaignType,
campaignData.startTimestamp,
campaignData.duration,
campaignData.campaignData
)
)
);
Expand Down Expand Up @@ -440,33 +440,33 @@ contract DistributionCreator is UUPSHelper, ReentrancyGuardUpgradeable {
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/

/// @notice Internal version of `createCampaign`
function _createCampaign(CampaignParameters memory campaign) internal returns (bytes32) {
uint256 rewardTokenMinAmount = rewardTokenMinAmounts[campaign.rewardToken];
function _createCampaign(CampaignParameters memory newCampaign) internal returns (bytes32) {
uint256 rewardTokenMinAmount = rewardTokenMinAmounts[newCampaign.rewardToken];
// if epoch parameters lead to a past campaign
if (campaign.startTimestamp < block.timestamp) revert CampaignSouldStartInFuture();
if (newCampaign.startTimestamp < block.timestamp) revert CampaignSouldStartInFuture();
// if the campaign doesn't last at least one second
if (campaign.duration == 0) revert CampaignDurationIsZero();
if (newCampaign.duration == 0) revert CampaignDurationIsZero();
// if the reward token is not whitelisted as an incentive token
if (rewardTokenMinAmount == 0) revert CampaignRewardTokenNotWhitelisted();
// if the amount distributed is too small with respect to what is allowed
if ((campaign.amount * HOUR) / campaign.duration < rewardTokenMinAmount) revert CampaignRewardTooLow();
if ((newCampaign.amount * HOUR) / newCampaign.duration < rewardTokenMinAmount) revert CampaignRewardTooLow();

if (campaign.creator == address(0)) campaign.creator = msg.sender;
if (newCampaign.creator == address(0)) newCampaign.creator = msg.sender;

// Computing fees: these are waived for whitelisted addresses and if there is a whitelisted token in a pool
uint256 _fees = campaignSpecificFees[campaign.campaignType];
uint256 _fees = campaignSpecificFees[newCampaign.campaignType];
if (_fees == 0) _fees = defaultFees;
uint256 campaignAmountMinusFees = _computeFees(_fees, campaign.amount, campaign.rewardToken);
campaign.amount = campaignAmountMinusFees;
uint256 campaignAmountMinusFees = _computeFees(_fees, newCampaign.amount, newCampaign.rewardToken);
newCampaign.amount = campaignAmountMinusFees;

campaign.campaignId = campaignId(campaign);
newCampaign.campaignId = campaignId(newCampaign);

if (_campaignLookup[campaign.campaignId] != 0) revert CampaignAlreadyExists();
_campaignLookup[campaign.campaignId] = campaignList.length + 1;
campaignList.push(campaign);
emit NewCampaign(campaign);
if (_campaignLookup[newCampaign.campaignId] != 0) revert CampaignAlreadyExists();
_campaignLookup[newCampaign.campaignId] = campaignList.length + 1;
campaignList.push(newCampaign);
emit NewCampaign(newCampaign);

return campaign.campaignId;
return newCampaign.campaignId;
}

/// @notice Converts the deprecated distribution type into a campaign
Expand Down Expand Up @@ -567,9 +567,12 @@ contract DistributionCreator is UUPSHelper, ReentrancyGuardUpgradeable {
CampaignParameters[] memory activeRewards = new CampaignParameters[](returnSize);
uint32 i = skip;
while (i < campaignListLength) {
CampaignParameters memory campaign = campaignList[i];
if (campaign.startTimestamp + campaign.duration > start && campaign.startTimestamp < end) {
activeRewards[length] = campaign;
CampaignParameters memory campaignToProcess = campaignList[i];
if (
campaignToProcess.startTimestamp + campaignToProcess.duration > start &&
campaignToProcess.startTimestamp < end
) {
activeRewards[length] = campaignToProcess;
length += 1;
}
unchecked {
Expand Down
19 changes: 16 additions & 3 deletions contracts/struct/CampaignParameters.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,27 @@
pragma solidity ^0.8.17;

struct CampaignParameters {
// Populated once created
// POPULATED ONCE CREATED

// ID of the campaign. This can be left as a null bytes32 when creating campaigns
// on Merkl.
bytes32 campaignId;
// Chosen by campaign creator
// CHOSEN BY CAMPAIGN CREATOR

// Address of the campaign creator, if marked as address(0), it will be overriden with the
// address of the `msg.sender` creating the campaign
address creator;
// Address of the token used as a reward
address rewardToken;
// Amount of `rewardToken` to distribute across all the epochs
// Amount distributed per epoch is `amount/numEpoch`
uint256 amount;
// Type of campaign
uint32 campaignType;
// Timestamp at which the campaign should start
uint32 startTimestamp;
uint32 duration; // in seconds, has to be a multiple of EPOCH
// Duration of the campaign in seconds. Has to be a multiple of EPOCH = 3600
uint32 duration;
// Extra data to pass to specify the campaign
bytes campaignData;
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
"eslint-plugin-promise": "^6.0.0",
"eslint-plugin-standard": "5.0.0",
"ethers": "^5.7.1",
"hardhat": "^2.12.6",
"hardhat": "^2.19.4",
"hardhat-abi-exporter": "2.2.1",
"hardhat-contract-sizer": "2.0.3",
"hardhat-deploy": "^0.11.23",
Expand Down
121 changes: 23 additions & 98 deletions scripts/mainnet-fork/upgradeDistributionCreator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ChainId, CONTRACTS_ADDRESSES } from '@angleprotocol/sdk';
import { ChainId, registry } from '@angleprotocol/sdk';
import { deployments, ethers, network, web3 } from 'hardhat';
import yargs from 'yargs';

Expand Down Expand Up @@ -26,42 +26,7 @@ async function main() {
const { deploy } = deployments;
const [deployer] = await ethers.getSigners();

const manager = new ethers.Contract(
proxyAdminAddress,
ProxyAdmin__factory.createInterface(),
governorSigner,
) as ProxyAdmin;

const newImplementation = await new DistributionCreator__factory(deployer).deploy();
await manager.connect(governor).upgradeTo(newImplementation.address);

const proxyAdminAddress = CONTRACTS_ADDRESSES[ChainId.MAINNET].ProxyAdmin!;
const governor = CONTRACTS_ADDRESSES[ChainId.MAINNET].Governor! as string;
const coreBorrow = CONTRACTS_ADDRESSES[ChainId.MAINNET].CoreBorrow! as string;
const distributor = CONTRACTS_ADDRESSES[ChainId.MAINNET].Distributor! as string;
const angleDistributorAddress = CONTRACTS_ADDRESSES[ChainId.MAINNET].AngleDistributor! as string;
const angleAddress = CONTRACTS_ADDRESSES[ChainId.MAINNET].ANGLE! as string;
const agEURAddress = '0x1a7e4e63778B4f12a199C062f3eFdD288afCBce8';
// agEUR-USDC gauge address
const gaugeAddr = '0xEB7547a8a734b6fdDBB8Ce0C314a9E6485100a3C';

params = {
uniV3Pool: '0x735a26a57a0a0069dfabd41595a970faf5e1ee8b',
token: angleAddress,
positionWrappers: [],
wrapperTypes: [],
amount: parseAmount.gwei('100000'),
propToken0: 4000,
propToken1: 2000,
propFees: 4000,
isOutOfRangeIncentivized: 0,
epochStart: 0,
numEpoch: 168,
boostedReward: 0,
boostingAddress: ZERO_ADDRESS,
rewardId: web3.utils.soliditySha3('TEST') as string,
additionalData: web3.utils.soliditySha3('test2ng') as string,
};
const governor = registry(ChainId.MAINNET)?.AngleLabs!;

await network.provider.request({
method: 'hardhat_impersonateAccount',
Expand All @@ -70,70 +35,30 @@ async function main() {
await network.provider.send('hardhat_setBalance', [governor, '0x10000000000000000000000000000']);
const governorSigner = await ethers.provider.getSigner(governor);

console.log('First deploying implementation for distributor');
await deploy('AngleDistributor_NewImplementation', {
contract: 'AngleDistributor',
from: deployer.address,
log: !argv.ci,
});
console.log('Success');

const distributorImplementationAddress = (await deployments.get('AngleDistributor_NewImplementation')).address;

const contractProxyAdmin = new ethers.Contract(
proxyAdminAddress,
ProxyAdmin__factory.createInterface(),
governorSigner,
) as ProxyAdmin;
const distributorAddress = registry(ChainId.MAINNET)?.Merkl?.DistributionCreator!;

angleDistributor = new ethers.Contract(
angleDistributorAddress,
AngleDistributor__factory.createInterface(),
manager = new ethers.Contract(
distributorAddress,
DistributionCreator__factory.createInterface(),
governorSigner,
) as AngleDistributor;
) as DistributionCreator;

angle = new ethers.Contract(angleAddress, ERC20__factory.createInterface(), governorSigner) as ERC20;

console.log('Now performing the upgrades');
await (
await contractProxyAdmin.connect(governorSigner).upgrade(angleDistributorAddress, distributorImplementationAddress)
).wait();
console.log('Success');

manager = (await deployUpgradeableUUPS(new MerkleRewardManager__factory(deployer))) as DistributionCreator;
await manager.initialize(coreBorrow, distributor, parseAmount.gwei('0.1'));

middleman = (await new MockMerklGaugeMiddleman__factory(deployer).deploy(coreBorrow)) as MockMerklGaugeMiddleman;

await middleman.setAddresses(angleDistributorAddress, angleAddress, manager.address);

console.log('Toggling signature whitelist');
await (await manager.connect(governorSigner).toggleSigningWhitelist(middleman.address)).wait();
console.log('Toggling token whitelist');
await manager.connect(governorSigner).toggleTokenWhitelist(agEURAddress);

console.log('Setting the gauge on the middleman');
await middleman.connect(governorSigner).setGauge(gaugeAddr, params);
console.log('Setting the middleman as a delegate for the gauge');
await angleDistributor.connect(governorSigner).setDelegateGauge(gaugeAddr, middleman.address, true);
console.log('Setting allowance');
await middleman.connect(governorSigner).setAngleAllowance();
console.log(middleman.address);

await increaseTime(86400 * 7);

const angleBalanceDistr = await angle.balanceOf(distributor);
console.log(formatAmount.ether(angleBalanceDistr.toString()));
await angleDistributor.connect(governorSigner).distributeReward(gaugeAddr);
const angleBalanceDistr1 = await angle.balanceOf(distributor);
console.log(formatAmount.ether(angleBalanceDistr1.toString()));
await angleDistributor.connect(governorSigner).distributeReward(gaugeAddr);
const angleBalanceDistr2 = await angle.balanceOf(distributor);
console.log(formatAmount.ether(angleBalanceDistr2.toString()));
await increaseTime(86400 * 7);
await angleDistributor.connect(governorSigner).distributeReward(gaugeAddr);
const angleBalanceDistr3 = await angle.balanceOf(distributor);
console.log(formatAmount.ether(angleBalanceDistr3.toString()));
const newImplementation = await new DistributionCreator__factory(deployer).deploy();
await manager.connect(governorSigner).upgradeTo(newImplementation.address);

console.log(await manager.core());
console.log(await manager.distributor());
console.log(await manager.feeRecipient());
console.log(await manager.defaultFees());
console.log(await manager.message());
console.log(await manager.distributionList(10));
console.log(await manager.feeRebate(governor));
console.log(await manager.isWhitelistedToken(registry(ChainId.MAINNET)?.agEUR?.AgToken!));
console.log(await manager._nonces('0xfda462548ce04282f4b6d6619823a7c64fdc0185'));
console.log(await manager.userSignatureWhitelist('0xfda462548ce04282f4b6d6619823a7c64fdc0185'));
console.log(await manager.rewardTokens(0));
console.log(await manager.campaignList(0));
console.log(await manager.campaignSpecificFees(0));
}

main().catch(error => {
Expand Down
Loading

0 comments on commit ebca692

Please sign in to comment.