Contract Name:
MooncatDistributor
Contract Source Code:
File 1 of 1 : MooncatDistributor
// SPDX-License-Identifier: NONE
pragma solidity 0.6.12;
// Part: OpenZeppelin/[email protected]/IERC20
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (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.
*/
function transfer(address recipient, uint256 amount) external returns (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.
*/
function allowance(address owner, address spender) external view returns (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.
*/
function approve(address spender, uint256 amount) external returns (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.
*/
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed 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.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}
// Part: OpenZeppelin/[email protected]/MerkleProof
/**
* @dev These functions deal with verification of Merkle trees (hash trees),
*/
library MerkleProof {
/**
* @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.
*/
function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (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 root
return computedHash == root;
}
}
// File: MooncatDistributor.sol
contract MooncatDistributor {
using MerkleProof for bytes32[];
IERC20 public token;
bytes32 public merkleRoot;
mapping(uint256 => uint256) public claimedBitMap;
uint256 expiryDate;
address owner;
event Claimed(uint256 index, address account, uint256 amount);
constructor(address _token, bytes32 _root, uint256 _expiry, address _owner) public {
token = IERC20(_token);
merkleRoot = _root;
expiryDate = _expiry;
owner = _owner;
}
function fetchUnclaimed() external {
require(block.timestamp > expiryDate, "!date");
require(token.transfer(owner, token.balanceOf(address(this))), "Token transfer failed");
}
function isClaimed(uint256 _index) public view returns(bool) {
uint256 wordIndex = _index / 256;
uint256 bitIndex = _index % 256;
uint256 word = claimedBitMap[wordIndex];
uint256 bitMask = 1 << bitIndex;
return word & bitMask == bitMask;
}
function _setClaimed(uint256 _index) internal {
uint256 wordIndex = _index / 256;
uint256 bitIndex = _index % 256;
claimedBitMap[wordIndex] |= 1 << bitIndex;
}
function claim(uint256 _index, address _account, uint256 _amount, bytes32[] memory _proof) external {
require(!isClaimed(_index), "Claimed already");
bytes32 node = keccak256(abi.encodePacked(_index, _account, _amount));
require(_proof.verify(merkleRoot, node), "Wrong proof");
_setClaimed(_index);
require(token.transfer(_account, _amount), "Token transfer failed");
emit Claimed(_index, _account, _amount);
}
}