MemeFolioAtomicLauncher
0x7861a13054ca22424bc18df742355df58333520b
Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
16,732 bytes
ABI entries
31
7 read · 4 write
License
none
Contract information
- Address
- 0x7861a13054ca22424bc18df742355df58333520b
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.26+commit.8a97fa7a
- Optimization
- Enabled
- Creator
- 0x4d1480c607…e84adf3d12
- Creation tx
- 0x4ad871a4e3…ec85877b06
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (7)
launchManifestHash(tuple) → bytes32
launched() → bool
manifestHash() → bytes32
owner() → address
permit2() → address
poolManager() → address
positionManager() → address
Events (3)
LaunchCommittedLaunchCompletedOwnershipTransferred
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "owner_",
"type": "address"
},
{
"internalType": "contract IPoolManager",
"name": "poolManager_",
"type": "address"
},
{
"internalType": "contract IPositionManager",
"name": "positionManager_",
"type": "address"
},
{
"internalType": "contract IAllowanceTransfer",
"name": "permit2_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "Create2EmptyBytecode",
"type": "error"
},
{
"inputs": [],
"name": "FailedCall",
"type": "error"
},
{
"inputs": [],
"name": "FailedDeployment",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "balance",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "needed",
"type": "uint256"
}
],
"name": "InsufficientBalance",
"type": "error"
},
{
"inputs": [],
"name": "InvalidAddress",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "expected",
"type": "address"
},
{
"internalType": "address",
"name": "actual",
"type": "address"
}
],
"name": "InvalidDeployment",
"type": "error"
},
{
"inputs": [],
"name": "InvalidLaunchConfiguration",
"type": "error"
},
{
"inputs": [],
"name": "InvalidManifest",
"type": "error"
},
{
"inputs": [],
"name": "LaunchInvariantFailed",
"type": "error"
},
{
"inputs": [],
"name": "ManifestAlreadyCommitted",
"type": "error"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "expected",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "actual",
"type": "bytes32"
}
],
"name": "ManifestMismatch",
"type": "error"
},
{
"inputs": [],
"name": "NoManifestCommitted",
"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": "ReentrancyGuardReentrantCall",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "SafeERC20FailedOperation",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "manifestHash",
"type": "bytes32"
}
],
"name": "LaunchCommitted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "manifestHash",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "hook",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "locker",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "distributor",
"type": "address"
},
{
"indexed": false,
"internalType": "bytes32",
"name": "poolId",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "uint256",
"name": "positionTokenId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "deploymentBlock",
"type": "uint256"
}
],
"name": "LaunchCompleted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "manifestHash_",
"type": "bytes32"
}
],
"name": "commitLaunch",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"components": [
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "initCode",
"type": "bytes"
},
{
"internalType": "address",
"name": "expectedAddress",
"type": "address"
}
],
"internalType": "struct MemeFolioAtomicLauncher.Deployment",
"name": "token",
"type": "tuple"
},
{
"components": [
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "initCode",
"type": "bytes"
},
{
"internalType": "address",
"name": "expectedAddress",
"type": "address"
}
],
"internalType": "struct MemeFolioAtomicLauncher.Deployment",
"name": "hook",
"type": "tuple"
},
{
"components": [
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "initCode",
"type": "bytes"
},
{
"internalType": "address",
"name": "expectedAddress",
"type": "address"
}
],
"internalType": "struct MemeFolioAtomicLauncher.Deployment",
"name": "locker",
"type": "tuple"
},
{
"components": [
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "initCode",
"type": "bytes"
},
{
"internalType": "address",
"name": "expectedAddress",
"type": "address"
}
],
"internalType": "struct MemeFolioAtomicLauncher.Deployment",
"name": "distributor",
"type": "tuple"
},
{
"internalType": "string",
"name": "tokenName",
"type": "string"
},
{
"internalType": "string",
"name": "tokenSymbol",
"type": "string"
},
{
"internalType": "uint256",
"name": "tokenSupply",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "minHolderBalance",
"type": "uint256"
},
{
"internalType": "address payable",
"name": "executor",
"type": "address"
},
{
"internalType": "address payable",
"name": "treasury",
"type": "address"
},
{
"internalType": "address",
"name": "admin",
"type": "address"
},
{
"internalType": "address",
"name": "operator",
"type": "address"
},
{
"internalType": "address[5]",
"name": "rewardTokens",
"type": "address[5]"
},
{
"internalType": "address[]",
"name": "excludedAccounts",
"type": "address[]"
},
{
"internalType": "uint256",
"name": "maxDistributionBatchSize",
"type": "uint256"
},
{
"internalType": "uint24",
"name": "poolFee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"internalType": "uint160",
"name": "initialSqrtPriceX96",
"type": "uint160"
}
],
"internalType": "struct MemeFolioAtomicLauncher.LaunchData",
"name": "data",
"type": "tuple"
}
],
"name": "launch",
"outputs": [
{
"components": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "hook",
"type": "address"
},
{
"internalType": "address",
"name": "locker",
"type": "address"
},
{
"internalType": "address",
"name": "distributor",
"type": "address"
},
{
"internalType": "bytes32",
"name": "poolId",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "positionTokenId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "deploymentBlock",
"type": "uint256"
}
],
"internalType": "struct MemeFolioAtomicLauncher.LaunchResult",
"name": "result",
"type": "tuple"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"components": [
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "initCode",
"type": "bytes"
},
{
"internalType": "address",
"name": "expectedAddress",
"type": "address"
}
],
"internalType": "struct MemeFolioAtomicLauncher.Deployment",
"name": "token",
"type": "tuple"
},
{
"components": [
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "initCode",
"type": "bytes"
},
{
"internalType": "address",
"name": "expectedAddress",
"type": "address"
}
],
"internalType": "struct MemeFolioAtomicLauncher.Deployment",
"name": "hook",
"type": "tuple"
},
{
"components": [
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "initCode",
"type": "bytes"
},
{
"internalType": "address",
"name": "expectedAddress",
"type": "address"
}
],
"internalType": "struct MemeFolioAtomicLauncher.Deployment",
"name": "locker",
"type": "tuple"
},
{
"components": [
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "initCode",
"type": "bytes"
},
{
"internalType": "address",
"name": "expectedAddress",
"type": "address"
}
],
"internalType": "struct MemeFolioAtomicLauncher.Deployment",
"name": "distributor",
"type": "tuple"
},
{
"internalType": "string",
"name": "tokenName",
"type": "string"
},
{
"internalType": "string",
"name": "tokenSymbol",
"type": "string"
},
{
"internalType": "uint256",
"name": "tokenSupply",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "minHolderBalance",
"type": "uint256"
},
{
"internalType": "address payable",
"name": "executor",
"type": "address"
},
{
"internalType": "address payable",
"name": "treasury",
"type": "address"
},
{
"internalType": "address",
"name": "admin",
"type": "address"
},
{
"internalType": "address",
"name": "operator",
"type": "address"
},
{
"internalType": "address[5]",
"name": "rewardTokens",
"type": "address[5]"
},
{
"internalType": "address[]",
"name": "excludedAccounts",
"type": "address[]"
},
{
"internalType": "uint256",
"name": "maxDistributionBatchSize",
"type": "uint256"
},
{
"internalType": "uint24",
"name": "poolFee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"internalType": "uint160",
"name": "initialSqrtPriceX96",
"type": "uint160"
}
],
"internalType": "struct MemeFolioAtomicLauncher.LaunchData",
"name": "data",
"type": "tuple"
}
],
"name": "launchManifestHash",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "launched",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "manifestHash",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "permit2",
"outputs": [
{
"internalType": "contract IAllowanceTransfer",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "poolManager",
"outputs": [
{
"internalType": "contract IPoolManager",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "positionManager",
"outputs": [
{
"internalType": "contract IPositionManager",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { IHooks } from "@uniswap/v4-core/src/interfaces/IHooks.sol";
import { IPoolManager } from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import { TickMath } from "@uniswap/v4-core/src/libraries/TickMath.sol";
import { Currency, CurrencyLibrary } from "@uniswap/v4-core/src/types/Currency.sol";
import { PoolId, PoolIdLibrary } from "@uniswap/v4-core/src/types/PoolId.sol";
import { PoolKey } from "@uniswap/v4-core/src/types/PoolKey.sol";
import { IPositionManager } from "@uniswap/v4-periphery/src/interfaces/IPositionManager.sol";
import { Actions } from "@uniswap/v4-periphery/src/libraries/Actions.sol";
import { LiquidityAmounts } from "@uniswap/v4-periphery/src/libraries/LiquidityAmounts.sol";
import {
PositionInfo,
PositionInfoLibrary
} from "@uniswap/v4-periphery/src/libraries/PositionInfoLibrary.sol";
import { IAllowanceTransfer } from "permit2/src/interfaces/IAllowanceTransfer.sol";
import { MemeFolioDistributor } from "./MemeFolioDistributor.sol";
import { MemeFolioTaxHook } from "./MemeFolioTaxHook.sol";
import { MemeFolioToken } from "./MemeFolioToken.sol";
import { PermanentV4LiquidityLocker } from "./PermanentV4LiquidityLocker.sol";
/// @title MemeFolioAtomicLauncher
/// @notice One-shot launch coordinator committed to one complete Meme ETF launch manifest.
contract MemeFolioAtomicLauncher is Ownable, ReentrancyGuard {
using Address for address payable;
using PoolIdLibrary for PoolKey;
using PositionInfoLibrary for PositionInfo;
using SafeERC20 for IERC20;
struct Deployment {
bytes32 salt;
bytes initCode;
address expectedAddress;
}
struct LaunchData {
Deployment token;
Deployment hook;
Deployment locker;
Deployment distributor;
string tokenName;
string tokenSymbol;
uint256 tokenSupply;
uint256 minHolderBalance;
address payable executor;
address payable treasury;
address admin;
address operator;
address[5] rewardTokens;
address[] excludedAccounts;
uint256 maxDistributionBatchSize;
uint24 poolFee;
int24 tickSpacing;
int24 tickLower;
int24 tickUpper;
uint160 initialSqrtPriceX96;
}
struct LaunchResult {
address token;
address hook;
address locker;
address distributor;
bytes32 poolId;
uint256 positionTokenId;
uint256 deploymentBlock;
}
address internal constant DEAD = 0x000000000000000000000000000000000000dEaD;
IPoolManager public immutable poolManager;
IPositionManager public immutable positionManager;
IAllowanceTransfer public immutable permit2;
bytes32 public manifestHash;
bool public launched;
error InvalidAddress();
error InvalidDeployment(address expected, address actual);
error InvalidLaunchConfiguration();
error InvalidManifest();
error LaunchInvariantFailed();
error ManifestMismatch(bytes32 expected, bytes32 actual);
error ManifestAlreadyCommitted();
error NoManifestCommitted();
event LaunchCommitted(bytes32 indexed manifestHash);
event LaunchCompleted(
bytes32 indexed manifestHash,
address indexed token,
address indexed hook,
address locker,
address distributor,
bytes32 poolId,
uint256 positionTokenId,
uint256 deploymentBlock
);
constructor(
address owner_,
IPoolManager poolManager_,
IPositionManager positionManager_,
IAllowanceTransfer permit2_
) Ownable(owner_) {
if (
owner_ == address(0) || address(poolManager_) == address(0)
|| address(positionManager_) == address(0) || address(permit2_) == address(0)
) revert InvalidAddress();
poolManager = poolManager_;
positionManager = positionManager_;
permit2 = permit2_;
}
function commitLaunch(bytes32 manifestHash_) external onlyOwner {
if (manifestHash_ == bytes32(0)) revert InvalidManifest();
if (manifestHash != bytes32(0)) revert ManifestAlreadyCommitted();
manifestHash = manifestHash_;
emit LaunchCommitted(manifestHash_);
}
/// @notice Hashes every launch input together with this chain and the immutable dependencies.
function launchManifestHash(LaunchData calldata data) public view returns (bytes32) {
bytes32 launchDataHash = keccak256(abi.encode(data));
return keccak256(
abi.encode(
block.chainid,
address(this),
address(poolManager),
address(positionManager),
address(permit2),
launchDataHash
)
);
}
/// @notice Creates and verifies the complete first-tradable Meme ETF state atomically.
function launch(LaunchData calldata data)
external
onlyOwner
nonReentrant
returns (LaunchResult memory result)
{
bytes32 committed = manifestHash;
if (committed == bytes32(0)) revert NoManifestCommitted();
bytes32 actualManifest = launchManifestHash(data);
if (actualManifest != committed) revert ManifestMismatch(committed, actualManifest);
if (launched) revert LaunchInvariantFailed();
_validateLaunchData(data);
// Effects precede every external launch call. Any later failure reverts this flag together
// with all CREATE2 deployments, while successful execution permanently blocks reentry.
launched = true;
_transferOwnership(address(0));
result.token = _deploy(data.token);
result.hook = _deploy(data.hook);
result.locker = _deploy(data.locker);
result.distributor = _deploy(data.distributor);
_verifyComponents(data, result);
PoolKey memory key = _poolKey(data, result);
result.poolId = PoolId.unwrap(key.toId());
{
int24 initializedTick = poolManager.initialize(key, data.initialSqrtPriceX96);
if (initializedTick != data.tickUpper) revert LaunchInvariantFailed();
}
if (MemeFolioTaxHook(payable(result.hook)).launchAuthority() != address(0)) {
revert LaunchInvariantFailed();
}
uint128 liquidity = LiquidityAmounts.getLiquidityForAmount1(
TickMath.getSqrtPriceAtTick(data.tickLower), data.initialSqrtPriceX96, data.tokenSupply
);
if (liquidity == 0) revert InvalidLaunchConfiguration();
result.positionTokenId = _mintPosition(data, result, key, liquidity);
PermanentV4LiquidityLocker(result.locker).finalizeLock(result.positionTokenId);
IERC20 token = IERC20(result.token);
uint256 dust = token.balanceOf(address(this));
if (dust != 0) token.safeTransfer(DEAD, dust);
permit2.approve(result.token, address(positionManager), 0, 0);
token.forceApprove(address(permit2), 0);
uint256 forcedNative = address(this).balance;
if (forcedNative != 0) data.treasury.sendValue(forcedNative);
_verifyFinalState(data, result, token);
result.deploymentBlock = block.number;
emit LaunchCompleted(
committed,
result.token,
result.hook,
result.locker,
result.distributor,
result.poolId,
result.positionTokenId,
result.deploymentBlock
);
}
function _validateLaunchData(LaunchData calldata data) private view {
if (
bytes(data.tokenName).length == 0 || bytes(data.tokenSymbol).length == 0
|| data.tokenSupply == 0 || data.minHolderBalance == 0
|| data.minHolderBalance > data.tokenSupply || data.executor == address(0)
|| data.treasury == address(0) || data.admin == address(0)
|| data.operator == address(0) || data.executor == data.treasury
|| data.poolFee != 0 || data.tickSpacing <= 0 || data.tickLower >= data.tickUpper
|| data.tickLower % data.tickSpacing != 0 || data.tickUpper % data.tickSpacing != 0
|| data.initialSqrtPriceX96 != TickMath.getSqrtPriceAtTick(data.tickUpper)
|| data.maxDistributionBatchSize == 0 || data.maxDistributionBatchSize > 100
) revert InvalidLaunchConfiguration();
_validateDeployment(data.token);
_validateDeployment(data.hook);
_validateDeployment(data.locker);
_validateDeployment(data.distributor);
if (
data.token.expectedAddress == data.hook.expectedAddress
|| data.token.expectedAddress == data.locker.expectedAddress
|| data.token.expectedAddress == data.distributor.expectedAddress
|| data.hook.expectedAddress == data.locker.expectedAddress
|| data.hook.expectedAddress == data.distributor.expectedAddress
|| data.locker.expectedAddress == data.distributor.expectedAddress
) revert InvalidLaunchConfiguration();
for (uint256 i; i < data.rewardTokens.length; ++i) {
address reward = data.rewardTokens[i];
if (
reward == address(0) || reward == data.token.expectedAddress
|| reward.code.length == 0
) {
revert InvalidLaunchConfiguration();
}
for (uint256 j; j < i; ++j) {
if (data.rewardTokens[j] == reward) revert InvalidLaunchConfiguration();
}
}
}
function _validateDeployment(Deployment calldata deployment) private view {
if (deployment.initCode.length == 0 || deployment.expectedAddress == address(0)) {
revert InvalidLaunchConfiguration();
}
address computed = Create2.computeAddress(
deployment.salt, keccak256(deployment.initCode), address(this)
);
if (computed != deployment.expectedAddress) {
revert InvalidDeployment(deployment.expectedAddress, computed);
}
if (computed.code.length != 0) revert InvalidLaunchConfiguration();
}
function _deploy(Deployment calldata deployment) private returns (address deployed) {
deployed = Create2.deploy(0, deployment.salt, deployment.initCode);
if (deployed != deployment.expectedAddress) {
revert InvalidDeployment(deployment.expectedAddress, deployed);
}
}
function _verifyComponents(LaunchData calldata data, LaunchResult memory result) private view {
MemeFolioToken token = MemeFolioToken(result.token);
MemeFolioTaxHook hook = MemeFolioTaxHook(payable(result.hook));
PermanentV4LiquidityLocker locker = PermanentV4LiquidityLocker(result.locker);
MemeFolioDistributor distributor = MemeFolioDistributor(payable(result.distributor));
if (
keccak256(bytes(token.name())) != keccak256(bytes(data.tokenName))
|| keccak256(bytes(token.symbol())) != keccak256(bytes(data.tokenSymbol))
|| token.initialSupply() != data.tokenSupply
|| token.totalSupply() != data.tokenSupply
|| token.balanceOf(address(this)) != data.tokenSupply
|| token.minHolderBalance() != data.minHolderBalance
|| address(hook.poolManager()) != address(poolManager)
|| hook.projectToken() != result.token || hook.buybackWallet() != data.executor
|| hook.treasuryWallet() != data.treasury
|| hook.poolTickSpacing() != data.tickSpacing
|| hook.initialSqrtPriceX96() != data.initialSqrtPriceX96
|| hook.launchAuthority() != address(this)
|| address(locker.positionManager()) != address(positionManager)
|| locker.launchAuthority() != address(this)
|| locker.projectToken() != result.token || locker.hook() != result.hook
|| locker.poolFee() != data.poolFee || locker.tickSpacing() != data.tickSpacing
|| locker.tickLower() != data.tickLower || locker.tickUpper() != data.tickUpper
|| address(distributor.sourceToken()) != result.token
|| distributor.treasury() != data.treasury
|| distributor.minHolderBalance() != data.minHolderBalance
|| distributor.maxDistributionBatchSize() != data.maxDistributionBatchSize
|| !distributor.hasRole(distributor.DEFAULT_ADMIN_ROLE(), data.admin)
|| !distributor.hasRole(distributor.OPERATOR_ROLE(), data.operator)
|| distributor.rewardTokenCount() != data.rewardTokens.length
) revert LaunchInvariantFailed();
for (uint256 i; i < data.rewardTokens.length; ++i) {
if (distributor.rewardTokenAt(i) != data.rewardTokens[i]) {
revert LaunchInvariantFailed();
}
}
for (uint256 i; i < data.excludedAccounts.length; ++i) {
if (!distributor.excludedFromRewards(data.excludedAccounts[i])) {
revert LaunchInvariantFailed();
}
}
}
function _poolKey(LaunchData calldata data, LaunchResult memory result)
private
pure
returns (PoolKey memory)
{
return PoolKey({
currency0: CurrencyLibrary.ADDRESS_ZERO,
currency1: Currency.wrap(result.token),
fee: data.poolFee,
tickSpacing: data.tickSpacing,
hooks: IHooks(result.hook)
});
}
function _mintPosition(
LaunchData calldata data,
LaunchResult memory result,
PoolKey memory key,
uint128 liquidity
) private returns (uint256 positionTokenId) {
IERC20(result.token).forceApprove(address(permit2), type(uint256).max);
permit2.approve(result.token, address(positionManager), type(uint160).max, type(uint48).max);
uint256 firstCandidateTokenId = positionManager.nextTokenId();
bytes memory actions = abi.encodePacked(
uint8(Actions.MINT_POSITION),
uint8(Actions.CLOSE_CURRENCY),
uint8(Actions.CLOSE_CURRENCY)
);
bytes[] memory params = new bytes[](3);
params[0] = abi.encode(
key,
data.tickLower,
data.tickUpper,
uint256(liquidity),
uint128(0),
uint128(data.tokenSupply),
result.locker,
bytes("")
);
params[1] = abi.encode(key.currency0);
params[2] = abi.encode(key.currency1);
positionManager.modifyLiquidities(abi.encode(actions, params), block.timestamp + 30 minutes);
positionTokenId = _findCanonicalLockerPosition(
firstCandidateTokenId,
positionManager.nextTokenId(),
result.locker,
key,
data.tickLower,
data.tickUpper
);
}
function _findCanonicalLockerPosition(
uint256 firstTokenId,
uint256 nextTokenId,
address locker,
PoolKey memory expectedKey,
int24 expectedTickLower,
int24 expectedTickUpper
) private view returns (uint256 bestTokenId) {
uint128 bestLiquidity = 0;
for (uint256 tokenId = firstTokenId; tokenId < nextTokenId; ++tokenId) {
if (IERC721(address(positionManager)).ownerOf(tokenId) != locker) continue;
(PoolKey memory key, PositionInfo info) =
positionManager.getPoolAndPositionInfo(tokenId);
if (
Currency.unwrap(key.currency0) != Currency.unwrap(expectedKey.currency0)
|| Currency.unwrap(key.currency1) != Currency.unwrap(expectedKey.currency1)
|| key.fee != expectedKey.fee || key.tickSpacing != expectedKey.tickSpacing
|| address(key.hooks) != address(expectedKey.hooks)
|| info.tickLower() != expectedTickLower
|| info.tickUpper() != expectedTickUpper
) continue;
uint128 positionLiquidity = positionManager.getPositionLiquidity(tokenId);
if (positionLiquidity > bestLiquidity) {
bestLiquidity = positionLiquidity;
bestTokenId = tokenId;
}
}
if (bestTokenId == 0 || bestLiquidity == 0) revert LaunchInvariantFailed();
}
function _verifyFinalState(LaunchData calldata data, LaunchResult memory result, IERC20 token)
private
view
{
PermanentV4LiquidityLocker locker = PermanentV4LiquidityLocker(result.locker);
(uint160 permitAmount,,) =
permit2.allowance(result.token, address(this), address(positionManager));
if (
token.balanceOf(address(this)) != 0 || address(this).balance != 0
|| token.allowance(address(this), address(permit2)) != 0 || permitAmount != 0
|| MemeFolioTaxHook(payable(result.hook)).launchAuthority() != address(0)
|| locker.launchAuthority() != address(0)
|| locker.positionTokenId() != result.positionTokenId
|| IERC721(address(positionManager)).ownerOf(result.positionTokenId)
!= result.locker || data.token.expectedAddress.code.length == 0
|| data.hook.expectedAddress.code.length == 0
|| data.locker.expectedAddress.code.length == 0
|| data.distributor.expectedAddress.code.length == 0
) revert LaunchInvariantFailed();
}
}
Chain explorer2971msChain node91ms