merkle

Created Diff never expires
54 removals
Lines
Total
Removed
Words
Total
Removed
To continue using this feature, upgrade to
Diffchecker logo
Diffchecker Pro
122 lines
36 additions
Lines
Total
Added
Words
Total
Added
To continue using this feature, upgrade to
Diffchecker logo
Diffchecker Pro
105 lines
// SPDX-License-Identifier: UNLICENSED
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.7.6;
pragma solidity 0.7.6;


interface Minter {
interface IERC20 {
function mint(address _receiver, uint256 _amount) external;
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
}
}




// MerkleDistributor for ongoing EPS airdrop to veCRV holders
// MerkleAirdropper for ongoing FLTY airdrop to LQTY holders & stakers
// Based on the EMN refund contract by banteg - https://github.com/banteg/your-eminence
// Based on the EMN refund contract by banteg - https://github.com/banteg/your-eminence
contract MerkleDistributor {
contract MerkleAirdropper {

// 2-year airdrop process
uint256 constant ROUNDS = 104;


bytes32[] public merkleRoots;
bytes32[] public merkleRoots;
bytes32 public pendingMerkleRoot;
uint256 public lastRoot;
uint256 public lastRoot;


// admin address which can propose adding a new merkle root
// admin address for proposing a new merkle root
address public proposalAuthority;
address public authority;
// admin address which approves or rejects a proposed merkle root
// ERC20 token for distributing
address public reviewAuthority;
address public airdropToken;

modifier authorityOnly {
require(msg.sender == authority, "MerkleAirdropper: Authority only.");
_;
}


event Claimed(
event Claimed(
uint256 merkleIndex,
uint256 merkleIndex,
uint256 index,
uint256 index,
address account,
address account,
uint256 amount
uint256 amount
);
);


// This is a packed array of booleans.
// This is a packed array of booleans.
mapping(uint256 => mapping(uint256 => uint256)) private claimedBitMap;
mapping(uint256 => mapping(uint256 => uint256)) private claimedBitMap;
Minter public rewardMinter;

constructor(address _proposalAuthority, address _reviewAuthority) public {
proposalAuthority = _proposalAuthority;
reviewAuthority = _reviewAuthority;
}

function setMinter(Minter _rewardMinter) public {
require(rewardMinter == Minter(0));
rewardMinter = _rewardMinter;
}


function setProposalAuthority(address _account) public {
constructor(address _authority, address _airdropToken) {
require(msg.sender == proposalAuthority);
authority = _authority;
proposalAuthority = _account;
airdropToken = _airdropToken;
}
}


function setReviewAuthority(address _account) public {
function setAuthority(address _authority) public authorityOnly {
require(msg.sender == reviewAuthority);
authority = _authority;
reviewAuthority = _account;
}
}


// Each week, the proposal authority calls to submit the merkle root for a new airdrop.
// Each week, the authority calls to submit the merkle root for a new airdrop.
function proposewMerkleRoot(bytes32 _merkleRoot) public {
function proposewMerkleRoot(bytes32 _merkleRoot) public authorityOnly {
require(msg.sender == proposalAuthority);
require(merkleRoots.length < ROUNDS);
require(pendingMerkleRoot == 0x00);
require(merkleRoots.length < 52);
require(block.timestamp > lastRoot + 604800);
require(block.timestamp > lastRoot + 604800);
pendingMerkleRoot = _merkleRoot;
merkleRoots.push(_merkleRoot);
}
lastRoot = block.timestamp / 604800 * 604800;

// After validating the correctness of the pending merkle root, the reviewing authority
// calls to confirm it and the distribution may begin.
function reviewPendingMerkleRoot(bool _approved) public {
require(msg.sender == reviewAuthority);
require(pendingMerkleRoot != 0x00);
if (_approved) {
merkleRoots.push(pendingMerkleRoot);
lastRoot = block.timestamp / 604800 * 604800;
}
delete pendingMerkleRoot;
}
}


function isClaimed(uint256 merkleIndex, uint256 index) public view returns (bool) {
function isClaimed(uint256 merkleIndex, uint256 index) public view returns (bool) {
uint256 claimedWordIndex = index / 256;
uint256 claimedWordIndex = index / 256;
uint256 claimedBitIndex = index % 256;
uint256 claimedBitIndex = index % 256;
uint256 claimedWord = claimedBitMap[merkleIndex][claimedWordIndex];
uint256 claimedWord = claimedBitMap[merkleIndex][claimedWordIndex];
uint256 mask = (1 << claimedBitIndex);
uint256 mask = (1 << claimedBitIndex);
return claimedWord & mask == mask;
return claimedWord & mask == mask;
}
}


function _setClaimed(uint256 merkleIndex, uint256 index) private {
function _setClaimed(uint256 merkleIndex, uint256 index) private {
uint256 claimedWordIndex = index / 256;
uint256 claimedWordIndex = index / 256;
uint256 claimedBitIndex = index % 256;
uint256 claimedBitIndex = index % 256;
claimedBitMap[merkleIndex][claimedWordIndex] = claimedBitMap[merkleIndex][claimedWordIndex] | (1 << claimedBitIndex);
claimedBitMap[merkleIndex][claimedWordIndex] = claimedBitMap[merkleIndex][claimedWordIndex] | (1 << claimedBitIndex);
}
}


function claim(uint256 merkleIndex, uint256 index, uint256 amount, bytes32[] calldata merkleProof) external {
function claim(uint256 merkleIndex, uint256 index, uint256 amount, bytes32[] calldata merkleProof) external {
require(merkleIndex < merkleRoots.length, "MerkleDistributor: Invalid merkleIndex");
require(merkleIndex < merkleRoots.length, "MerkleAirdropper: Invalid merkleIndex");
require(!isClaimed(merkleIndex, index), 'MerkleDistributor: Drop already claimed.');
require(!isClaimed(merkleIndex, index), 'MerkleAirdropper: Drop already claimed.');


// Verify the merkle proof.
// Verify the merkle proof.
bytes32 node = keccak256(abi.encodePacked(index, msg.sender, amount));
bytes32 node = keccak256(abi.encodePacked(index, msg.sender, amount));
require(verify(merkleProof, merkleRoots[merkleIndex], node), 'MerkleDistributor: Invalid proof.');
require(verify(merkleProof, merkleRoots[merkleIndex], node), 'MerkleAirdropper: Invalid proof.');


// Mark it claimed and send the token.
// Mark it claimed and send the token.
_setClaimed(merkleIndex, index);
_setClaimed(merkleIndex, index);
rewardMinter.mint(msg.sender, amount);
IERC20(airdropToken).transferFrom(authority, msg.sender, amount);


emit Claimed(merkleIndex, index, msg.sender, amount);
emit Claimed(merkleIndex, index, msg.sender, amount);
}
}


function verify(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
function verify(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
bytes32 computedHash = leaf;
bytes32 computedHash = leaf;


for (uint256 i = 0; i < proof.length; i++) {
for (uint256 i = 0; i < proof.length; i++) {
bytes32 proofElement = proof[i];
bytes32 proofElement = proof[i];


if (computedHash <= proofElement) {
if (computedHash <= proofElement) {
// Hash(current computed hash + current element of the proof)
// Hash(current computed hash + current element of the proof)
computedHash = keccak256(abi.encodePacked(computedHash, proofElement));
computedHash = keccak256(abi.encodePacked(computedHash, proofElement));
} else {
} else {
// Hash(current element of the proof + current computed hash)
// Hash(current element of the proof + current computed hash)
computedHash = keccak256(abi.encodePacked(proofElement, computedHash));
computedHash = keccak256(abi.encodePacked(proofElement, computedHash));
}
}
}
}


// Check if the computed hash (root) is equal to the provided root
// Check if the computed hash (root) is equal to the provided root
return computedHash == root;
return computedHash == root;
}
}


}
}