Launchpad.sol 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. pragma solidity ^0.8.24;
  2. import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
  3. import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
  4. import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
  5. import "hardhat/console.sol"; // For debugging purposes, can be removed in production
  6. contract Launchpad is ReentrancyGuard {
  7. address public owner;
  8. address public token; // sale token address
  9. uint256 public totalTokens; // total tokens for sale
  10. uint256 public tokensSold; // tokens sold so far
  11. uint256 public startTime; // beginning time of the sale
  12. uint256 public endTime; // ending time of the sale
  13. bytes32 public merkleRoot; // Merkle root for whitelisting
  14. bool public enableClaimToken; // enable claim token
  15. uint256 public claimStartTime; // start time for claiming tokens
  16. address public paymentToken; // payment tokens accepted for the sale
  17. uint256 public salePaymentPrice; // payment token price in terms of sale token
  18. mapping(address => uint256) public claimableTokensAmount; // user contributions for claiming tokens
  19. uint256 public accountsCount; // number of accounts that have contributed
  20. event SaleCreated(address token, uint256 totalTokens);
  21. event Contributed(address user, uint256 amount, address paymentToken);
  22. event TokensClaimed(address user, uint256 amount);
  23. event SaleCancelled(address token, uint256 refundedAmount);
  24. event EnableClaimToken(bool enabled, uint256 claimStartTime);
  25. event DisableClaimToken(bool disabled);
  26. constructor() {
  27. owner = msg.sender;
  28. accountsCount = 0;
  29. }
  30. // Modifiers
  31. modifier onlyOwner() {
  32. require(msg.sender == owner, "Only owner can call this function");
  33. _;
  34. }
  35. modifier saleActive() {
  36. require(merkleRoot != bytes32(0), "MerkleRoot not initialized");
  37. console.log("Claim start time:", startTime);
  38. console.log("Current time:", block.timestamp);
  39. console.log("Claiming tokens enabled:", endTime);
  40. require(block.timestamp >= startTime && block.timestamp <= endTime, "Sale is not active");
  41. _;
  42. }
  43. modifier validClaimTime() {
  44. require(enableClaimToken, "Claiming tokens is not enabled");
  45. require(block.timestamp >= claimStartTime, "Claiming tokens not started yet");
  46. _;
  47. }
  48. // Owner Functions
  49. function createSale(
  50. address _token,
  51. uint256 _totalTokens,
  52. uint256 _startTime,
  53. uint256 _endTime,
  54. address _paymentToken,
  55. uint256 _price
  56. ) public onlyOwner {
  57. require(_token != address(0), "Invalid token address");
  58. require(_totalTokens > 0, "Total tokens must be greater than 0");
  59. require(_startTime < _endTime, "Invalid time range");
  60. paymentToken = _paymentToken;
  61. salePaymentPrice = _price;
  62. token = _token;
  63. totalTokens = _totalTokens;
  64. startTime = _startTime;
  65. endTime = _endTime;
  66. tokensSold = 0;
  67. merkleRoot = bytes32(0); // Reset Merkle root
  68. IERC20(_token).transferFrom(msg.sender, address(this), _totalTokens); // Transfer tokens to the contracts
  69. emit SaleCreated(token, totalTokens);
  70. }
  71. function withdrawRemainingTokens() public onlyOwner {
  72. require(block.timestamp > endTime, "Sale is still active");
  73. uint256 remaining = IERC20(token).balanceOf(address(this));
  74. IERC20(token).transfer(owner, remaining);
  75. }
  76. function withdrawPayments() public onlyOwner {
  77. require(block.timestamp > endTime, "Sale is still active");
  78. if (paymentToken == address(0)) {
  79. payable(owner).transfer(address(this).balance);
  80. } else {
  81. uint256 balance = IERC20(paymentToken).balanceOf(address(this));
  82. IERC20(paymentToken).transfer(owner, balance);
  83. }
  84. }
  85. function cancelSale() public onlyOwner {
  86. require(block.timestamp < startTime, "Sale already started");
  87. require(token != address(0), "Sale not initialized");
  88. uint256 balance = IERC20(token).balanceOf(address(this));
  89. require(balance > 0, "No tokens to refund");
  90. IERC20(token).transfer(owner, balance); // Refund tokens to owner
  91. token = address(0);
  92. totalTokens = 0;
  93. tokensSold = 0;
  94. startTime = 0;
  95. endTime = 0;
  96. merkleRoot = bytes32(0); // Reset Merkle root
  97. emit SaleCancelled(token, balance);
  98. }
  99. function enableClaimTokens(uint256 _enableClaimTokenTime) public onlyOwner {
  100. require(_enableClaimTokenTime >= block.timestamp, "Enable claim time must be in the future");
  101. require(_enableClaimTokenTime >= endTime, "Claiming tokens can only be enabled after the sale ends");
  102. enableClaimToken = true;
  103. claimStartTime = _enableClaimTokenTime; // Set claim start time to now
  104. emit EnableClaimToken(enableClaimToken, claimStartTime);
  105. }
  106. function disableClaimTokens() public onlyOwner {
  107. require(enableClaimToken, "Claiming tokens is already disabled");
  108. enableClaimToken = false;
  109. emit DisableClaimToken(enableClaimToken);
  110. }
  111. // Setter
  112. function setOwner(address _newOwner) public onlyOwner {
  113. require(_newOwner != address(0), "Invalid owner address");
  114. owner = _newOwner;
  115. }
  116. function setSalePayment(address _paymentToken, uint256 _price) public onlyOwner {
  117. require(_price > 0, "Price must be greater than 0");
  118. paymentToken = _paymentToken;
  119. salePaymentPrice = _price;
  120. }
  121. function setMerkleRoot(bytes32 _merkleRoot) public onlyOwner {
  122. merkleRoot = _merkleRoot;
  123. }
  124. //Getter
  125. function getOwner() public view returns (address) {
  126. return owner;
  127. }
  128. function getSaleToken() public view returns (address) {
  129. return token;
  130. }
  131. function getTotalTokens() public view returns (uint256) {
  132. return totalTokens;
  133. }
  134. function getTokensSold() public view returns (uint256) {
  135. return tokensSold;
  136. }
  137. function getStartTime() public view returns (uint256) {
  138. return startTime;
  139. }
  140. function getEndTime() public view returns (uint256) {
  141. return endTime;
  142. }
  143. function getSaleDetails()
  144. public
  145. view
  146. returns (
  147. address token,
  148. uint256 totalTokens,
  149. uint256 tokensSold,
  150. uint256 startTime,
  151. uint256 endTime,
  152. bytes32 merkleRoot,
  153. address paymentToken,
  154. uint256 salePaymentPrice
  155. )
  156. {
  157. return (token, totalTokens, tokensSold, startTime, endTime, merkleRoot, paymentToken, salePaymentPrice);
  158. }
  159. function getClaimableTokens(address _user) public view returns (uint256) {
  160. return claimableTokensAmount[_user];
  161. }
  162. function getPaymentTokenPrice() public view returns (uint256) {
  163. return salePaymentPrice;
  164. }
  165. function getPaymentTokens() public view returns (address) {
  166. return paymentToken;
  167. }
  168. function getClaimStartTime() public view returns (uint256) {
  169. return claimStartTime;
  170. }
  171. function isClaimEnabled() public view returns (bool) {
  172. return enableClaimToken;
  173. }
  174. //Main Function
  175. //MUST TEST !
  176. function contributeETH(
  177. uint256 _buyAmount,
  178. uint256 _maxBuyAmount,
  179. bytes32[] memory _proof
  180. ) public payable saleActive nonReentrant {
  181. require(paymentToken == address(0), "Payment token must be ETH for this sale");
  182. require(msg.value > 0, "Must send ETH");
  183. require(_buyAmount > 0, "Must buy a positive amount");
  184. require(_buyAmount <= _maxBuyAmount, "Buy amount exceeds max buy amount");
  185. require(salePaymentPrice > 0, "Payment token not accepted for this sale");
  186. // 验证 Merkle 证明
  187. bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(msg.sender, _maxBuyAmount))));
  188. require(MerkleProof.verify(_proof, merkleRoot, leaf), "Invalid proof");
  189. uint256 cost = (salePaymentPrice / 10 ** 18) * _buyAmount; // Assuming salePaymentPrice is in wei
  190. require(msg.value >= cost, "Not enough ETH sent");
  191. tokensSold += _buyAmount;
  192. if (claimableTokensAmount[msg.sender] == 0) {
  193. accountsCount++; // Increment accounts count only if this is the first contribution
  194. }
  195. claimableTokensAmount[msg.sender] += _buyAmount; // update user's claimable tokens
  196. require(claimableTokensAmount[msg.sender] <= _maxBuyAmount, "Buy amount exceeds max buy amount");
  197. payable(msg.sender).transfer(msg.value - cost); //refund excess ETH
  198. emit Contributed(msg.sender, _buyAmount, address(0));
  199. }
  200. function contributeERC20(
  201. uint256 _buyAmount,
  202. uint256 _maxBuyAmount,
  203. bytes32[] memory _proof
  204. ) public saleActive nonReentrant {
  205. require(_buyAmount > 0, "Must send a positive amount");
  206. require(_buyAmount <= _maxBuyAmount, "Buy amount exceeds max buy amount");
  207. bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(msg.sender, _maxBuyAmount))));
  208. require(MerkleProof.verify(_proof, merkleRoot, leaf), "Invalid proof");
  209. uint256 price = salePaymentPrice;
  210. require(price > 0, "Payment token not accepted for this sale");
  211. uint256 cost = (price / 10 ** 18) * _buyAmount; // Assuming salePaymentPrice is in wei
  212. require(
  213. IERC20(paymentToken).allowance(msg.sender, address(this)) >= cost,
  214. "Not enough allowance for payment token"
  215. );
  216. tokensSold += _buyAmount;
  217. if (claimableTokensAmount[msg.sender] == 0) {
  218. accountsCount++; // Increment accounts count only if this is the first contribution
  219. }
  220. claimableTokensAmount[msg.sender] += _buyAmount; // update user's claimable tokens
  221. console.log("Claimable tokens amount for user:", claimableTokensAmount[msg.sender]);
  222. require(claimableTokensAmount[msg.sender] <= _maxBuyAmount, "Buy amount exceeds max buy amount");
  223. IERC20(paymentToken).transferFrom(msg.sender, address(this), cost); // 从用户账户转移支付代币
  224. emit Contributed(msg.sender, _buyAmount, paymentToken);
  225. }
  226. function claimTokens() public validClaimTime {
  227. require(block.timestamp > endTime, "Sale is still active");
  228. uint256 amount = claimableTokensAmount[msg.sender];
  229. require(amount > 0, "No tokens to claim");
  230. if (tokensSold <= totalTokens) {
  231. IERC20(token).transfer(msg.sender, amount); // 分发销售代币
  232. emit TokensClaimed(msg.sender, amount);
  233. } else {
  234. uint256 boughtAmountAvg = totalTokens / accountsCount; // 平均每个账户购买的代币数量
  235. uint256 refundTokenAmount = amount - boughtAmountAvg; // 计算退款金额
  236. uint256 refundAmount = (refundAmount / 10 ** 18) * salePaymentPrice;
  237. if (paymentToken == address(0)) {
  238. payable(msg.sender).transfer(refundAmount); // 退款ETH
  239. } else {
  240. IERC20(paymentToken).transfer(msg.sender, refundAmount); // 退款ERC20代币
  241. }
  242. IERC20(token).transfer(msg.sender, boughtAmountAvg); // 分发销售代币
  243. }
  244. }
  245. }