// SPDX-License-Identifier: MITpragmasolidity >=0.6.0 <0.8.0;/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/interfaceIERC20{
/**
* @dev Returns the amount of tokens in existence.
*/functiontotalSupply() externalviewreturns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/functionbalanceOf(address account) externalviewreturns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/functiontransfer(address recipient, uint256 amount) externalreturns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/functionallowance(address owner, address spender) externalviewreturns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/functionapprove(address spender, uint256 amount) externalreturns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/functiontransferFrom(address sender, address recipient, uint256 amount) externalreturns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/eventTransfer(addressindexedfrom, addressindexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/eventApproval(addressindexed owner, addressindexed spender, uint256 value);
}
Contract Source Code
File 2 of 5: IMerkleDistributor.sol
// SPDX-License-Identifier: UNLICENSEDpragmasolidity >=0.5.0;// Allows anyone to claim a token if they exist in a merkle root.interfaceIMerkleDistributor{
// Returns the address of the token distributed by this contract.functiontoken() externalviewreturns (address);
// Returns the merkle root of the merkle tree containing account balances available to claim.functionmerkleRoot() externalviewreturns (bytes32);
// Returns the current claiming weekfunctionweek() externalviewreturns (uint32);
// Returns true if the claim function is frozenfunctionfrozen() externalviewreturns (bool);
// Returns true if the index has been marked claimed.functionisClaimed(uint256 index) externalviewreturns (bool);
// Claim the given amount of the token to the given address. Reverts if the inputs are invalid.functionclaim(uint256 index, address account, uint256 amount, bytes32[] calldata merkleProof) external;
// Freezes the claim function and allow the merkleRoot to be changed.functionfreeze() external;
// Unfreezes the claim function.functionunfreeze() external;
// Update the merkle root and increment the week.functionupdateMerkleRoot(bytes32 newMerkleRoot) external;
// This event is triggered whenever a call to #claim succeeds.eventClaimed(uint256 index, uint256 amount, addressindexed account, uint256indexed week);
// This event is triggered whenever the merkle root gets updated.eventMerkleRootUpdated(bytes32indexed merkleRoot, uint32indexed week);
}
Contract Source Code
File 3 of 5: MerkleDistributor.sol
// SPDX-License-Identifier: UNLICENSEDpragmasolidity =0.6.11;import"@openzeppelin/contracts/token/ERC20/IERC20.sol";
import"@openzeppelin/contracts/cryptography/MerkleProof.sol";
import"./interfaces/IMerkleDistributor.sol";
import"./Ownable.sol";
contractMerkleDistributorisIMerkleDistributor, Ownable{
addresspublicimmutableoverride token;
bytes32publicoverride merkleRoot;
uint32publicoverride week;
boolpublicoverride frozen;
// This is a packed array of booleans.mapping(uint256=>mapping(uint256=>uint256)) private claimedBitMap;
constructor(address token_, bytes32 merkleRoot_) public{
token = token_;
merkleRoot = merkleRoot_;
week =0;
frozen =false;
}
functionisClaimed(uint256 index) publicviewoverridereturns (bool) {
uint256 claimedWordIndex = index /256;
uint256 claimedBitIndex = index %256;
uint256 claimedWord = claimedBitMap[week][claimedWordIndex];
uint256 mask = (1<< claimedBitIndex);
return claimedWord & mask == mask;
}
function_setClaimed(uint256 index) private{
uint256 claimedWordIndex = index /256;
uint256 claimedBitIndex = index %256;
claimedBitMap[week][claimedWordIndex] = claimedBitMap[week][claimedWordIndex] | (1<< claimedBitIndex);
}
functionclaim(uint256 index, address account, uint256 amount, bytes32[] calldata merkleProof) externaloverride{
require(!frozen, 'MerkleDistributor: Claiming is frozen.');
require(!isClaimed(index), 'MerkleDistributor: Drop already claimed.');
// Verify the merkle proof.bytes32 node =keccak256(abi.encodePacked(index, account, amount));
require(MerkleProof.verify(merkleProof, merkleRoot, node), 'MerkleDistributor: Invalid proof.');
// Mark it claimed and send the token.
_setClaimed(index);
require(IERC20(token).transfer(account, amount), 'MerkleDistributor: Transfer failed.');
emit Claimed(index, amount, account, week);
}
functionfreeze() publicoverrideonlyOwner{
frozen =true;
}
functionunfreeze() publicoverrideonlyOwner{
frozen =false;
}
functionupdateMerkleRoot(bytes32 _merkleRoot) publicoverrideonlyOwner{
require(frozen, 'MerkleDistributor: Contract must be frozen.');
// Increment the week (simulates the clearing of the claimedBitMap)
week = week +1;
// Set the new merkle root
merkleRoot = _merkleRoot;
emit MerkleRootUpdated(merkleRoot, week);
}
}
Contract Source Code
File 4 of 5: MerkleProof.sol
// SPDX-License-Identifier: MITpragmasolidity >=0.6.0 <0.8.0;/**
* @dev These functions deal with verification of Merkle trees (hash trees),
*/libraryMerkleProof{
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*/functionverify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internalpurereturns (bool) {
bytes32 computedHash = leaf;
for (uint256 i =0; i < proof.length; i++) {
bytes32 proofElement = proof[i];
if (computedHash <= proofElement) {
// Hash(current computed hash + current element of the proof)
computedHash =keccak256(abi.encodePacked(computedHash, proofElement));
} else {
// Hash(current element of the proof + current computed hash)
computedHash =keccak256(abi.encodePacked(proofElement, computedHash));
}
}
// Check if the computed hash (root) is equal to the provided rootreturn computedHash == root;
}
}