BasketShareDeployer

0xc52c2e1069a8be70a9603cd676a7b246a4fe4d84

Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
15,490 bytes
ABI entries
1
0 read · 1 write
License
none

Contract information

Address
0xc52c2e1069a8be70a9603cd676a7b246a4fe4d84
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0x2440b6a015…48d5625061

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (0)

No read functions

The ABI is unavailable or exposes no view functions.

ABI

[
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "salt",
        "type": "bytes32"
      },
      {
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "symbol",
        "type": "string"
      },
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      },
      {
        "internalType": "contract AssetRegistry",
        "name": "registry",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "minimumGenesisDeposit",
        "type": "uint256"
      }
    ],
    "name": "deploy",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "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 { AssetRegistry } from "./AssetRegistry.sol";
import { BasketAcquisitionVault } from "./BasketAcquisitionVault.sol";
import { BasketShare } from "./BasketShare.sol";
import { DividendDistributor } from "./DividendDistributor.sol";
import { RevenueToken } from "./RevenueToken.sol";
import { ShareCoordinator } from "./ShareCoordinator.sol";
import { TaxProcessorV2 } from "./TaxProcessorV2.sol";
import { V2TwapGuard } from "./V2TwapGuard.sol";
import { IAggregatorV3 } from "./interfaces/IAggregatorV3.sol";
import { IRobinhoodStockToken } from "./interfaces/IRobinhoodStockToken.sol";
import { IUniswapV2Factory } from "./interfaces/IUniswapV2Factory.sol";
import { IUniswapV2Pair } from "./interfaces/IUniswapV2Pair.sol";
import { IUniswapV2Router02 } from "./interfaces/IUniswapV2Router02.sol";
import { IWETH } from "./interfaces/IWETH.sol";
import { ProtocolConfig } from "./types/ProtocolConfig.sol";

abstract contract ComponentDeployer {
    function _callerSalt(bytes32 salt) internal view returns (bytes32) {
        return _saltFor(msg.sender, salt);
    }

    function _saltFor(address caller, bytes32 salt) internal pure returns (bytes32) {
        return keccak256(abi.encode(caller, salt));
    }

    function _create2Address(address deployer, bytes32 salt, bytes32 initCodeHash)
        internal
        pure
        returns (address)
    {
        return address(
            uint160(
                uint256(keccak256(abi.encodePacked(bytes1(0xff), deployer, salt, initCodeHash)))
            )
        );
    }

    function _deploy(bytes memory initCode, bytes32 salt) internal returns (address deployed) {
        assembly ("memory-safe") {
            deployed := create2(0, add(initCode, 0x20), mload(initCode), salt)
        }
        require(deployed != address(0), "CREATE2_FAILED");
    }
}

contract AssetRegistryDeployer is ComponentDeployer {
    struct Arguments {
        address owner;
        AssetRegistry.OracleConfig oracleConfig;
        ProtocolConfig.AssetConfig[] assets;
    }

    function deploy(bytes32 salt, Arguments calldata args) external returns (address) {
        return address(
            new AssetRegistry{ salt: _callerSalt(salt) }(args.owner, args.oracleConfig, args.assets)
        );
    }
}

contract BasketShareDeployer is ComponentDeployer {
    function deploy(
        bytes32 salt,
        string calldata name,
        string calldata symbol,
        address owner,
        AssetRegistry registry,
        uint256 minimumGenesisDeposit
    ) external returns (address) {
        return address(
            new BasketShare{ salt: _callerSalt(salt) }(
                name, symbol, owner, registry, minimumGenesisDeposit
            )
        );
    }
}

contract DividendDistributorDeployer is ComponentDeployer {
    function deploy(bytes32 salt, address owner, uint256 minimumEligibleBalance)
        external
        returns (address)
    {
        return address(
            new DividendDistributor{ salt: _callerSalt(salt) }(owner, minimumEligibleBalance)
        );
    }
}

contract ShareCoordinatorDeployer is ComponentDeployer {
    function deploy(
        bytes32 salt,
        address owner,
        address revenueToken,
        address distributor,
        uint256 minimumEligibleBalance
    ) external returns (address) {
        return address(
            new ShareCoordinator{ salt: _callerSalt(salt) }(
                owner, revenueToken, distributor, minimumEligibleBalance
            )
        );
    }
}

contract BasketAcquisitionVaultDeployer is ComponentDeployer {
    struct Arguments {
        address owner;
        BasketShare basket;
        DividendDistributor distributor;
        AssetRegistry registry;
        uint16 fillerPayoutBps;
        uint256 maxNativePerFill;
        uint256 dailyNativePerAsset;
    }

    function deploy(bytes32 salt, Arguments calldata args) external returns (address) {
        return address(
            new BasketAcquisitionVault{ salt: _callerSalt(salt) }(
                args.owner,
                args.basket,
                args.distributor,
                args.registry,
                args.fillerPayoutBps,
                args.maxNativePerFill,
                args.dailyNativePerAsset
            )
        );
    }
}

contract RevenueTokenDeployer is ComponentDeployer {
    struct Arguments {
        string name;
        string symbol;
        uint256 initialSupply;
        address initialHolder;
        address owner;
        uint16 buyTaxBps;
        uint16 sellTaxBps;
        uint256 processingThreshold;
        uint256 processingThresholdFloor;
        uint32 shareSyncGasLimit;
    }

    function deploy(bytes32 salt, Arguments calldata args) external returns (address) {
        return _deploy(_initCode(args), _callerSalt(salt));
    }

    function _initCode(Arguments calldata args) private pure returns (bytes memory) {
        bytes memory nameTail = _stringTail(args.name);
        bytes memory symbolTail = _stringTail(args.symbol);
        bytes memory staticHead = abi.encode(
            args.initialSupply,
            args.initialHolder,
            args.owner,
            args.buyTaxBps,
            args.sellTaxBps,
            args.processingThreshold,
            args.processingThresholdFloor,
            args.shareSyncGasLimit
        );
        bytes memory constructorArgs = bytes.concat(
            abi.encode(bytes32(uint256(320)), bytes32(uint256(320 + nameTail.length))),
            staticHead,
            nameTail,
            symbolTail
        );
        return bytes.concat(type(RevenueToken).creationCode, constructorArgs);
    }

    function _stringTail(string calldata value) private pure returns (bytes memory tail) {
        bytes calldata raw = bytes(value);
        uint256 paddedLength = (raw.length + 31) & ~uint256(31);
        tail = new bytes(32 + paddedLength);
        assembly ("memory-safe") {
            mstore(add(tail, 0x20), raw.length)
            calldatacopy(add(tail, 0x40), raw.offset, raw.length)
        }
    }

    function predict(address caller, bytes32 salt, Arguments calldata args)
        external
        view
        returns (address)
    {
        bytes32 initCodeHash = keccak256(_initCode(args));
        return _create2Address(address(this), _saltFor(caller, salt), initCodeHash);
    }
}

contract V2TwapGuardDeployer is ComponentDeployer {
    struct Arguments {
        address pair;
        address tokenIn;
        address tokenOut;
        uint32 minimumAge;
        uint32 maximumAge;
        uint16 maximumDeviationBps;
        uint16 maximumInputReserveBps;
    }

    function deploy(bytes32 salt, Arguments calldata args) external returns (address) {
        return address(
            new V2TwapGuard{ salt: _callerSalt(salt) }(
                args.pair,
                args.tokenIn,
                args.tokenOut,
                args.minimumAge,
                args.maximumAge,
                args.maximumDeviationBps,
                args.maximumInputReserveBps
            )
        );
    }
}

contract TaxProcessorV2Deployer is ComponentDeployer {
    struct Arguments {
        address owner;
        address weth;
        address router;
        address guard;
        address feeReceiver;
        address payable vault;
        uint256 protocolRevenueBps;
        uint256 cooldown;
    }

    function deploy(bytes32 salt, Arguments calldata args) external returns (address) {
        bytes memory initCode = abi.encodePacked(
            type(TaxProcessorV2).creationCode,
            abi.encode(
                args.owner,
                args.weth,
                args.router,
                args.guard,
                args.feeReceiver,
                args.vault,
                args.protocolRevenueBps,
                args.cooldown
            )
        );
        return _deploy(initCode, _callerSalt(salt));
    }
}

/// @notice Immutable phase-two implementation executed in the factory's storage context.
/// @dev The factory creates this implementation in its constructor and exposes no update path.
contract ProtocolFinalizer {
    error IncorrectNativeValue(uint256 expected, uint256 actual);
    error PairAlreadySeeded();
    error LiquidityMismatch(uint256 tokenUsed, uint256 nativeUsed);
    error DeploymentNotPending(bytes32 instanceId, DeploymentState state);
    error UnauthorizedFinalizer(address expected, address actual);
    error FactoryTokenBalanceMismatch(uint256 expected, uint256 actual);
    error ZeroLiquidityIssued();
    error PeerBindingInvalid();
    error FactoryRetainedTokens(uint256 balance);
    error InitialShareSyncDebt(uint256 observedDebtCount);
    error InitialGovernanceShareMismatch(uint256 observedShares, uint256 expectedWeight);
    error TokenTransferFailed(address token, address recipient, uint256 amount);

    enum DeploymentState {
        None,
        Pending,
        Finalized
    }

    struct Instance {
        address registry;
        address basketShare;
        address distributor;
        address vault;
        address revenueToken;
        address guard;
        address processor;
        address pair;
        address shareCoordinator;
    }

    struct PendingDeployment {
        DeploymentState state;
        address creator;
        bytes32 configHash;
        address owner;
        address router;
        uint256 tokenLiquidity;
        uint256 nativeLiquidity;
        address lpRecipient;
        Instance instance;
    }

    event ProtocolDeployed(
        bytes32 indexed instanceId,
        bytes32 indexed configHash,
        address indexed creator,
        address revenueToken,
        address basketShare,
        address pair,
        address lpRecipient
    );
    event InitialLiquidity(
        address indexed pair, uint256 tokenReserve, uint256 nativeReserve, address lpRecipient
    );

    // Must remain storage-layout identical to ProtocolFactory's first two state variables.
    mapping(bytes32 instanceId => bool used) private _deploymentUsed;
    mapping(bytes32 instanceId => PendingDeployment deployment) private _deployments;

    function finalizeAndSeed(bytes32 instanceId) external payable {
        PendingDeployment storage stored = _deployments[instanceId];
        DeploymentState state = stored.state;
        if (state != DeploymentState.Pending) revert DeploymentNotPending(instanceId, state);
        if (msg.sender != stored.creator) {
            revert UnauthorizedFinalizer(stored.creator, msg.sender);
        }
        if (msg.value != stored.nativeLiquidity) {
            revert IncorrectNativeValue(stored.nativeLiquidity, msg.value);
        }

        PendingDeployment memory deployment = stored;
        RevenueToken revenue = RevenueToken(deployment.instance.revenueToken);
        uint256 factoryBalance = revenue.balanceOf(address(this));
        uint256 totalSupply = revenue.totalSupply();
        if (factoryBalance != totalSupply) {
            revert FactoryTokenBalanceMismatch(totalSupply, factoryBalance);
        }
        if (IERC20(deployment.instance.pair).totalSupply() != 0) revert PairAlreadySeeded();
        _validatePendingPeerGraph(deployment.instance);

        stored.state = DeploymentState.Finalized;
        (uint256 tokenReserve, uint256 nativeReserve) = _seedLiquidity(
            deployment.router,
            deployment.tokenLiquidity,
            deployment.nativeLiquidity,
            deployment.lpRecipient,
            deployment.instance
        );
        _activate(deployment.instance);
        revenue.activate();

        uint256 remainder = revenue.balanceOf(address(this));
        if (remainder != 0 && !revenue.transfer(deployment.owner, remainder)) {
            revert TokenTransferFailed(address(revenue), deployment.owner, remainder);
        }
        uint256 retained = revenue.balanceOf(address(this));
        if (retained != 0) revert FactoryRetainedTokens(retained);
        uint256 debtCount = revenue.syncDebtCount();
        if (debtCount != 0) revert InitialShareSyncDebt(debtCount);
        uint256 observedShares =
            DividendDistributor(deployment.instance.distributor).sharesOf(deployment.owner);
        if (observedShares != 0) {
            revert InitialGovernanceShareMismatch(observedShares, 0);
        }
        _validateActivePeerGraph(deployment.instance);
        _transferOwnership(deployment.instance, deployment.owner);
        _emitDeployment(
            instanceId,
            deployment.configHash,
            deployment.creator,
            deployment.instance,
            deployment.lpRecipient,
            tokenReserve,
            nativeReserve
        );
    }

    function _activate(Instance memory instance) private {
        TaxProcessorV2(payable(instance.processor)).activate();
        BasketShare(instance.basketShare).activate();
        DividendDistributor(instance.distributor).activate();
        ShareCoordinator(instance.shareCoordinator).activate();
        BasketAcquisitionVault(payable(instance.vault)).activate();
    }

    function _seedLiquidity(
        address router,
        uint256 tokenLiquidity,
        uint256 nativeLiquidity,
        address lpRecipient,
        Instance memory instance
    ) private returns (uint256 tokenReserve, uint256 nativeReserve) {
        RevenueToken revenue = RevenueToken(instance.revenueToken);
        IWETH weth = IWETH(IUniswapV2Router02(router).WETH());
        uint256 tokenBalanceBefore = revenue.balanceOf(instance.pair);
        uint256 nativeBalanceBefore = weth.balanceOf(instance.pair);
        uint256 lpBefore = IERC20(instance.pair).balanceOf(lpRecipient);
        if (!revenue.transfer(instance.pair, tokenLiquidity)) {
            revert TokenTransferFailed(address(revenue), instance.pair, tokenLiquidity);
        }
        weth.deposit{ value: nativeLiquidity }();
        if (!weth.transfer(instance.pair, nativeLiquidity)) {
            revert TokenTransferFailed(address(weth), instance.pair, nativeLiquidity);
        }
        uint256 liquidity = IUniswapV2Pair(instance.pair).mint(lpRecipient);
        if (liquidity == 0 || IERC20(instance.pair).balanceOf(lpRecipient) - lpBefore != liquidity)
        {
            revert ZeroLiquidityIssued();
        }

        (tokenReserve, nativeReserve) = _revenueNativeReserves(instance);
        if (
            tokenReserve != tokenBalanceBefore + tokenLiquidity
                || nativeReserve != nativeBalanceBefore + nativeLiquidity
        ) {
            revert LiquidityMismatch(tokenReserve, nativeReserve);
        }
    }

    function _revenueNativeReserves(Instance memory instance)
        private
        view
        returns (uint256 tokenReserve, uint256 nativeReserve)
    {
        IUniswapV2Pair pair = IUniswapV2Pair(instance.pair);
        (uint112 reserve0, uint112 reserve1,) = pair.getReserves();
        return pair.token0() == instance.revenueToken
            ? (uint256(reserve0), uint256(reserve1))
            : (uint256(reserve1), uint256(reserve0));
    }

    function _validatePendingPeerGraph(Instance memory instance) private view {
        BasketShare basket = BasketShare(instance.basketShare);
        DividendDistributor distributor = DividendDistributor(instance.distributor);
        BasketAcquisitionVault vault = BasketAcquisitionVault(payable(instance.vault));
        TaxProcessorV2 processor = TaxProcessorV2(payable(instance.processor));
        RevenueToken revenue = RevenueToken(instance.revenueToken);
        ShareCoordinator coordinator = ShareCoordinator(instance.shareCoordinator);
        V2TwapGuard guard = V2TwapGuard(instance.guard);
        if (
            !_peerBindingsValid(
                instance, basket, distributor, vault, processor, revenue, coordinator, guard
            ) || basket.active() || distributor.active() || vault.active() || processor.active()
                || revenue.active() || coordinator.active()
                || Ownable(instance.registry).owner() != address(this)
                || Ownable(instance.basketShare).owner() != address(this)
                || Ownable(instance.distributor).owner() != address(this)
                || Ownable(instance.vault).owner() != address(this)
                || Ownable(instance.revenueToken).owner() != address(this)
                || Ownable(instance.processor).owner() != address(this)
                || Ownable(instance.shareCoordinator).owner() != address(this)
        ) revert PeerBindingInvalid();
    }

    function _validateActivePeerGraph(Instance memory instance) private view {
        BasketShare basket = BasketShare(instance.basketShare);
        DividendDistributor distributor = DividendDistributor(instance.distributor);
        BasketAcquisitionVault vault = BasketAcquisitionVault(payable(instance.vault));
        TaxProcessorV2 processor = TaxProcessorV2(payable(instance.processor));
        RevenueToken revenue = RevenueToken(instance.revenueToken);
        ShareCoordinator coordinator = ShareCoordinator(instance.shareCoordinator);
        if (
            !basket.active() || !distributor.active() || !vault.active() || !processor.active()
                || !revenue.active() || !coordinator.active()
        ) revert PeerBindingInvalid();
    }

    function _peerBindingsValid(
        Instance memory instance,
        BasketShare basket,
        DividendDistributor distributor,
        BasketAcquisitionVault vault,
        TaxProcessorV2 processor,
        RevenueToken revenue,
        ShareCoordinator coordinator,
        V2TwapGuard guard
    ) private view returns (bool) {
        return address(basket.registry()) == instance.registry && basket.vault() == instance.vault
            && basket.distributor() == instance.distributor
            && distributor.revenueToken() == instance.revenueToken
            && distributor.shareCoordinator() == instance.shareCoordinator
            && distributor.vault() == instance.vault
            && address(distributor.basketShare()) == instance.basketShare
            && address(vault.basketShare()) == instance.basketShare
            && address(vault.distributor()) == instance.distributor
            && address(vault.registry()) == instance.registry
            && processor.revenueToken() == instance.revenueToken
            && address(processor.guard()) == instance.guard
            && processor.acquisitionVault() == instance.vault && address(guard.pair()) == instance.pair
            && guard.tokenIn() == instance.revenueToken && guard.tokenOut() == address(processor.WETH())
            && revenue.processor() == instance.processor
            && revenue.shareCoordinator() == instance.shareCoordinator
            && coordinator.revenueToken() == instance.revenueToken
            && coordinator.stakingProvider() == address(0)
            && coordinator.distributor() == instance.distributor
            && coordinator.minimumEligibleBalance() == distributor.minimumEligibleBalance();
    }

    function _transferOwnership(Instance memory instance, address governance) private {
        Ownable(instance.registry).transferOwnership(governance);
        Ownable(instance.basketShare).transferOwnership(governance);
        Ownable(instance.distributor).transferOwnership(governance);
        Ownable(instance.vault).transferOwnership(governance);
        Ownable(instance.revenueToken).transferOwnership(governance);
        Ownable(instance.processor).transferOwnership(governance);
        Ownable(instance.shareCoordinator).transferOwnership(governance);
    }

    function _emitDeployment(
        bytes32 instanceId,
        bytes32 hash,
        address creator,
        Instance memory instance,
        address lpRecipient,
        uint256 tokenReserve,
        uint256 nativeReserve
    ) private {
        emit ProtocolDeployed(
            instanceId,
            hash,
            creator,
            instance.revenueToken,
            instance.basketShare,
            instance.pair,
            lpRecipient
        );
        emit InitialLiquidity(instance.pair, tokenReserve, nativeReserve, lpRecipient);
    }
}

/// @notice Stateless-after-launch CREATE2 factory for complete protocol instances.
contract ProtocolFactory {
    uint256 private constant BPS = 10_000;

    bytes32 private constant REGISTRY_ID = keccak256("ASSET_REGISTRY");
    bytes32 private constant BASKET_ID = keccak256("BASKET_SHARE");
    bytes32 private constant DISTRIBUTOR_ID = keccak256("DIVIDEND_DISTRIBUTOR");
    bytes32 private constant VAULT_ID = keccak256("ACQUISITION_VAULT");
    bytes32 private constant REVENUE_ID = keccak256("REVENUE_TOKEN");
    bytes32 private constant GUARD_ID = keccak256("V2_TWAP_GUARD");
    bytes32 private constant PROCESSOR_ID = keccak256("TAX_PROCESSOR_V2");
    bytes32 private constant COORDINATOR_ID = keccak256("SHARE_COORDINATOR");
    uint32 private constant MIN_SHARE_SYNC_GAS_LIMIT = 200_000;

    error DeploymentAlreadyUsed();
    error WrongChain(uint256 expected, uint256 actual);
    error ZeroAddress();
    error NoCode(address target);
    error RouterFactoryMismatch();
    error RouterWrappedNativeMismatch();
    error InvalidAssetCount(uint256 count);
    error InvalidAsset(uint256 index);
    error DuplicateAsset(address token);
    error InvalidWeightSum(uint256 weightSum);
    error InvalidLaunchConfig();
    error InconsistentLaunchConfig();
    error InvalidEconomics();
    error IncorrectNativeValue(uint256 expected, uint256 actual);
    error PairCreationFailed();
    error PairOrderingInvalid(address token0, address token1);
    error PairAlreadySeeded();
    error LiquidityMismatch(uint256 tokenUsed, uint256 nativeUsed);
    error DeploymentNotPending(bytes32 instanceId, DeploymentState state);
    error UnauthorizedFinalizer(address expected, address actual);
    error FactoryTokenBalanceMismatch(uint256 expected, uint256 actual);
    error ZeroLiquidityIssued();
    error PeerBindingInvalid();
    error FactoryRetainedTokens(uint256 balance);
    error InitialShareSyncDebt(uint256 observedDebtCount);
    error InitialGovernanceShareMismatch(uint256 observedShares, uint256 expectedWeight);

    enum DeploymentState {
        None,
        Pending,
        Finalized
    }

    struct Instance {
        address registry;
        address basketShare;
        address distributor;
        address vault;
        address revenueToken;
        address guard;
        address processor;
        address pair;
        address shareCoordinator;
    }

    struct DeployerSet {
        address registry;
        address basket;
        address distributor;
        address vault;
        address revenue;
        address guard;
        address processor;
        address coordinator;
    }

    struct DeploymentRequest {
        ProtocolConfig.DeploymentConfig config;
        ProtocolConfig.LaunchConfig launch;
        uint256 userNonce;
    }

    struct PendingDeployment {
        DeploymentState state;
        address creator;
        bytes32 configHash;
        address owner;
        address router;
        uint256 tokenLiquidity;
        uint256 nativeLiquidity;
        address lpRecipient;
        Instance instance;
    }

    event ProtocolPending(
        bytes32 indexed instanceId,
        bytes32 indexed configHash,
        address indexed creator,
        address revenueToken,
        address basketShare,
        address pair,
        address lpRecipient
    );
    event ProtocolDeployed(
        bytes32 indexed instanceId,
        bytes32 indexed configHash,
        address indexed creator,
        address revenueToken,
        address basketShare,
        address pair,
        address lpRecipient
    );
    event InitialLiquidity(
        address indexed pair, uint256 tokenReserve, uint256 nativeReserve, address lpRecipient
    );

    // Slots 0 and 1 are consumed through immutable delegate execution by ProtocolFinalizer.
    mapping(bytes32 instanceId => bool used) public deploymentUsed;
    mapping(bytes32 instanceId => PendingDeployment deployment) private _deployments;

    AssetRegistryDeployer public immutable registryDeployer;
    BasketShareDeployer public immutable basketDeployer;
    DividendDistributorDeployer public immutable distributorDeployer;
    BasketAcquisitionVaultDeployer public immutable vaultDeployer;
    RevenueTokenDeployer public immutable revenueDeployer;
    V2TwapGuardDeployer public immutable guardDeployer;
    TaxProcessorV2Deployer public immutable processorDeployer;
    ShareCoordinatorDeployer public immutable coordinatorDeployer;
    address public immutable finalizer;

    constructor(DeployerSet memory deployers) {
        _requireCode(deployers.registry);
        _requireCode(deployers.basket);
        _requireCode(deployers.distributor);
        _requireCode(deployers.vault);
        _requireCode(deployers.revenue);
        _requireCode(deployers.guard);
        _requireCode(deployers.processor);
        _requireCode(deployers.coordinator);
        registryDeployer = AssetRegistryDeployer(deployers.registry);
        basketDeployer = BasketShareDeployer(deployers.basket);
        distributorDeployer = DividendDistributorDeployer(deployers.distributor);
        vaultDeployer = BasketAcquisitionVaultDeployer(deployers.vault);
        revenueDeployer = RevenueTokenDeployer(deployers.revenue);
        guardDeployer = V2TwapGuardDeployer(deployers.guard);
        processorDeployer = TaxProcessorV2Deployer(deployers.processor);
        coordinatorDeployer = ShareCoordinatorDeployer(deployers.coordinator);
        finalizer = address(new ProtocolFinalizer());
    }

    function configHash(DeploymentRequest calldata request) external pure returns (bytes32) {
        return _configHash(request.config, request.launch);
    }

    function computeInstanceId(DeploymentRequest calldata request, address creator)
        external
        view
        returns (bytes32)
    {
        return _instanceId(_configHash(request.config, request.launch), creator, request.userNonce);
    }

    function deploymentState(bytes32 instanceId) external view returns (DeploymentState) {
        return _deployments[instanceId].state;
    }

    function pendingDeployment(bytes32 instanceId)
        external
        view
        returns (PendingDeployment memory)
    {
        return _deployments[instanceId];
    }

    function predictRevenueTokenAddress(DeploymentRequest calldata request, address creator)
        external
        view
        returns (address)
    {
        bytes32 hash = _configHash(request.config, request.launch);
        bytes32 salt = _componentSalt(hash, creator, request.userNonce, REVENUE_ID);
        return revenueDeployer.predict(address(this), salt, _revenueArguments(request.config));
    }

    function deploy(DeploymentRequest calldata request)
        external
        payable
        returns (Instance memory instance)
    {
        ProtocolConfig.DeploymentConfig calldata config = request.config;
        ProtocolConfig.LaunchConfig calldata launch = request.launch;
        uint256 userNonce = request.userNonce;
        _validate(config, launch);
        if (msg.value != 0) {
            revert IncorrectNativeValue(0, msg.value);
        }

        bytes32 hash = _configHash(config, launch);
        bytes32 instanceId = _instanceId(hash, msg.sender, userNonce);
        if (deploymentUsed[instanceId]) revert DeploymentAlreadyUsed();
        deploymentUsed[instanceId] = true;

        instance.revenueToken = _predictRevenue(config, hash, msg.sender, userNonce);
        instance.pair = _createOrValidatePair(
            config.chain.v2Factory, instance.revenueToken, config.chain.wrappedNative
        );

        instance.registry = _deployRegistry(
            config, launch, _componentSalt(hash, msg.sender, userNonce, REGISTRY_ID)
        );
        instance.basketShare = _deployBasket(
            launch, instance.registry, _componentSalt(hash, msg.sender, userNonce, BASKET_ID)
        );
        instance.distributor =
            _deployDistributor(config, _componentSalt(hash, msg.sender, userNonce, DISTRIBUTOR_ID));
        instance.vault =
            _deployVault(launch, instance, _componentSalt(hash, msg.sender, userNonce, VAULT_ID));

        RevenueToken revenue =
            _deployRevenue(config, _componentSalt(hash, msg.sender, userNonce, REVENUE_ID));
        if (address(revenue) != instance.revenueToken) revert PeerBindingInvalid();

        instance.shareCoordinator = _deployCoordinator(
            config, instance, _componentSalt(hash, msg.sender, userNonce, COORDINATOR_ID)
        );
        instance.guard = _deployGuard(
            config, launch, instance, _componentSalt(hash, msg.sender, userNonce, GUARD_ID)
        );
        instance.processor = _deployProcessor(
            config, instance, _componentSalt(hash, msg.sender, userNonce, PROCESSOR_ID)
        );

        _bindAndConfigure(instance, config.owner);
        _deployments[instanceId] = PendingDeployment({
            state: DeploymentState.Pending,
            creator: msg.sender,
            configHash: hash,
            owner: config.owner,
            router: config.chain.v2Router,
            tokenLiquidity: launch.liquidity.initialTokenLiquidity,
            nativeLiquidity: launch.liquidity.initialNativeLiquidity,
            lpRecipient: launch.liquidity.lpRecipient,
            instance: instance
        });
        emit ProtocolPending(
            instanceId,
            hash,
            msg.sender,
            instance.revenueToken,
            instance.basketShare,
            instance.pair,
            launch.liquidity.lpRecipient
        );
    }

    function finalizeAndSeed(bytes32) external payable {
        address implementation = finalizer;
        assembly ("memory-safe") {
            calldatacopy(0, 0, calldatasize())
            let success := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
            returndatacopy(0, 0, returndatasize())
            if iszero(success) { revert(0, returndatasize()) }
            return(0, returndatasize())
        }
    }

    function _instanceId(bytes32 hash, address creator, uint256 userNonce)
        private
        view
        returns (bytes32)
    {
        return keccak256(abi.encode(address(this), block.chainid, creator, hash, userNonce));
    }

    function _configHash(
        ProtocolConfig.DeploymentConfig calldata config,
        ProtocolConfig.LaunchConfig calldata launch
    ) private pure returns (bytes32) {
        return keccak256(abi.encode(config, launch));
    }

    function _deployRegistry(
        ProtocolConfig.DeploymentConfig calldata config,
        ProtocolConfig.LaunchConfig calldata launch,
        bytes32 salt
    ) private returns (address) {
        AssetRegistry.OracleConfig memory oracleConfig = AssetRegistry.OracleConfig({
            nativeUsdFeed: config.chain.nativeUsdFeed,
            sequencerUptimeFeed: config.chain.sequencerUptimeFeed,
            nativeMaxPriceAge: launch.oracle.nativeMaxPriceAge,
            nativeFeedDecimals: launch.oracle.nativeFeedDecimals,
            sequencerGracePeriod: config.security.sequencerGracePeriod,
            feedUpdateDelay: config.security.feedUpdateDelay,
            feedUpdateMaxDeviationBps: config.security.feedUpdateMaxDeviationBps
        });
        AssetRegistryDeployer.Arguments memory args;
        args.owner = address(this);
        args.oracleConfig = oracleConfig;
        args.assets = config.assets;
        return registryDeployer.deploy(salt, args);
    }

    function _deployBasket(
        ProtocolConfig.LaunchConfig calldata launch,
        address registry,
        bytes32 salt
    ) private returns (address) {
        return basketDeployer.deploy(
            salt,
            launch.basketName,
            launch.basketSymbol,
            address(this),
            AssetRegistry(registry),
            launch.acquisition.minimumGenesisDeposit
        );
    }

    function _deployDistributor(ProtocolConfig.DeploymentConfig calldata config, bytes32 salt)
        private
        returns (address)
    {
        return
            distributorDeployer.deploy(salt, address(this), config.economics.minimumDividendBalance);
    }

    function _deployVault(
        ProtocolConfig.LaunchConfig calldata launch,
        Instance memory instance,
        bytes32 salt
    ) private returns (address) {
        BasketAcquisitionVaultDeployer.Arguments memory args = BasketAcquisitionVaultDeployer
            .Arguments({
            owner: address(this),
            basket: BasketShare(instance.basketShare),
            distributor: DividendDistributor(instance.distributor),
            registry: AssetRegistry(instance.registry),
            fillerPayoutBps: launch.acquisition.fillerPayoutBps,
            maxNativePerFill: launch.acquisition.maxNativePerFill,
            dailyNativePerAsset: launch.acquisition.dailyNativePerAsset
        });
        return vaultDeployer.deploy(salt, args);
    }

    function _deployGuard(
        ProtocolConfig.DeploymentConfig calldata config,
        ProtocolConfig.LaunchConfig calldata launch,
        Instance memory instance,
        bytes32 salt
    ) private returns (address) {
        V2TwapGuardDeployer.Arguments memory args = V2TwapGuardDeployer.Arguments({
            pair: instance.pair,
            tokenIn: instance.revenueToken,
            tokenOut: config.chain.wrappedNative,
            minimumAge: launch.twap.twapMinimumAge,
            maximumAge: launch.twap.twapMaximumAge,
            maximumDeviationBps: config.economics.maxTwapDeviationBps,
            maximumInputReserveBps: config.economics.maxSwapReserveBps
        });
        return guardDeployer.deploy(salt, args);
    }

    function _deployProcessor(
        ProtocolConfig.DeploymentConfig calldata config,
        Instance memory instance,
        bytes32 salt
    ) private returns (address) {
        TaxProcessorV2Deployer.Arguments memory args = TaxProcessorV2Deployer.Arguments({
            owner: address(this),
            weth: config.chain.wrappedNative,
            router: config.chain.v2Router,
            guard: instance.guard,
            feeReceiver: config.protocolRevenueRecipient,
            vault: payable(instance.vault),
            protocolRevenueBps: config.economics.protocolRevenueBps,
            cooldown: config.security.processingCooldown
        });
        return processorDeployer.deploy(salt, args);
    }

    function _deployCoordinator(
        ProtocolConfig.DeploymentConfig calldata config,
        Instance memory instance,
        bytes32 salt
    ) private returns (address) {
        return coordinatorDeployer.deploy(
            salt,
            address(this),
            instance.revenueToken,
            instance.distributor,
            config.economics.minimumDividendBalance
        );
    }

    function _bindAndConfigure(Instance memory instance, address treasury) private {
        TaxProcessorV2 processor = TaxProcessorV2(payable(instance.processor));
        processor.bindRevenueToken(instance.revenueToken);

        BasketShare basket = BasketShare(instance.basketShare);
        basket.bind(instance.vault, instance.distributor);

        DividendDistributor distributor = DividendDistributor(instance.distributor);
        distributor.bind(
            instance.revenueToken, instance.shareCoordinator, instance.vault, instance.basketShare
        );

        RevenueToken revenue = RevenueToken(instance.revenueToken);
        revenue.bindPeers(instance.processor, instance.shareCoordinator);
        revenue.registerAmmPair(instance.pair);

        _configureExclusions(instance, treasury);
    }

    function _deployRevenue(ProtocolConfig.DeploymentConfig calldata config, bytes32 salt)
        private
        returns (RevenueToken)
    {
        return RevenueToken(revenueDeployer.deploy(salt, _revenueArguments(config)));
    }

    function _configureExclusions(Instance memory instance, address treasury) private {
        DividendDistributor distributor = DividendDistributor(instance.distributor);
        ShareCoordinator coordinator = ShareCoordinator(instance.shareCoordinator);
        address[11] memory excluded = [
            instance.pair,
            instance.registry,
            instance.revenueToken,
            instance.processor,
            instance.distributor,
            instance.basketShare,
            instance.vault,
            instance.guard,
            address(this),
            instance.shareCoordinator,
            treasury
        ];
        for (uint256 i; i < excluded.length; ++i) {
            distributor.setExcluded(excluded[i], true);
            coordinator.setExcluded(excluded[i], true);
        }

        RevenueToken revenue = RevenueToken(instance.revenueToken);
        address[5] memory tokenExcluded =
            [address(this), instance.registry, instance.basketShare, instance.vault, instance.guard];
        for (uint256 i; i < tokenExcluded.length; ++i) {
            revenue.setExcluded(tokenExcluded[i], true);
        }
    }

    function _validate(
        ProtocolConfig.DeploymentConfig calldata config,
        ProtocolConfig.LaunchConfig calldata launch
    ) private view {
        if (config.chain.chainId != block.chainid) {
            revert WrongChain(config.chain.chainId, block.chainid);
        }
        if (
            config.owner == address(0) || config.protocolRevenueRecipient == address(0)
                || launch.liquidity.lpRecipient == address(0)
        ) revert ZeroAddress();
        _requireCode(config.chain.wrappedNative);
        _requireCode(config.chain.v2Factory);
        _requireCode(config.chain.v2Router);
        _requireCode(config.chain.nativeUsdFeed);
        if (config.chain.sequencerUptimeFeed != address(0)) {
            _requireCode(config.chain.sequencerUptimeFeed);
        }
        if (config.chain.v4PoolManager != address(0)) _requireCode(config.chain.v4PoolManager);
        if (IUniswapV2Router02(config.chain.v2Router).factory() != config.chain.v2Factory) {
            revert RouterFactoryMismatch();
        }
        if (IUniswapV2Router02(config.chain.v2Router).WETH() != config.chain.wrappedNative) {
            revert RouterWrappedNativeMismatch();
        }

        if (
            bytes(launch.basketName).length == 0 || bytes(launch.basketSymbol).length == 0
                || launch.oracle.nativeMaxPriceAge == 0 || launch.twap.twapMinimumAge == 0
                || launch.twap.twapMaximumAge <= launch.twap.twapMinimumAge
                || launch.acquisition.maxNativePerFill == 0
                || launch.acquisition.dailyNativePerAsset == 0
                || launch.liquidity.initialNativeLiquidity == 0
                || launch.liquidity.initialTokenLiquidity == 0
                || launch.liquidity.initialTokenLiquidity >= config.token.initialSupply
                || launch.acquisition.minimumGenesisDeposit == 0
        ) revert InvalidLaunchConfig();
        if (
            launch.twap.twapMinimumAge != config.security.twapMinimumAge
                || launch.twap.twapMaximumAge != config.security.twapMaximumAge
                || launch.acquisition.fillerPayoutBps != config.economics.fillerPayoutBps
                || launch.acquisition.minimumGenesisDeposit != config.economics.minimumGenesisDeposit
        ) revert InconsistentLaunchConfig();
        if (
            config.token.initialSupply == 0 || config.economics.buyTaxBps > 1000
                || config.economics.sellTaxBps > 1000 || config.economics.protocolRevenueBps > BPS
                || config.economics.fillerPayoutBps > 11_000 || config.economics.maxSwapReserveBps == 0
                || config.economics.maxSwapReserveBps > BPS
                || config.economics.maxTwapDeviationBps >= BPS
                || config.economics.processingThreshold == 0
                || config.economics.processingThresholdFloor > config.economics.processingThreshold
                || config.economics.minimumDividendBalance == 0
                || config.economics.genesisSharePriceUsd != 1e18
                || config.economics.minimumLiquidityShares != 1000
                || config.security.feedUpdateDelay == 0
                || config.security.feedUpdateMaxDeviationBps > BPS
                || config.security.shareSyncGasLimit < MIN_SHARE_SYNC_GAS_LIMIT
        ) revert InvalidEconomics();

        uint256 count = config.assets.length;
        if (count == 0 || count > ProtocolConfig.MAX_ASSETS) revert InvalidAssetCount(count);
        uint256 weightSum;
        for (uint256 i; i < count; ++i) {
            ProtocolConfig.AssetConfig calldata asset = config.assets[i];
            _requireCode(asset.token);
            _requireCode(asset.usdFeed);
            if (
                asset.weightBps == 0 || asset.maxPriceAge == 0 || asset.maxTimestampSkew == 0
                    || asset.expectedUiMultiplier == 0
                    || IRobinhoodStockToken(asset.token).decimals() != asset.expectedTokenDecimals
                    || IRobinhoodStockToken(asset.token).oraclePaused()
                    || IRobinhoodStockToken(asset.token).uiMultiplier() != asset.expectedUiMultiplier
                    || IAggregatorV3(asset.usdFeed).decimals() != asset.expectedFeedDecimals
            ) revert InvalidAsset(i);
            for (uint256 j; j < i; ++j) {
                if (config.assets[j].token == asset.token) revert DuplicateAsset(asset.token);
            }
            weightSum += asset.weightBps;
        }
        if (weightSum != BPS) revert InvalidWeightSum(weightSum);
        if (
            IAggregatorV3(config.chain.nativeUsdFeed).decimals() != launch.oracle.nativeFeedDecimals
        ) {
            revert InvalidLaunchConfig();
        }
    }

    function _createOrValidatePair(address factory, address revenue, address weth)
        private
        returns (address pair)
    {
        pair = IUniswapV2Factory(factory).getPair(revenue, weth);
        if (pair == address(0)) pair = IUniswapV2Factory(factory).createPair(revenue, weth);
        if (pair == address(0) || pair.code.length == 0) revert PairCreationFailed();
        if (IUniswapV2Factory(factory).getPair(revenue, weth) != pair) {
            revert PairCreationFailed();
        }
        address expected0 = revenue < weth ? revenue : weth;
        address expected1 = revenue < weth ? weth : revenue;
        address token0 = IUniswapV2Pair(pair).token0();
        address token1 = IUniswapV2Pair(pair).token1();
        if (token0 != expected0 || token1 != expected1) {
            revert PairOrderingInvalid(token0, token1);
        }
        (uint112 reserve0, uint112 reserve1,) = IUniswapV2Pair(pair).getReserves();
        if (reserve0 != 0 || reserve1 != 0) revert PairAlreadySeeded();
    }

    function _predictRevenue(
        ProtocolConfig.DeploymentConfig calldata config,
        bytes32 hash,
        address creator,
        uint256 userNonce
    ) private view returns (address) {
        return revenueDeployer.predict(
            address(this),
            _componentSalt(hash, creator, userNonce, REVENUE_ID),
            _revenueArguments(config)
        );
    }

    function _revenueArguments(ProtocolConfig.DeploymentConfig calldata config)
        private
        view
        returns (RevenueTokenDeployer.Arguments memory args)
    {
        args.name = config.token.name;
        args.symbol = config.token.symbol;
        args.initialSupply = config.token.initialSupply;
        args.initialHolder = address(this);
        args.owner = address(this);
        args.buyTaxBps = config.economics.buyTaxBps;
        args.sellTaxBps = config.economics.sellTaxBps;
        args.processingThreshold = config.economics.processingThreshold;
        args.processingThresholdFloor = config.economics.processingThresholdFloor;
        args.shareSyncGasLimit = config.security.shareSyncGasLimit;
    }

    function _componentSalt(bytes32 hash, address creator, uint256 userNonce, bytes32 componentId)
        private
        view
        returns (bytes32)
    {
        return keccak256(
            abi.encode(address(this), block.chainid, creator, hash, userNonce, componentId)
        );
    }

    function _requireCode(address target) private view {
        if (target.code.length == 0) revert NoCode(target);
    }
}
Chain explorer5660msChain node115ms