LaunchDeployer

0xa2445bd356556e8214f1cdbcebfd0c7f032a3449

Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
524 bytes
ABI entries
16
8 read · 0 write
License
none

Contract information

Address
0xa2445bd356556e8214f1cdbcebfd0c7f032a3449
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0x6bba72749b…93633d805e

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (8)

initTick()int24
mirror()address
pool()address
positionTokenId()uint256
renderer()address
router()address
token()address
tokenSalt()bytes32

Events (1)

LaunchCompleted

ABI

[
  {
    "inputs": [
      {
        "components": [
          {
            "internalType": "address",
            "name": "v3Factory",
            "type": "address"
          },
          {
            "internalType": "bytes32",
            "name": "v3FactoryCodehash",
            "type": "bytes32"
          },
          {
            "internalType": "address",
            "name": "npm",
            "type": "address"
          },
          {
            "internalType": "bytes32",
            "name": "npmCodehash",
            "type": "bytes32"
          },
          {
            "internalType": "address",
            "name": "weth",
            "type": "address"
          },
          {
            "internalType": "bytes32",
            "name": "wethCodehash",
            "type": "bytes32"
          },
          {
            "internalType": "address",
            "name": "universalRouter",
            "type": "address"
          },
          {
            "internalType": "bytes32",
            "name": "universalRouterCodehash",
            "type": "bytes32"
          }
        ],
        "internalType": "struct LaunchDependencies",
        "name": "dependencies",
        "type": "tuple"
      },
      {
        "components": [
          {
            "internalType": "address",
            "name": "owner",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "dev",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "guardian",
            "type": "address"
          }
        ],
        "internalType": "struct LaunchRoles",
        "name": "roles",
        "type": "tuple"
      },
      {
        "internalType": "contract KaleidoRendererV2",
        "name": "rendererV2",
        "type": "address"
      },
      {
        "internalType": "bytes32",
        "name": "rendererCodehash",
        "type": "bytes32"
      },
      {
        "internalType": "bytes32",
        "name": "launchSalt",
        "type": "bytes32"
      },
      {
        "internalType": "int24",
        "name": "initialTick",
        "type": "int24"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "dependency",
        "type": "address"
      },
      {
        "internalType": "bytes32",
        "name": "expected",
        "type": "bytes32"
      },
      {
        "internalType": "bytes32",
        "name": "actual",
        "type": "bytes32"
      }
    ],
    "name": "DependencyCodehashMismatch",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "dependency",
        "type": "address"
      }
    ],
    "name": "DependencyHasNoCode",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidInitTick",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "LauncherCannotRemainOwner",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAddress",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroTokenSalt",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "mirror",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "router",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "renderer",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "pool",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "positionTokenId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "int24",
        "name": "initTick",
        "type": "int24"
      }
    ],
    "name": "LaunchCompleted",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "initTick",
    "outputs": [
      {
        "internalType": "int24",
        "name": "",
        "type": "int24"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "mirror",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pool",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "positionTokenId",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renderer",
    "outputs": [
      {
        "internalType": "contract KaleidoRendererV2",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "router",
    "outputs": [
      {
        "internalType": "contract ClaimRouter",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "token",
    "outputs": [
      {
        "internalType": "contract KaleidoToken",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "tokenSalt",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  }
]

Source code

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.26;

import { Script, console2 } from "forge-std/Script.sol";
import { TickMath } from "@uniswap/v4-core/src/libraries/TickMath.sol";

import { KaleidoToken } from "../src/KaleidoToken.sol";
import { ClaimRouter } from "../src/ClaimRouter.sol";
import { KaleidoRendererV2 } from "../src/KaleidoRendererV2.sol";

struct LaunchDependencies {
    address v3Factory;
    bytes32 v3FactoryCodehash;
    address npm;
    bytes32 npmCodehash;
    address weth;
    bytes32 wethCodehash;
    address universalRouter;
    bytes32 universalRouterCodehash;
}

struct LaunchRoles {
    address owner;
    address dev;
    address guardian;
}

interface ILaunchV3Factory {
    function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address);
}

interface ILaunchV3Pool {
    function slot0() external view returns (uint160 sqrtPriceX96, int24 tick, uint16, uint16, uint16, uint8, bool);
}

interface ILaunchNpm {
    function ownerOf(uint256 tokenId) external view returns (address);
}

/// @notice One-transaction production assembly for KALEIDO. The constructor creates the core, seeds the v3 reserve,
///         deploys the exact ClaimRouter implementation, wires it one-shot, installs the pre-verified Prisme V2
///         renderer, and transfers the core's only remaining ownership to the final owner.
/// @dev The renderer is deliberately deployed and verified BEFORE this transaction. Embedding its 8.9 kB creation
///      code here would push the launcher's EIP-3860 initcode too close to the 49,152-byte ceiling; renderer setup is
///      cosmetic and does not create the H-01 pool-init window. Every money-bearing/core step is atomic.
///
///      IMPORTANT: the token uses CREATE2 with an operator-supplied high-entropy salt. Keep that salt secret until
///      direct sequencer submission; revealing it makes the child address predictable again. Atomic assembly then
///      removes the post-deployment window. The seed checks remain the final on-chain fail-closed defense.
contract LaunchDeployer {
    error ZeroAddress();
    error DependencyHasNoCode(address dependency);
    error DependencyCodehashMismatch(address dependency, bytes32 expected, bytes32 actual);
    error InvalidInitTick();
    error ZeroTokenSalt();
    error LauncherCannotRemainOwner();

    event LaunchCompleted(
        address indexed token,
        address indexed mirror,
        address indexed router,
        address renderer,
        address pool,
        uint256 positionTokenId,
        int24 initTick
    );

    int24 internal constant TICK_SPACING = 200;

    KaleidoToken public immutable token;
    address public immutable mirror;
    ClaimRouter public immutable router;
    KaleidoRendererV2 public immutable renderer;
    address public immutable pool;
    uint256 public immutable positionTokenId;
    int24 public immutable initTick;
    bytes32 public immutable tokenSalt;

    constructor(
        LaunchDependencies memory dependencies,
        LaunchRoles memory roles,
        KaleidoRendererV2 rendererV2,
        bytes32 rendererCodehash,
        bytes32 launchSalt,
        int24 initialTick
    ) {
        if (
            roles.owner == address(0) || roles.dev == address(0) || roles.guardian == address(0)
                || address(rendererV2) == address(0)
        ) {
            revert ZeroAddress();
        }
        if (launchSalt == bytes32(0)) revert ZeroTokenSalt();
        if (roles.owner == address(this)) revert LauncherCannotRemainOwner();

        _checkDependency(dependencies.v3Factory, dependencies.v3FactoryCodehash);
        _checkDependency(dependencies.npm, dependencies.npmCodehash);
        _checkDependency(dependencies.weth, dependencies.wethCodehash);
        _checkDependency(dependencies.universalRouter, dependencies.universalRouterCodehash);
        _checkDependency(address(rendererV2), rendererCodehash);

        int24 minTick = TickMath.minUsableTick(TICK_SPACING);
        int24 maxTick = TickMath.maxUsableTick(TICK_SPACING);
        if (initialTick % TICK_SPACING != 0 || initialTick <= minTick || initialTick >= maxTick) {
            revert InvalidInitTick();
        }

        // The launcher is the temporary owner because only it can execute all setup calls inside this transaction.
        KaleidoToken t = new KaleidoToken{ salt: launchSalt }(
            dependencies.v3Factory, dependencies.npm, dependencies.weth, address(this), roles.dev
        );
        (int24 tickLower, int24 tickUpper) = _singleSidedTicks(t.kaleidoIsToken0(), initialTick);
        uint256 posId = t.seed(TickMath.getSqrtPriceAtTick(initialTick), tickLower, tickUpper);

        // Constructing the exact implementation here closes M-03R for the launch path: no operator-supplied router
        // can self-attest with a fake core() getter. ClaimRouter also binds WETH back to the core in its constructor.
        ClaimRouter cr = new ClaimRouter(address(t), dependencies.universalRouter, roles.guardian, dependencies.weth);
        t.setClaimRouter(address(cr));
        t.setRenderer(address(rendererV2));
        t.transferOwnership(roles.owner);

        token = t;
        mirror = address(t.mirror());
        router = cr;
        renderer = rendererV2;
        pool = t.pool();
        positionTokenId = posId;
        initTick = initialTick;
        tokenSalt = launchSalt;

        _emitLaunchCompleted();
    }

    function _emitLaunchCompleted() private {
        emit LaunchCompleted(
            address(token), mirror, address(router), address(renderer), pool, positionTokenId, initTick
        );
    }

    function _checkDependency(address dependency, bytes32 expectedCodehash) private view {
        if (dependency == address(0)) revert ZeroAddress();
        if (dependency.code.length == 0) revert DependencyHasNoCode(dependency);
        bytes32 actual = dependency.codehash;
        if (expectedCodehash == bytes32(0) || actual != expectedCodehash) {
            revert DependencyCodehashMismatch(dependency, expectedCodehash, actual);
        }
    }

    function _singleSidedTicks(bool kaleidoIsToken0, int24 initialTick)
        private
        pure
        returns (int24 tickLower, int24 tickUpper)
    {
        if (kaleidoIsToken0) {
            tickLower = initialTick;
            tickUpper = TickMath.maxUsableTick(TICK_SPACING);
        } else {
            tickLower = TickMath.minUsableTick(TICK_SPACING);
            tickUpper = initialTick;
        }
    }
}

/// @dev Shared, pinned Robinhood Mainnet 4663 configuration and postflight assertions for the production scripts.
abstract contract RobinhoodLaunchBase is Script {
    uint256 internal constant ROBINHOOD_MAINNET_CHAIN_ID = 4663;
    uint256 internal constant SEED_MAX_DUST = 1e9;

    address internal constant V3_FACTORY = 0x1f7d7550B1b028f7571E69A784071F0205FD2EfA;
    address internal constant V3_NPM = 0x73991a25C818Bf1f1128dEAaB1492D45638DE0D3;
    address internal constant WETH = 0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73;
    address internal constant UNIVERSAL_ROUTER = 0x8876789976dEcBfCbBbe364623C63652db8C0904;

    // Live runtime hashes measured on Robinhood Mainnet at P0 implementation time. Any drift aborts the launch and
    // requires a dependency re-verification; silently accepting changed infrastructure is not launch-safe.
    bytes32 internal constant V3_FACTORY_CODEHASH = 0xec72b1abd1f2faee020cfea9c646bd8994f9fb389054f6e574f103a895091739;
    bytes32 internal constant V3_NPM_CODEHASH = 0x0a493d1af3d0f25fed8efa205244ebee14114267a08647fc38c515c7cd6ead4f;
    bytes32 internal constant WETH_CODEHASH = 0x5706be52f64875fee65a2cec0d80e47a23d8793cbe85d214b48445e2d05f5353;
    bytes32 internal constant UNIVERSAL_ROUTER_CODEHASH =
        0x2ce6aaaf9f4151f5e1cbf774668772f17f532ae11b15e9284fd0a072a8b0fbde;
    bytes32 internal constant RENDERER_V2_CODEHASH = 0xf755d81a77116eb5bf58038534eb8ba82e79a32487d0b64c25c3023724245dbe;

    function _dependencies() internal pure returns (LaunchDependencies memory) {
        return LaunchDependencies({
            v3Factory: V3_FACTORY,
            v3FactoryCodehash: V3_FACTORY_CODEHASH,
            npm: V3_NPM,
            npmCodehash: V3_NPM_CODEHASH,
            weth: WETH,
            wethCodehash: WETH_CODEHASH,
            universalRouter: UNIVERSAL_ROUTER,
            universalRouterCodehash: UNIVERSAL_ROUTER_CODEHASH
        });
    }

    function _requireMainnetConfirmation() internal view {
        require(block.chainid == ROBINHOOD_MAINNET_CHAIN_ID, "Robinhood Mainnet/fork only");
        require(
            keccak256(bytes(vm.envString("MAINNET_LAUNCH_CONFIRM"))) == keccak256("KALEIDO_MAINNET_4663"),
            "set MAINNET_LAUNCH_CONFIRM=KALEIDO_MAINNET_4663"
        );
    }

    function _readInitTick() internal view returns (int24 initialTick) {
        int256 raw = vm.envInt("INIT_TICK");
        require(raw >= type(int24).min && raw <= type(int24).max, "INIT_TICK outside int24");
        // forge-lint: disable-next-line(unsafe-typecast) -- range checked immediately above.
        initialTick = int24(raw);
    }

    function _verify(
        LaunchDeployer launcher,
        LaunchRoles memory roles,
        KaleidoRendererV2 expectedRenderer,
        bytes32 expectedTokenSalt,
        int24 expectedInitTick
    ) internal view {
        KaleidoToken t = launcher.token();
        ClaimRouter cr = launcher.router();
        uint256 posId = launcher.positionTokenId();

        require(address(t) != address(0) && address(cr) != address(0), "zero launch component");
        require(t.seeded(), "core not seeded");
        require(t.owner() == roles.owner, "wrong final owner");
        require(t.dev() == roles.dev, "wrong dev");
        require(t.claimRouter() == address(cr), "router not wired");
        require(t.renderer() == address(expectedRenderer), "renderer not wired");
        require(address(expectedRenderer).codehash == RENDERER_V2_CODEHASH, "wrong renderer bytecode");
        require(address(t.mirror()) == launcher.mirror(), "mirror mismatch");
        require(t.pool() == launcher.pool() && t.pool().code.length > 0, "pool mismatch");
        require(t.positionTokenId() == posId && ILaunchNpm(V3_NPM).ownerOf(posId) == address(t), "position unlocked");
        require(t.balanceOf(address(t)) <= SEED_MAX_DUST, "reserve dust too high");
        require(t.allowance(address(t), V3_NPM) == 0, "NPM allowance not revoked");
        require(address(cr.core()) == address(t), "router core mismatch");
        require(cr.weth() == WETH, "router WETH mismatch");
        require(cr.universalRouter() == UNIVERSAL_ROUTER, "wrong UniversalRouter");
        require(cr.guardian() == roles.guardian, "wrong guardian");
        require(launcher.initTick() == expectedInitTick, "launcher tick mismatch");
        require(launcher.tokenSalt() == expectedTokenSalt && expectedTokenSalt != bytes32(0), "token salt mismatch");

        (address token0, address token1) = t.kaleidoIsToken0() ? (address(t), WETH) : (WETH, address(t));
        require(ILaunchV3Factory(V3_FACTORY).getPool(token0, token1, t.POOL_FEE()) == t.pool(), "non-canonical pool");
        (uint160 poolPrice,,,,,,) = ILaunchV3Pool(t.pool()).slot0();
        require(poolPrice == TickMath.getSqrtPriceAtTick(expectedInitTick), "wrong pool price");
    }

    function _log(LaunchDeployer launcher) internal view {
        console2.log("LaunchDeployer", address(launcher));
        console2.log("KaleidoToken ", address(launcher.token()));
        console2.log("KaleidoMirror", launcher.mirror());
        console2.log("ClaimRouter  ", address(launcher.router()));
        console2.log("Renderer V2 ", address(launcher.renderer()));
        console2.log("v3 pool      ", launcher.pool());
        console2.log("position id  ", launcher.positionTokenId());
    }
}

/// @notice Deploy the renderer ahead of the core launch. This transaction carries no protocol funds and creates no
///         predictable KALEIDO address. Verify it on Blockscout and pass it as RENDERER_ADDRESS to `Launch`.
contract DeployRenderer is RobinhoodLaunchBase {
    function run() external returns (KaleidoRendererV2 rendererV2) {
        _requireMainnetConfirmation();
        uint256 privateKey = vm.envUint("PRIVATE_KEY");
        vm.startBroadcast(privateKey);
        rendererV2 = new KaleidoRendererV2();
        vm.stopBroadcast();
        require(address(rendererV2).codehash == RENDERER_V2_CODEHASH, "renderer bytecode mismatch");
        console2.log("KaleidoRendererV2", address(rendererV2));
        console2.logBytes32(address(rendererV2).codehash);
    }
}

/// @notice Production launch script. Simulate it against a fresh mainnet fork first; broadcast the exact transaction
///         directly to Robinhood's sequencer with `--skip-simulation` only after the runbook gates are signed off.
///         A remote pre-broadcast simulation would expose TOKEN_SALT before transaction ordering is secured.
contract Launch is RobinhoodLaunchBase {
    function run() external returns (LaunchDeployer launcher) {
        _requireMainnetConfirmation();

        uint256 privateKey = vm.envUint("PRIVATE_KEY");
        LaunchRoles memory roles = LaunchRoles({
            owner: vm.envAddress("OWNER_ADDRESS"),
            dev: vm.envAddress("DEV_ADDRESS"),
            guardian: vm.envAddress("GUARDIAN_ADDRESS")
        });
        KaleidoRendererV2 rendererV2 = KaleidoRendererV2(vm.envAddress("RENDERER_ADDRESS"));
        bytes32 launchSalt = vm.envBytes32("TOKEN_SALT");
        int24 initialTick = _readInitTick();

        vm.startBroadcast(privateKey);
        launcher = new LaunchDeployer(_dependencies(), roles, rendererV2, RENDERER_V2_CODEHASH, launchSalt, initialTick);
        vm.stopBroadcast();

        _verify(launcher, roles, rendererV2, launchSalt, initialTick);
        _log(launcher);
    }
}

/// @notice Independent, read-only postflight. Run from a separate machine/RPC after broadcast and before announcing
///         or funding any ancillary service.
contract VerifyLaunch is RobinhoodLaunchBase {
    function run() external view {
        _requireMainnetConfirmation();
        LaunchDeployer launcher = LaunchDeployer(vm.envAddress("LAUNCHER_ADDRESS"));
        LaunchRoles memory roles = LaunchRoles({
            owner: vm.envAddress("OWNER_ADDRESS"),
            dev: vm.envAddress("DEV_ADDRESS"),
            guardian: vm.envAddress("GUARDIAN_ADDRESS")
        });
        KaleidoRendererV2 rendererV2 = KaleidoRendererV2(vm.envAddress("RENDERER_ADDRESS"));
        _verify(launcher, roles, rendererV2, vm.envBytes32("TOKEN_SALT"), _readInitTick());
        _log(launcher);
        console2.log("POSTFLIGHT OK");
    }
}
Chain explorer5678msChain node79ms