编译器
0.8.11+commit.d7f03943
文件 1 的 5:Airdrop.sol
pragma solidity >=0.8.0 <0.9.0;
import "./vendor/@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./vendor/@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import "./interfaces/ModuleManager.sol";
import "./VestingPool.sol";
contract Airdrop is VestingPool {
bytes32 public root;
uint64 public immutable redeemDeadline;
constructor(
address _token,
address _manager,
uint64 _redeemDeadline
) VestingPool(_token, _manager) {
require(_redeemDeadline > block.timestamp, "Redeem deadline should be in the future");
redeemDeadline = _redeemDeadline;
}
function initializeRoot(bytes32 _root) public onlyPoolManager {
require(root == bytes32(0), "State root already initialized");
root = _root;
}
function redeem(
uint8 curveType,
uint16 durationWeeks,
uint64 startDate,
uint128 amount,
bytes32[] calldata proof
) external {
require(block.timestamp <= redeemDeadline, "Deadline to redeem vesting has been exceeded");
require(root != bytes32(0), "State root not initialized");
bytes32 vestingId = _addVesting(msg.sender, curveType, false, durationWeeks, startDate, amount);
require(MerkleProof.verify(proof, root, vestingId), "Invalid merkle proof");
}
function claimVestedTokensViaModule(
bytes32 vestingId,
address beneficiary,
uint128 tokensToClaim
) public {
uint128 tokensClaimed = updateClaimedTokens(vestingId, beneficiary, tokensToClaim);
require(IERC20(token).approve(poolManager, tokensClaimed), "Could not approve tokens");
uint256 balancePoolBefore = IERC20(token).balanceOf(address(this));
uint256 balanceBeneficiaryBefore = IERC20(token).balanceOf(beneficiary);
bytes memory transferData = abi.encodeWithSignature(
"transferFrom(address,address,uint256)",
address(this),
beneficiary,
tokensClaimed
);
require(ModuleManager(poolManager).execTransactionFromModule(token, 0, transferData, 0), "Module transaction failed");
require(IERC20(token).approve(poolManager, 0), "Could not set token allowance to 0");
uint256 balancePoolAfter = IERC20(token).balanceOf(address(this));
uint256 balanceBeneficiaryAfter = IERC20(token).balanceOf(beneficiary);
require(balancePoolAfter == balancePoolBefore - tokensClaimed, "Could not deduct tokens from pool");
require(balanceBeneficiaryAfter == balanceBeneficiaryBefore + tokensClaimed, "Could not add tokens to beneficiary");
}
function claimUnusedTokens(address beneficiary) external onlyPoolManager {
require(block.timestamp > redeemDeadline, "Tokens can still be redeemed");
uint256 unusedTokens = tokensAvailableForVesting();
require(unusedTokens > 0, "No tokens to claim");
require(IERC20(token).transfer(beneficiary, unusedTokens), "Token transfer failed");
}
function addVesting(
address,
uint8,
bool,
uint16,
uint64,
uint128
) public pure override {
revert("This method is not available for this contract");
}
}
文件 2 的 5: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 to, 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 from,
address to,
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);
}
文件 3 的 5: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 = _efficientHash(computedHash, proofElement);
} else {
computedHash = _efficientHash(proofElement, computedHash);
}
}
return computedHash;
}
function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
assembly {
mstore(0x00, a)
mstore(0x20, b)
value := keccak256(0x00, 0x40)
}
}
}
文件 4 的 5:ModuleManager.sol
pragma solidity >=0.8.0 <0.9.0;
interface ModuleManager {
function execTransactionFromModule(
address to,
uint256 value,
bytes memory data,
uint8 operation
) external returns (bool success);
}
文件 5 的 5:VestingPool.sol
pragma solidity >=0.8.0 <0.9.0;
import "./vendor/@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract VestingPool {
event AddedVesting(bytes32 indexed id, address indexed account);
event ClaimedVesting(bytes32 indexed id, address indexed account, address indexed beneficiary);
event PausedVesting(bytes32 indexed id);
event UnpausedVesting(bytes32 indexed id);
event CancelledVesting(bytes32 indexed id);
struct Vesting {
address account;
uint8 curveType;
bool managed;
uint16 durationWeeks;
uint64 startDate;
uint128 amount;
uint128 amountClaimed;
uint64 pausingDate;
bool cancelled;
}
bytes32 private constant DOMAIN_SEPARATOR_TYPEHASH = 0x47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218;
bytes32 private constant VESTING_TYPEHASH = 0x43838b5ce9ca440d1ac21b07179a1fdd88aa2175e5ea103f6e37aa6d18ce78ad;
address public immutable token;
address public immutable poolManager;
uint256 public totalTokensInVesting;
mapping(bytes32 => Vesting) public vestings;
modifier onlyPoolManager() {
require(msg.sender == poolManager, "Can only be called by pool manager");
_;
}
constructor(address _token, address _poolManager) {
token = _token;
poolManager = _poolManager;
}
function addVesting(
address account,
uint8 curveType,
bool managed,
uint16 durationWeeks,
uint64 startDate,
uint128 amount
) public virtual onlyPoolManager {
_addVesting(account, curveType, managed, durationWeeks, startDate, amount);
}
function tokensAvailableForVesting() public view virtual returns (uint256) {
return IERC20(token).balanceOf(address(this)) - totalTokensInVesting;
}
function _addVesting(
address account,
uint8 curveType,
bool managed,
uint16 durationWeeks,
uint64 startDate,
uint128 amount
) internal returns (bytes32 vestingId) {
require(account != address(0), "Invalid account");
require(curveType < 2, "Invalid vesting curve");
vestingId = vestingHash(account, curveType, managed, durationWeeks, startDate, amount);
require(vestings[vestingId].account == address(0), "Vesting id already used");
uint256 availableTokens = tokensAvailableForVesting();
require(availableTokens >= amount, "Not enough tokens available");
totalTokensInVesting += amount;
vestings[vestingId] = Vesting({
account: account,
curveType: curveType,
managed: managed,
durationWeeks: durationWeeks,
startDate: startDate,
amount: amount,
amountClaimed: 0,
pausingDate: 0,
cancelled: false
});
emit AddedVesting(vestingId, account);
}
function claimVestedTokens(
bytes32 vestingId,
address beneficiary,
uint128 tokensToClaim
) public {
uint128 tokensClaimed = updateClaimedTokens(vestingId, beneficiary, tokensToClaim);
require(IERC20(token).transfer(beneficiary, tokensClaimed), "Token transfer failed");
}
function updateClaimedTokens(
bytes32 vestingId,
address beneficiary,
uint128 tokensToClaim
) internal returns (uint128 tokensClaimed) {
require(beneficiary != address(0), "Cannot claim to 0-address");
Vesting storage vesting = vestings[vestingId];
require(vesting.account == msg.sender, "Can only be claimed by vesting owner");
uint128 availableClaim = _calculateVestedAmount(vesting) - vesting.amountClaimed;
tokensClaimed = tokensToClaim == type(uint128).max ? availableClaim : tokensToClaim;
require(tokensClaimed <= availableClaim, "Trying to claim too many tokens");
totalTokensInVesting -= tokensClaimed;
vesting.amountClaimed += tokensClaimed;
emit ClaimedVesting(vestingId, vesting.account, beneficiary);
}
function cancelVesting(bytes32 vestingId) public onlyPoolManager {
Vesting storage vesting = vestings[vestingId];
require(vesting.account != address(0), "Vesting not found");
require(vesting.managed, "Only managed vestings can be cancelled");
require(!vesting.cancelled, "Vesting already cancelled");
bool isFutureVesting = block.timestamp <= vesting.startDate;
if (vesting.pausingDate == 0) {
vesting.pausingDate = isFutureVesting ? vesting.startDate : uint64(block.timestamp);
}
uint128 unusedToken = isFutureVesting ? vesting.amount : vesting.amount - _calculateVestedAmount(vesting);
totalTokensInVesting -= unusedToken;
vesting.cancelled = true;
emit CancelledVesting(vestingId);
}
function pauseVesting(bytes32 vestingId) public onlyPoolManager {
Vesting storage vesting = vestings[vestingId];
require(vesting.account != address(0), "Vesting not found");
require(vesting.managed, "Only managed vestings can be paused");
require(vesting.pausingDate == 0, "Vesting already paused");
vesting.pausingDate = block.timestamp <= vesting.startDate ? vesting.startDate : uint64(block.timestamp);
emit PausedVesting(vestingId);
}
function unpauseVesting(bytes32 vestingId) public onlyPoolManager {
Vesting storage vesting = vestings[vestingId];
require(vesting.account != address(0), "Vesting not found");
require(vesting.pausingDate != 0, "Vesting is not paused");
require(!vesting.cancelled, "Vesting has been cancelled and cannot be unpaused");
uint64 timePaused = block.timestamp <= vesting.pausingDate ? 0 : uint64(block.timestamp) - vesting.pausingDate;
vesting.startDate = vesting.startDate + timePaused;
vesting.pausingDate = 0;
emit UnpausedVesting(vestingId);
}
function calculateVestedAmount(bytes32 vestingId) external view returns (uint128 vestedAmount, uint128 claimedAmount) {
Vesting storage vesting = vestings[vestingId];
require(vesting.account != address(0), "Vesting not found");
vestedAmount = _calculateVestedAmount(vesting);
claimedAmount = vesting.amountClaimed;
}
function _calculateVestedAmount(Vesting storage vesting) internal view returns (uint128 vestedAmount) {
require(vesting.startDate <= block.timestamp, "Vesting not active yet");
uint64 durationSeconds = uint64(vesting.durationWeeks) * 7 * 24 * 60 * 60;
uint64 vestedSeconds = vesting.pausingDate > 0
? vesting.pausingDate - vesting.startDate
: uint64(block.timestamp) - vesting.startDate;
if (vestedSeconds >= durationSeconds) {
vestedAmount = vesting.amount;
} else if (vesting.curveType == 0) {
vestedAmount = calculateLinear(vesting.amount, vestedSeconds, durationSeconds);
} else if (vesting.curveType == 1) {
vestedAmount = calculateExponential(vesting.amount, vestedSeconds, durationSeconds);
} else {
revert("Invalid curve type");
}
}
function calculateLinear(
uint128 targetAmount,
uint64 elapsedTime,
uint64 totalTime
) internal pure returns (uint128) {
uint256 amount = (uint256(targetAmount) * uint256(elapsedTime)) / uint256(totalTime);
require(amount <= type(uint128).max, "Overflow in curve calculation");
return uint128(amount);
}
function calculateExponential(
uint128 targetAmount,
uint64 elapsedTime,
uint64 totalTime
) internal pure returns (uint128) {
uint256 amount = (uint256(targetAmount) * uint256(elapsedTime) * uint256(elapsedTime)) / (uint256(totalTime) * uint256(totalTime));
require(amount <= type(uint128).max, "Overflow in curve calculation");
return uint128(amount);
}
function vestingHash(
address account,
uint8 curveType,
bool managed,
uint16 durationWeeks,
uint64 startDate,
uint128 amount
) public view returns (bytes32 vestingId) {
bytes32 domainSeparator = keccak256(abi.encode(DOMAIN_SEPARATOR_TYPEHASH, block.chainid, this));
bytes32 vestingDataHash = keccak256(abi.encode(VESTING_TYPEHASH, account, curveType, managed, durationWeeks, startDate, amount));
vestingId = keccak256(abi.encodePacked(bytes1(0x19), bytes1(0x01), domainSeparator, vestingDataHash));
}
}
{
"compilationTarget": {
"contracts/Airdrop.sol": "Airdrop"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs",
"useLiteralContent": true
},
"optimizer": {
"enabled": false,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"address","name":"_manager","type":"address"},{"internalType":"uint64","name":"_redeemDeadline","type":"uint64"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"id","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"}],"name":"AddedVesting","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"id","type":"bytes32"}],"name":"CancelledVesting","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"id","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"beneficiary","type":"address"}],"name":"ClaimedVesting","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"id","type":"bytes32"}],"name":"PausedVesting","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"id","type":"bytes32"}],"name":"UnpausedVesting","type":"event"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint8","name":"","type":"uint8"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint16","name":"","type":"uint16"},{"internalType":"uint64","name":"","type":"uint64"},{"internalType":"uint128","name":"","type":"uint128"}],"name":"addVesting","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes32","name":"vestingId","type":"bytes32"}],"name":"calculateVestedAmount","outputs":[{"internalType":"uint128","name":"vestedAmount","type":"uint128"},{"internalType":"uint128","name":"claimedAmount","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"vestingId","type":"bytes32"}],"name":"cancelVesting","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"beneficiary","type":"address"}],"name":"claimUnusedTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"vestingId","type":"bytes32"},{"internalType":"address","name":"beneficiary","type":"address"},{"internalType":"uint128","name":"tokensToClaim","type":"uint128"}],"name":"claimVestedTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"vestingId","type":"bytes32"},{"internalType":"address","name":"beneficiary","type":"address"},{"internalType":"uint128","name":"tokensToClaim","type":"uint128"}],"name":"claimVestedTokensViaModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_root","type":"bytes32"}],"name":"initializeRoot","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"vestingId","type":"bytes32"}],"name":"pauseVesting","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"poolManager","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"curveType","type":"uint8"},{"internalType":"uint16","name":"durationWeeks","type":"uint16"},{"internalType":"uint64","name":"startDate","type":"uint64"},{"internalType":"uint128","name":"amount","type":"uint128"},{"internalType":"bytes32[]","name":"proof","type":"bytes32[]"}],"name":"redeem","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"redeemDeadline","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"root","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":"tokensAvailableForVesting","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalTokensInVesting","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"vestingId","type":"bytes32"}],"name":"unpauseVesting","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint8","name":"curveType","type":"uint8"},{"internalType":"bool","name":"managed","type":"bool"},{"internalType":"uint16","name":"durationWeeks","type":"uint16"},{"internalType":"uint64","name":"startDate","type":"uint64"},{"internalType":"uint128","name":"amount","type":"uint128"}],"name":"vestingHash","outputs":[{"internalType":"bytes32","name":"vestingId","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"vestings","outputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint8","name":"curveType","type":"uint8"},{"internalType":"bool","name":"managed","type":"bool"},{"internalType":"uint16","name":"durationWeeks","type":"uint16"},{"internalType":"uint64","name":"startDate","type":"uint64"},{"internalType":"uint128","name":"amount","type":"uint128"},{"internalType":"uint128","name":"amountClaimed","type":"uint128"},{"internalType":"uint64","name":"pausingDate","type":"uint64"},{"internalType":"bool","name":"cancelled","type":"bool"}],"stateMutability":"view","type":"function"}]