编译器
0.8.26+commit.8a97fa7a
文件 1 的 4:Context.sol
pragma solidity ^0.8.20;
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
文件 2 的 4:IERC20.sol
pragma solidity ^0.8.20;
interface IERC20 {
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 value) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
文件 3 的 4:Ownable.sol
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
abstract contract Ownable is Context {
address private _owner;
error OwnableUnauthorizedAccount(address account);
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
modifier onlyOwner() {
_checkOwner();
_;
}
function owner() public view virtual returns (address) {
return _owner;
}
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
文件 4 的 4:PresaleV2.sol
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract PresaleV2 is Ownable {
struct UserInfo {
uint256 tokensPurchased;
uint256 purchasedAmountClaimed;
uint256 tokensReferred;
uint256 referredAmountClaimed;
}
mapping(address => UserInfo) public users;
mapping(bytes => address) public referrers;
uint256 public totalTokensSupplied;
uint256 public totalTokensPurchased;
uint256 public tokenRatio;
uint256 public constant VESTING_PERIOD = 14 days;
uint256 public constant CLIFF_DURATION = 12 hours;
uint256 public constant INITIAL_UNLOCK_PERCENT = 75;
uint256 public constant BONUS = 3;
uint256 private constant PERCENT = 100;
uint256 private constant FALSE = 1;
uint256 private constant TRUE = 2;
uint256 public saleEnded = FALSE;
uint256 public claimEnabled = FALSE;
uint256 public claimTime;
uint256 public launchTime;
IERC20 public tokenAddress;
error ExceedsSupply();
error SaleNotEnded();
error InvalidSaleState();
error NotEnabled();
error AlreadyLaunched();
constructor(address _tokenAddress) Ownable(msg.sender) {
tokenAddress = IERC20(_tokenAddress);
}
receive() external payable {}
function launch(uint256 ratio, uint256 suppliedTokens) external onlyOwner {
if (launchTime != 0) {
revert AlreadyLaunched();
}
launchTime = block.timestamp;
tokenRatio = ratio;
totalTokensSupplied = suppliedTokens;
tokenAddress.transferFrom(msg.sender, address(this), suppliedTokens);
}
function endSale() external onlyOwner {
saleEnded = TRUE;
}
function enableClaims() external onlyOwner {
if (saleEnded != TRUE) {
revert SaleNotEnded();
}
claimEnabled = TRUE;
claimTime = block.timestamp;
tokenAddress.transfer(msg.sender, totalTokensSupplied - totalTokensPurchased);
}
function purchase(bytes calldata referral) external payable {
if (launchTime == 0 || saleEnded == TRUE) {
revert InvalidSaleState();
}
uint256 tokenAmountPreBonus = msg.value * tokenRatio;
uint256 bonusAmount;
address referrer;
if (referral.length != 0) {
referrer = referrers[referral];
if (referrer != address(0)) {
bonusAmount = tokenAmountPreBonus * BONUS / PERCENT;
}
}
if (totalTokensPurchased + tokenAmountPreBonus + (bonusAmount * 2) > totalTokensSupplied) {
revert ExceedsSupply();
}
totalTokensPurchased = totalTokensPurchased + tokenAmountPreBonus + (bonusAmount * 2);
users[msg.sender].tokensPurchased = users[msg.sender].tokensPurchased + (tokenAmountPreBonus + bonusAmount);
users[referrer].tokensReferred = users[referrer].tokensReferred + bonusAmount;
}
function claimTokensReferred() external {
if (claimEnabled != TRUE) {
revert NotEnabled();
}
uint256 vested = vestedAmount(users[msg.sender].tokensReferred);
uint256 claimable = vested - users[msg.sender].referredAmountClaimed;
users[msg.sender].referredAmountClaimed = vested;
tokenAddress.transfer(msg.sender, claimable);
}
function claim() external {
if (claimEnabled != TRUE) {
revert NotEnabled();
}
uint256 vested = vestedAmount(users[msg.sender].tokensPurchased);
uint256 claimable = vested - users[msg.sender].purchasedAmountClaimed;
users[msg.sender].purchasedAmountClaimed = vested;
tokenAddress.transfer(msg.sender, claimable);
}
function setReferrers(bytes[] calldata codes, address[] calldata addresses) external onlyOwner {
for(uint256 i; i < codes.length; ) {
referrers[codes[i]] = addresses[i];
unchecked {
++i;
}
}
}
function withdrawETH() external onlyOwner {
(bool success, ) = msg.sender.call{value: address(this).balance}("");
require(success);
}
function emergencyWithdrawTokens(address token) external onlyOwner {
IERC20(token).transfer(msg.sender, IERC20(token).balanceOf(address(this)));
}
function tokensRemainingToPurchase() external view returns (uint256){
return totalTokensSupplied - totalTokensPurchased;
}
function vestedAmount(uint256 totalPurchased) public view returns (uint256) {
if (claimTime == 0) {
return 0;
}
uint256 elapsedTime = block.timestamp - claimTime;
uint256 initalUnlockAmount = (totalPurchased * INITIAL_UNLOCK_PERCENT) / PERCENT;
if (elapsedTime < CLIFF_DURATION) {
return initalUnlockAmount;
} else if (elapsedTime >= CLIFF_DURATION + VESTING_PERIOD) {
return totalPurchased;
} else {
elapsedTime = block.timestamp - (claimTime + CLIFF_DURATION);
uint256 nonInitialAmount = (totalPurchased * (PERCENT - INITIAL_UNLOCK_PERCENT)) / PERCENT;
uint256 currVestedAmount = (nonInitialAmount * elapsedTime) / VESTING_PERIOD;
return initalUnlockAmount + currVestedAmount;
}
}
}
{
"compilationTarget": {
"contracts/PresaleV2.sol": "PresaleV2"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": [],
"viaIR": true
}
[{"inputs":[{"internalType":"address","name":"_tokenAddress","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AlreadyLaunched","type":"error"},{"inputs":[],"name":"ExceedsSupply","type":"error"},{"inputs":[],"name":"InvalidSaleState","type":"error"},{"inputs":[],"name":"NotEnabled","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[],"name":"SaleNotEnded","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[],"name":"BONUS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CLIFF_DURATION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INITIAL_UNLOCK_PERCENT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VESTING_PERIOD","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"claim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"claimEnabled","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"claimTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"claimTokensReferred","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"emergencyWithdrawTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"enableClaims","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"endSale","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"ratio","type":"uint256"},{"internalType":"uint256","name":"suppliedTokens","type":"uint256"}],"name":"launch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"launchTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"referral","type":"bytes"}],"name":"purchase","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes","name":"","type":"bytes"}],"name":"referrers","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"saleEnded","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"codes","type":"bytes[]"},{"internalType":"address[]","name":"addresses","type":"address[]"}],"name":"setReferrers","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"tokenAddress","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokenRatio","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokensRemainingToPurchase","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalTokensPurchased","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalTokensSupplied","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"users","outputs":[{"internalType":"uint256","name":"tokensPurchased","type":"uint256"},{"internalType":"uint256","name":"purchasedAmountClaimed","type":"uint256"},{"internalType":"uint256","name":"tokensReferred","type":"uint256"},{"internalType":"uint256","name":"referredAmountClaimed","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"totalPurchased","type":"uint256"}],"name":"vestedAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"withdrawETH","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]