-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
402 additions
and
66 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,236 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol"; | ||
import "@uniswap/v3-periphery/contracts/interfaces/INonfungiblePositionManager.sol"; | ||
import "@uniswap/v3-periphery/contracts/interfaces/IPeripheryImmutableState.sol"; | ||
import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; | ||
import "@uniswap/v3-periphery/contracts/interfaces/external/IWETH9.sol"; | ||
import "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol"; | ||
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; | ||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | ||
import "../utils/ReentrancyGuard.sol"; | ||
import "./MathUtil.sol"; | ||
|
||
contract AIMePowerFairLaunchV2 is ERC20, ReentrancyGuard, IERC721Receiver { | ||
uint256 public price; | ||
uint256 public amountPerUnits; | ||
uint256 public reservedAmount; | ||
uint256 public deadline; | ||
uint24 public poolFee; | ||
address public pool; | ||
|
||
uint256 public mintLimit; | ||
uint256 public minted; | ||
|
||
bool public started; | ||
address public launcher; | ||
|
||
address public immutable WETH; | ||
ISwapRouter public immutable swapRouter; | ||
INonfungiblePositionManager public immutable nonfungiblePositionManager; | ||
|
||
uint256 public positionTokenId; | ||
|
||
event AIMePowerFairMinted(address indexed to, uint256 amount, uint256 ethAmount); | ||
event AIMePowerFairRefund(address indexed to, uint256 amount, uint256 ethAmount); | ||
|
||
constructor( | ||
uint256 _price, | ||
uint256 _amountPerUnits, | ||
uint256 _totalSupply, | ||
uint256 _reservedAmount, | ||
address _launcher, | ||
uint256 _deadline, | ||
uint24 _poolFee, | ||
INonfungiblePositionManager _nonfungiblePositionManager, | ||
ISwapRouter _swapRouter, | ||
string memory _name, | ||
string memory _symbol | ||
) ERC20(_name, _symbol) { | ||
price = _price; | ||
amountPerUnits = _amountPerUnits; | ||
deadline = _deadline; | ||
launcher = _launcher; | ||
reservedAmount = _reservedAmount; | ||
poolFee = _poolFee; | ||
started = false; | ||
|
||
_mint(address(this), _totalSupply); | ||
|
||
// 50% for mint | ||
mintLimit = (_totalSupply - reservedAmount) / 2; | ||
|
||
// uniswap | ||
nonfungiblePositionManager = _nonfungiblePositionManager; | ||
swapRouter = _swapRouter; | ||
WETH = IPeripheryImmutableState(address(_swapRouter)).WETH9(); | ||
} | ||
|
||
/// @notice Collects the fees associated with provided liquidity | ||
function collectFees() external { | ||
// set amount0Max and amount1Max to uint256.max to collect all fees | ||
INonfungiblePositionManager.CollectParams memory params = | ||
INonfungiblePositionManager.CollectParams({ | ||
tokenId: positionTokenId, | ||
recipient: address(this), | ||
amount0Max: type(uint128).max, | ||
amount1Max: type(uint128).max | ||
}); | ||
|
||
(uint256 amount0, uint256 amount1) = nonfungiblePositionManager.collect(params); | ||
|
||
// todo: send collected fee to receiver or just buy token | ||
} | ||
|
||
receive() external payable { | ||
// todo: require value != 0? | ||
if (msg.sender != launcher) { | ||
if (block.timestamp > deadline || minted == mintLimit) { | ||
start(); | ||
} else { | ||
mint(); | ||
} | ||
} else { | ||
start(); | ||
} | ||
} | ||
|
||
function onERC721Received( | ||
address, | ||
address, | ||
uint256, | ||
bytes calldata | ||
) external override returns (bytes4) { | ||
return this.onERC721Received.selector; | ||
} | ||
|
||
function mint() internal virtual nonReentrant { | ||
require(!started, "AIMEPowerFairLaunch: already started"); | ||
require(msg.value >= price, "AIMEPowerFairLaunch: value not match"); | ||
require(!_isContract(msg.sender), "AIMEPowerFairLaunch: can not mint to contract"); | ||
require(msg.sender == tx.origin, "AIMEPowerFairLaunch: can not mint to contract."); | ||
|
||
uint256 units = msg.value / price; | ||
uint256 amountDesired = units * amountPerUnits; | ||
uint256 amount = amountDesired; | ||
if (minted + amountDesired > mintLimit) { | ||
amount = mintLimit - minted; | ||
units = amount / amountPerUnits + 1; | ||
} | ||
|
||
uint256 realCost = units * price; | ||
uint256 refund = msg.value - realCost; | ||
|
||
minted += amount; | ||
_transfer(address(this), msg.sender, amount); | ||
|
||
emit AIMePowerFairMinted(msg.sender, amount, realCost); | ||
|
||
if (refund > 0) { | ||
(bool success, ) = payable(msg.sender).call{value: refund}(""); | ||
require(success, "Failed to refund"); | ||
} | ||
} | ||
|
||
function start() internal { | ||
require(!started, "AIMEPowerFairLaunch: already started"); | ||
started = true; | ||
|
||
// eth -> weth | ||
IWETH9(WETH).deposit{value: address(this).balance}(); | ||
|
||
if (address(this) < WETH) { | ||
_createPool(address(this), WETH, minted, IWETH9(WETH).balanceOf(address(this))); | ||
} else { | ||
_createPool(WETH, address(this), IWETH9(WETH).balanceOf(address(this)), minted); | ||
} | ||
// todo: launch event | ||
} | ||
|
||
function _createPool(address token0, address token1, uint256 token0Amount, uint256 token1Amount) internal { | ||
uint160 sqrtPriceX96 = uint160((2 ** 96) * MathUtil.SquareRoot(token0Amount) / MathUtil.SquareRoot(token1Amount)); | ||
|
||
// init pool | ||
pool = nonfungiblePositionManager | ||
.createAndInitializePoolIfNecessary( | ||
token0, | ||
token1, | ||
poolFee, | ||
sqrtPriceX96 | ||
); | ||
|
||
// Approve the position manager | ||
TransferHelper.safeApprove( | ||
token0, | ||
address(nonfungiblePositionManager), | ||
token0Amount | ||
); | ||
TransferHelper.safeApprove( | ||
token1, | ||
address(nonfungiblePositionManager), | ||
token1Amount | ||
); | ||
|
||
int24 tickSpacing = IUniswapV3Pool(pool).tickSpacing(); | ||
(, int24 tick, , , , , ) = IUniswapV3Pool(pool).slot0(); | ||
|
||
// todo: calculate tick lower and upper | ||
INonfungiblePositionManager.MintParams | ||
memory params = INonfungiblePositionManager.MintParams({ | ||
token0: token0, | ||
token1: token1, | ||
fee: poolFee, | ||
tickLower: tick - 2 * tickSpacing, | ||
tickUpper: tick - 2 * tickSpacing, | ||
amount0Desired: token0Amount, | ||
amount1Desired: token1Amount, | ||
amount0Min: token0Amount, | ||
amount1Min: token1Amount, | ||
recipient: address(this), | ||
deadline: block.timestamp | ||
}); | ||
|
||
(uint256 tokenId, uint256 liquidity, uint256 amount0, uint256 amount1) = nonfungiblePositionManager | ||
.mint(params); | ||
|
||
positionTokenId = tokenId; | ||
|
||
// todo: refund tokens | ||
} | ||
|
||
function _afterTokenTransfer( | ||
address from, | ||
address to, | ||
uint256 amount | ||
) internal override { | ||
if (!started && to == address(this) && from != address(0)) { | ||
_refund(from, amount); | ||
} | ||
} | ||
|
||
function _refund(address from, uint256 value) internal nonReentrant { | ||
require(!started, "AIMEPowerFairLaunch: already started"); | ||
require(!_isContract(from), "AIMEPowerFairLaunch: can not refund to contract"); | ||
require(from == tx.origin, "AIMEPowerFairLaunch: can not refund to contract."); | ||
require(value >= amountPerUnits, "AIMEPowerFairLaunch: value not match"); | ||
require(value % amountPerUnits == 0, "AIMEPowerFairLaunch: value not match"); | ||
|
||
uint256 ethAmount = (value / amountPerUnits) * price; | ||
require(ethAmount > 0, "AIMEPowerFairLaunch: no refund"); | ||
|
||
minted -= value; | ||
(bool success, ) = payable(from).call{value: ethAmount}(""); | ||
require(success, "Failed to refund"); | ||
|
||
emit AIMePowerFairRefund(from, value, ethAmount); | ||
} | ||
|
||
function _isContract(address _addr) internal view returns (bool) { | ||
uint32 size; | ||
assembly { | ||
size := extcodesize(_addr) | ||
} | ||
return (size > 0); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
library MathUtil { | ||
function SquareRoot(uint y) internal pure returns (uint z) { | ||
if (y > 3) { | ||
z = y; | ||
uint x = y / 2 + 1; | ||
while (x < z) { | ||
z = x; | ||
x = (y / x + x) / 2; | ||
} | ||
} else if (y != 0) { | ||
z = 1; | ||
} | ||
} | ||
} |
Oops, something went wrong.