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
- Creator
- 0x58bEE2351d…23EFdd09fb
- 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