文件 1 的 4:IERC20.sol
pragma solidity ^0.8.0;
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
文件 2 的 4:IMerkleDistributor.sol
pragma solidity ^0.8.7;
interface IMerkleDistributor {
event Claimed(uint256 index, address account);
event Clawback();
function claim(
uint256 index,
bytes32[] calldata merkleProof
)
external;
function claimByGovernance(
uint256 index,
address account,
bytes32[] calldata merkleProof
) external;
function clawback() external;
function token() external view returns (address);
function amountToClaim() external view returns (uint256);
function merkleRoot() external view returns (bytes32);
function isClaimed(uint256 index, address account) external view returns (bool);
function unlockTimestamp() external view returns (uint256);
function clawbackTimestamp() external view returns (uint256);
function verifyMerkleProof(
uint256 index,
address account,
bytes32[] calldata merkleProof
)
external
view
returns (bool);
}
文件 3 的 4:MerkleDistributor.sol
pragma solidity ^0.8.7;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import "./interfaces/IMerkleDistributor.sol";
contract MerkleDistributor is IMerkleDistributor {
address private immutable _token;
address private immutable _governance;
bytes32 private immutable _merkleRoot;
uint256 private immutable _unlockTimestamp;
uint256 private immutable _clawbackTimestamp;
uint256 private immutable _amountToClaim;
mapping(bytes32 => bool) private _claimed;
error ClaimLocked();
error ClawbackLocked();
error AlreadyClaimed();
error InvalidProof();
error NotGovernance();
error NotGovernanceOrSelf();
error ClawbackFailed();
error ClaimFailed();
modifier unlocked() {
if (block.timestamp < _unlockTimestamp) revert ClaimLocked();
_;
}
modifier clawbackAllowed() {
if (block.timestamp < _clawbackTimestamp) revert ClawbackLocked();
_;
}
modifier notClaimed(uint256 index, address account) {
if (isClaimed(index, account)) revert AlreadyClaimed();
_;
}
modifier validProof(
uint256 index,
address account,
bytes32[] memory merkleProof
) {
bool result = verifyMerkleProof(
index,
account,
merkleProof
);
if (!result) revert InvalidProof();
_;
}
modifier isGovernance() {
if (msg.sender != _governance) revert NotGovernance();
_;
}
constructor(
address token_,
uint256 amountToClaim_,
bytes32 merkleRoot_,
address governance_,
uint256 unlockTimestamp_,
uint256 clawbackTimestamp_
) {
_token = token_;
_amountToClaim = amountToClaim_;
_merkleRoot = merkleRoot_;
_governance = governance_;
_unlockTimestamp = unlockTimestamp_;
_clawbackTimestamp = clawbackTimestamp_;
}
function claim(
uint256 index,
bytes32[] calldata merkleProof
)
external
override
unlocked
{
_claim(index, msg.sender, merkleProof);
}
function claimByGovernance(
uint256 index,
address account,
bytes32[] calldata merkleProof
)
external
override
isGovernance
unlocked
{
_claim(index, account, merkleProof);
}
function clawback()
external
override
isGovernance
clawbackAllowed
{
emit Clawback();
uint256 balance = IERC20(_token).balanceOf(address(this));
bool result = IERC20(_token).transfer(_governance, balance);
if (!result) revert ClawbackFailed();
}
function token() external view override returns (address) {
return _token;
}
function amountToClaim() external view override returns (uint256) {
return _amountToClaim;
}
function merkleRoot() external view override returns (bytes32) {
return _merkleRoot;
}
function unlockTimestamp() external view override returns (uint256) {
return _unlockTimestamp;
}
function clawbackTimestamp() external view override returns (uint256) {
return _clawbackTimestamp;
}
function isClaimed(uint256 index, address account) public view override returns (bool) {
return _claimed[_node(index, account)] == true;
}
function verifyMerkleProof(
uint256 index,
address account,
bytes32[] memory merkleProof
)
public
view
override
returns (bool)
{
bytes32 node = _node(index, account);
return MerkleProof.verify(merkleProof, _merkleRoot, node);
}
function _claim(
uint256 index,
address account,
bytes32[] memory merkleProof
)
private
notClaimed(index, account)
validProof(index, account, merkleProof)
{
_setClaimed(index, account);
emit Claimed(index, account);
bool result = IERC20(_token).transfer(account, _amountToClaim);
if (!result) revert ClaimFailed();
}
function _setClaimed(uint256 index, address account) private {
_claimed[_node(index, account)] = true;
}
function _node(uint256 index, address account) private pure returns (bytes32) {
return keccak256(abi.encodePacked(index, account));
}
}
文件 4 的 4:MerkleProof.sol
pragma solidity ^0.8.0;
library MerkleProof {
function verify(
bytes32[] memory proof,
bytes32 root,
bytes32 leaf
) internal pure returns (bool) {
return processProof(proof, leaf) == root;
}
function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
bytes32 proofElement = proof[i];
if (computedHash <= proofElement) {
computedHash = keccak256(abi.encodePacked(computedHash, proofElement));
} else {
computedHash = keccak256(abi.encodePacked(proofElement, computedHash));
}
}
return computedHash;
}
}
{
"compilationTarget": {
"contracts/MerkleDistributor.sol": "MerkleDistributor"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs",
"useLiteralContent": true
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"token_","type":"address"},{"internalType":"uint256","name":"amountToClaim_","type":"uint256"},{"internalType":"bytes32","name":"merkleRoot_","type":"bytes32"},{"internalType":"address","name":"governance_","type":"address"},{"internalType":"uint256","name":"unlockTimestamp_","type":"uint256"},{"internalType":"uint256","name":"clawbackTimestamp_","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AlreadyClaimed","type":"error"},{"inputs":[],"name":"ClaimFailed","type":"error"},{"inputs":[],"name":"ClaimLocked","type":"error"},{"inputs":[],"name":"ClawbackFailed","type":"error"},{"inputs":[],"name":"ClawbackLocked","type":"error"},{"inputs":[],"name":"InvalidProof","type":"error"},{"inputs":[],"name":"NotGovernance","type":"error"},{"inputs":[],"name":"NotGovernanceOrSelf","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"index","type":"uint256"},{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Claimed","type":"event"},{"anonymous":false,"inputs":[],"name":"Clawback","type":"event"},{"inputs":[],"name":"amountToClaim","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"},{"internalType":"bytes32[]","name":"merkleProof","type":"bytes32[]"}],"name":"claim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"},{"internalType":"address","name":"account","type":"address"},{"internalType":"bytes32[]","name":"merkleProof","type":"bytes32[]"}],"name":"claimByGovernance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"clawback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"clawbackTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"},{"internalType":"address","name":"account","type":"address"}],"name":"isClaimed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"merkleRoot","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"token","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unlockTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"},{"internalType":"address","name":"account","type":"address"},{"internalType":"bytes32[]","name":"merkleProof","type":"bytes32[]"}],"name":"verifyMerkleProof","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}]