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

Deploy v2 #55

Merged
merged 33 commits into from
Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
a755977
feat: merklV2 poc
sogipec Dec 1, 2023
916262d
merkl V2 contracts
sogipec Dec 3, 2023
9f41f8b
feat: distribution creator concatenated
sogipec Dec 5, 2023
ba7df73
feat: delete distribution creator
sogipec Dec 5, 2023
1d97045
feat: rework for wrapping new Merkl version in the contract
sogipec Dec 5, 2023
12c93d4
feat guardian governor
sogipec Dec 5, 2023
bcac8c6
feat: distribution update
sogipec Dec 12, 2023
79094e9
feat: add pagination in reward tokens
sogipec Dec 12, 2023
b9ac646
mock deployment merkl
sogipec Dec 19, 2023
be18616
feat: update creator
sogipec Dec 22, 2023
1ff8f66
feat: change creator
sogipec Dec 22, 2023
60ab65d
base deployment
sogipec Jan 10, 2024
9b3dc31
feat: wrapping campaigns
Picodes Jan 10, 2024
9557c32
fix: DistributionCreator v2
Picodes Jan 16, 2024
7f95d29
Iterations (#50)
sogipec Jan 31, 2024
fddf906
feat: getDistribution getter (#51)
Picodes Feb 1, 2024
4a9c48a
feat: chainId in campaignId (#52)
Picodes Feb 1, 2024
94645ab
Default fees (#53)
sogipec Feb 2, 2024
454198d
fix: signing
Picodes Feb 5, 2024
6c435bd
fix: signing (#54)
Picodes Feb 5, 2024
ff7fcfe
fix updates
sogipec Jan 31, 2024
0c8d92d
feat: deployment
sogipec Feb 5, 2024
43efbf3
feat: deploy-v2
sogipec Feb 5, 2024
76f0ef2
feat: deployments everywhere
sogipec Feb 5, 2024
d59a2a1
tests: update all tests for v2
0xtekgrinder Feb 9, 2024
d8fc719
chore: revert hardhat config back to pre v2
0xtekgrinder Feb 9, 2024
dc2852e
tests: add signAndCreateCampaign tests
0xtekgrinder Feb 9, 2024
b12e818
tests: add all missings unit tests for DistributionCreator
0xtekgrinder Feb 12, 2024
f365f2a
chore: coverage ci and README
0xtekgrinder Feb 12, 2024
e51cc67
tests: correct amount computation in distributioncreation test
0xtekgrinder Feb 12, 2024
3522c89
Scripts (#56)
sogipec Feb 14, 2024
63075d6
tests: revert to previous foundry tests
0xtekgrinder Feb 15, 2024
ab8d96c
fix: default value for private key in hardhat c onfig
0xtekgrinder Feb 15, 2024
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
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"editor.defaultFormatter": "JuanBlanco.solidity"
},
"editor.codeActionsOnSave": {
"source.fixAll": true
"source.fixAll": "explicit"
},
"solidity.compileUsingRemoteVersion": "v0.8.17+commit.8df45f5f"
}
843 changes: 378 additions & 465 deletions contracts/DistributionCreator.sol

Large diffs are not rendered by default.

22 changes: 14 additions & 8 deletions contracts/Distributor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";

import "./utils/UUPSHelper.sol";

Check warning on line 42 in contracts/Distributor.sol

View workflow job for this annotation

GitHub Actions / lint

global import of path ./utils/UUPSHelper.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)

struct MerkleTree {
// Root of a Merkle tree which leaves are `(address user, address token, uint amount)`
Expand All @@ -58,7 +58,7 @@
}

/// @title Distributor
/// @notice Allows LPs on AMMs with concentrated liquidity to claim the rewards that were distributed to them
/// @notice Allows to claim rewards distributed to them through Merkl
/// @author Angle Labs. Inc
contract Distributor is UUPSHelper {
using SafeERC20 for IERC20;
Expand Down Expand Up @@ -130,6 +130,12 @@
_;
}

/// @notice Checks whether the `msg.sender` has the governor role or the guardian role
modifier onlyGovernor() {
if (!core.isGovernor(msg.sender)) revert NotGovernor();
_;
}

/// @notice Checks whether the `msg.sender` is the `user` address or is a trusted address
modifier onlyTrustedOrUser(address user) {
if (user != msg.sender && canUpdateMerkleRoot[msg.sender] != 1 && !core.isGovernorOrGuardian(msg.sender))
Expand All @@ -147,7 +153,7 @@
}

/// @inheritdoc UUPSUpgradeable
function _authorizeUpgrade(address) internal view override onlyGuardianUpgrader(core) {}
function _authorizeUpgrade(address) internal view override onlyGovernorUpgrader(core) {}

// =============================== MAIN FUNCTION ===============================

Expand Down Expand Up @@ -205,7 +211,7 @@
// ============================ GOVERNANCE FUNCTIONS ===========================

/// @notice Adds or removes EOAs which are trusted to update the Merkle root
function toggleTrusted(address eoa) external onlyGovernorOrGuardian {
function toggleTrusted(address eoa) external onlyGovernor {
uint256 trustedStatus = 1 - canUpdateMerkleRoot[eoa];
canUpdateMerkleRoot[eoa] = trustedStatus;
emit TrustedToggled(eoa, trustedStatus == 1);
Expand All @@ -218,7 +224,7 @@
// A trusted address cannot update a tree right after a precedent tree update otherwise it can de facto
// validate a tree which has not passed the dispute period
((canUpdateMerkleRoot[msg.sender] != 1 || block.timestamp < endOfDisputePeriod) &&
!core.isGovernorOrGuardian(msg.sender))
!core.isGovernor(msg.sender))
) revert NotTrusted();
MerkleTree memory _lastTree = tree;
tree = _tree;
Expand Down Expand Up @@ -278,26 +284,26 @@
}

/// @notice Recovers any ERC20 token
function recoverERC20(address tokenAddress, address to, uint256 amountToRecover) external onlyGovernorOrGuardian {
function recoverERC20(address tokenAddress, address to, uint256 amountToRecover) external onlyGovernor {
IERC20(tokenAddress).safeTransfer(to, amountToRecover);
emit Recovered(tokenAddress, to, amountToRecover);
}

/// @notice Sets the dispute period after which a tree update becomes effective
function setDisputePeriod(uint48 _disputePeriod) external onlyGovernorOrGuardian {
function setDisputePeriod(uint48 _disputePeriod) external onlyGovernor {
disputePeriod = uint48(_disputePeriod);
emit DisputePeriodUpdated(_disputePeriod);
}

/// @notice Sets the token used as a caution during disputes
function setDisputeToken(IERC20 _disputeToken) external onlyGovernorOrGuardian {
function setDisputeToken(IERC20 _disputeToken) external onlyGovernor {
if (disputer != address(0)) revert UnresolvedDispute();
disputeToken = _disputeToken;
emit DisputeTokenUpdated(address(_disputeToken));
}

/// @notice Sets the amount of `disputeToken` used as a caution during disputes
function setDisputeAmount(uint256 _disputeAmount) external onlyGovernorOrGuardian {
function setDisputeAmount(uint256 _disputeAmount) external onlyGovernor {
if (disputer != address(0)) revert UnresolvedDispute();
disputeAmount = _disputeAmount;
emit DisputeAmountUpdated(_disputeAmount);
Expand Down
307 changes: 261 additions & 46 deletions contracts/deprecated/OldDistributionCreator.sol

Large diffs are not rendered by default.

68 changes: 43 additions & 25 deletions contracts/deprecated/OldDistributor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@

pragma solidity ^0.8.17;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";

import "../utils/UUPSHelper.sol";

Expand All @@ -54,14 +54,18 @@ struct MerkleTree {
struct Claim {
uint208 amount;
uint48 timestamp;
bytes32 merkleRoot;
}

/// @title OldDistributor
/// @title Distributor
/// @notice Allows LPs on AMMs with concentrated liquidity to claim the rewards that were distributed to them
/// @author Angle Labs. Inc
contract OldDistributor is UUPSHelper {
using SafeERC20 for IERC20;

/// @notice Epoch duration
uint32 internal constant _EPOCH_DURATION = 3600;

// ================================= VARIABLES =================================

/// @notice Tree of claimable tokens through this contract
Expand All @@ -80,10 +84,10 @@ contract OldDistributor is UUPSHelper {
/// @dev Used to store if there is an ongoing dispute
address public disputer;

/// @notice Last time the `tree` was updated
uint48 public lastTreeUpdate;
/// @notice When the current tree will become valid
uint48 public endOfDisputePeriod;

/// @notice Time before which a change in a tree becomes effective
/// @notice Time after which a change in a tree becomes effective, in EPOCH_DURATION
uint48 public disputePeriod;

/// @notice Amount to deposit to freeze the roots update
Expand All @@ -105,16 +109,17 @@ contract OldDistributor is UUPSHelper {

// =================================== EVENTS ==================================

event Claimed(address user, address token, uint256 amount);
event Claimed(address indexed user, address indexed token, uint256 amount);
event DisputeAmountUpdated(uint256 _disputeAmount);
event Disputed(string reason);
event DisputePeriodUpdated(uint48 _disputePeriod);
event DisputeTokenUpdated(address indexed _disputeToken);
event DisputeAmountUpdated(uint256 _disputeAmount);
event DisputeResolved(bool valid);
event OperatorClaimingToggled(address user, bool isEnabled);
event OperatorToggled(address user, address operator, bool isWhitelisted);
event DisputeTokenUpdated(address indexed _disputeToken);
event OperatorClaimingToggled(address indexed user, bool isEnabled);
event OperatorToggled(address indexed user, address indexed operator, bool isWhitelisted);
event Recovered(address indexed token, address indexed to, uint256 amount);
event TreeUpdated(bytes32 merkleRoot, bytes32 ipfsHash);
event Revoked(); // With this event an indexer could maintain a table (timestamp, merkleRootUpdate)
event TreeUpdated(bytes32 merkleRoot, bytes32 ipfsHash, uint48 endOfDisputePeriod);
event TrustedToggled(address indexed eoa, bool trust);

// ================================= MODIFIERS =================================
Expand Down Expand Up @@ -181,7 +186,7 @@ contract OldDistributor is UUPSHelper {

// Closing reentrancy gate here
uint256 toSend = amount - claimed[user][token].amount;
claimed[user][token] = Claim(SafeCast.toUint208(amount), uint48(block.timestamp));
claimed[user][token] = Claim(SafeCast.toUint208(amount), uint48(block.timestamp), getMerkleRoot());

IERC20(token).safeTransfer(user, toSend);
emit Claimed(user, token, toSend);
Expand All @@ -193,7 +198,7 @@ contract OldDistributor is UUPSHelper {

/// @notice Returns the MerkleRoot that is currently live for the contract
function getMerkleRoot() public view returns (bytes32) {
if (block.timestamp - lastTreeUpdate >= disputePeriod) return tree.merkleRoot;
if (block.timestamp >= endOfDisputePeriod && disputer == address(0)) return tree.merkleRoot;
else return lastTree.merkleRoot;
}

Expand All @@ -212,21 +217,24 @@ contract OldDistributor is UUPSHelper {
disputer != address(0) ||
// A trusted address cannot update a tree right after a precedent tree update otherwise it can de facto
// validate a tree which has not passed the dispute period
((canUpdateMerkleRoot[msg.sender] != 1 || block.timestamp - lastTreeUpdate < disputePeriod) &&
((canUpdateMerkleRoot[msg.sender] != 1 || block.timestamp < endOfDisputePeriod) &&
!core.isGovernorOrGuardian(msg.sender))
) revert NotTrusted();
MerkleTree memory _lastTree = tree;
tree = _tree;
lastTree = _lastTree;
lastTreeUpdate = uint48(block.timestamp);
emit TreeUpdated(_tree.merkleRoot, _tree.ipfsHash);

uint48 _endOfPeriod = _endOfDisputePeriod(uint48(block.timestamp));
endOfDisputePeriod = _endOfPeriod;
emit TreeUpdated(_tree.merkleRoot, _tree.ipfsHash, _endOfPeriod);
}

/// @notice Freezes the Merkle tree update until the dispute is resolved
/// @dev Requires a deposit of `disputeToken` that'll be slashed if the dispute is not accepted
/// @dev It is only possible to create a dispute for `disputePeriod` after each tree update
/// @dev It is only possible to create a dispute within `disputePeriod` after each tree update
function disputeTree(string memory reason) external {
if (block.timestamp - lastTreeUpdate >= disputePeriod) revert InvalidDispute();
if (disputer != address(0)) revert UnresolvedDispute();
if (block.timestamp >= endOfDisputePeriod) revert InvalidDispute();
IERC20(disputeToken).safeTransferFrom(msg.sender, address(this), disputeAmount);
disputer = msg.sender;
emit Disputed(reason);
Expand All @@ -242,7 +250,7 @@ contract OldDistributor is UUPSHelper {
_revokeTree();
} else {
IERC20(disputeToken).safeTransfer(msg.sender, disputeAmount);
lastTreeUpdate = uint48(block.timestamp);
endOfDisputePeriod = _endOfDisputePeriod(uint48(block.timestamp));
}
disputer = address(0);
emit DisputeResolved(valid);
Expand Down Expand Up @@ -275,9 +283,8 @@ contract OldDistributor is UUPSHelper {
emit Recovered(tokenAddress, to, amountToRecover);
}

/// @notice Sets the dispute period before which a tree update becomes effective
/// @notice Sets the dispute period after which a tree update becomes effective
function setDisputePeriod(uint48 _disputePeriod) external onlyGovernorOrGuardian {
if (_disputePeriod > block.timestamp) revert InvalidParam();
disputePeriod = uint48(_disputePeriod);
emit DisputePeriodUpdated(_disputePeriod);
}
Expand All @@ -301,9 +308,20 @@ contract OldDistributor is UUPSHelper {
/// @notice Fallback to the last version of the tree
function _revokeTree() internal {
MerkleTree memory _tree = lastTree;
lastTreeUpdate = 0;
endOfDisputePeriod = 0;
tree = _tree;
emit TreeUpdated(_tree.merkleRoot, _tree.ipfsHash);
emit Revoked();
emit TreeUpdated(
_tree.merkleRoot,
_tree.ipfsHash,
(uint48(block.timestamp) / _EPOCH_DURATION) * (_EPOCH_DURATION) // Last hour
);
}

/// @notice Returns the end of the dispute period
/// @dev treeUpdate is rounded up to next hour and then `disputePeriod` hours are added
function _endOfDisputePeriod(uint48 treeUpdate) internal view returns (uint48) {
return ((treeUpdate - 1) / _EPOCH_DURATION + 1 + disputePeriod) * (_EPOCH_DURATION);
}

/// @notice Checks the validity of a proof
Expand Down
29 changes: 29 additions & 0 deletions contracts/struct/CampaignParameters.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.17;

struct CampaignParameters {
// 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

// 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;
// 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;
}
6 changes: 6 additions & 0 deletions contracts/utils/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

pragma solidity ^0.8.17;

error CampaignDoesNotExist();
error CampaignAlreadyExists();
error CampaignDurationBelowHour();
error CampaignRewardTokenNotWhitelisted();
error CampaignRewardTooLow();
error CampaignSouldStartInFuture();
error InvalidDispute();
error InvalidLengths();
error InvalidParam();
Expand Down
14 changes: 7 additions & 7 deletions deploy/0_distributor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,35 +27,35 @@ const func: DeployFunction = async ({ deployments, ethers, network }) => {

console.log('Now deploying Distributor');
console.log('Starting with the implementation');
/*
await deploy('Distributor_Implementation_2', {

await deploy('Distributor_Implementation_V2_0', {
contract: 'Distributor',
from: deployer.address,
log: !argv.ci,
});
*/

const implementationAddress = (await ethers.getContract('Distributor_Implementation_2')).address;
const implementationAddress = (await ethers.getContract('Distributor_Implementation_V2_0')).address;

console.log(`Successfully deployed the implementation for Distributor at ${implementationAddress}`);
console.log('');

/*
console.log('Now deploying the Proxy');

await deploy('Distributor', {
await deploy('TestDistributor', {
contract: 'ERC1967Proxy',
from: deployer.address,
args: [implementationAddress, '0x'],
log: !argv.ci,
});

const distributor = (await deployments.get('Distributor')).address;
const distributor = (await deployments.get('TestDistributor')).address;
console.log(`Successfully deployed contract at the address ${distributor}`);
console.log('Initializing the contract');
const contract = new ethers.Contract(distributor, Distributor__factory.createInterface(), deployer) as Distributor;
await (await contract.connect(deployer).initialize(core)).wait();
console.log('Contract successfully initialized');
console.log('');
*/
};

func.tags = ['distributor'];
Expand Down
9 changes: 5 additions & 4 deletions deploy/1_distributionCreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,17 @@ const func: DeployFunction = async ({ deployments, ethers, network }) => {
console.log('Now deploying DistributionCreator');
console.log('Starting with the implementation');
console.log('deployer ', await deployer.getBalance());
await deploy('DistributionCreator_Implementation_7', {
await deploy('DistributionCreator_Implementation_V2_0', {
contract: 'DistributionCreator',
from: deployer.address,
log: !argv.ci,
});

const implementationAddress = (await ethers.getContract('DistributionCreator_Implementation_7')).address;
const implementationAddress = (await ethers.getContract('DistributionCreator_Implementation_V2_0')).address;

console.log(`Successfully deployed the implementation for DistributionCreator at ${implementationAddress}`);
console.log('');

/*
const distributor = (await deployments.get('Distributor')).address;
console.log('Now deploying the Proxy');

Expand All @@ -60,6 +60,7 @@ const func: DeployFunction = async ({ deployments, ethers, network }) => {
console.log('Contract successfully initialized');
console.log('');
console.log(await contract.core());
*/

/* Once good some functions need to be called to have everything setup.

Expand All @@ -78,5 +79,5 @@ const func: DeployFunction = async ({ deployments, ethers, network }) => {
};

func.tags = ['distributionCreator'];
func.dependencies = [''];
func.dependencies = ['distributor'];
export default func;
Loading
Loading