Security & Roadmap
Trust model, security features, known limitations, invariants, and production roadmap
Security & Roadmap
This page describes the protocol's trust model, active security features, known limitations, invariants that should hold, and the remaining production-hardening roadmap. For the system design that these properties derive from, see Architecture.
Trust Model
| Actor | Trust Level | Capabilities |
|---|---|---|
| Strategy Owner | Trusted for allocations and circuit-breaker | Can rebalance and pause/unpause. Cannot withdraw or upgrade. |
| Donors | Trustless | Send funds directly. No approvals, no routing. |
| Recipients | Trustless | Pull from warehouse. Isolated failures. |
| Protocol | Trustless | No admin keys. No upgrade authority. Per-tenant factories. |
Non-Custodial Properties
- The Strategy contract has no withdrawal function. Funds can only leave via
distribute(), which routes to configured recipients through the SplitsWarehouse or, for same-factory child strategies, by direct transfer to the child contract. The owner can change where funds go (rebalance) but can never extract funds. - Distribution is permissionless — anyone can call
distribute(). This prevents funds from being locked by an inactive owner. - Recipients self-custody — only the balance owner (or delegate) can call
warehouse.withdraw(). No one can redirect, freeze, or force withdrawals. - All off-chain social data (drafts, comments, reactions, notifications) is non-financial. The server can be compromised without risk to funds — on-chain state remains the source of truth.
Failure Isolation
- Each strategy is independent — no shared state except the warehouse.
- Batch deposits to the warehouse mean a reverting recipient does not block the others.
- Child strategies registered with the same factory receive allocations by direct transfer; the parent does not call
distributeon the child, so the child's pause state or internal errors cannot block the parent's payout to other recipients. - Factories are stateless beyond the implementation address and registry mappings — no admin path, no pause.
Security Features
ReentrancyGuardon all state-changing functions (distribute,harvest,deposit,withdraw).PausableUpgradeableonStrategyandYieldRedirector4626(owner-only circuit breaker).withdraw/redeemon the redirector remain callable while paused so depositors can always exit.- Rebalance → distribute cooldown:
rebalanceDistributeCooldownis an immutable on the factory; each clone reads it once at init. Every allocation change (includinginitialize) setsdistributeUnlockAt = block.timestamp + cooldown.distribute()reverts withDistributeLocked(distributeUnlockAt)until it expires. This mitigates rebalance-front-running of large incoming donations without adding a two-step UX. - Factory-owned registries:
isFactoryStrategyandisFactoryRedirectormappings anchor identity to the deploying factory.Strategy._isStrategy()staticcalls the factory's registry rather than trusting a spoofablefactory()return. - Sender-bound deterministic CREATE2:
createDeterministicderiveskeccak256(abi.encode(msg.sender, salt)), so different deployers cannot collide on the same salt.predictDeterministicAddress(deployer, salt)mirrors this. - ENS label claim guards:
StrategyFactory.assignPendingLabel(node, claimant)must pre-approve a strategy before it can consume an ENS name transferred to the factory.setENSNamerevertsUnknownStrategyfor non-registered addresses. - Harvest thresholds:
YieldRedirector4626.harvest()revertsHarvestTooSoonwhen called insideminHarvestInterval, andHarvestBelowMinimumwhenharvestable() < minHarvestAmount. Both thresholds are set from factory immutables. - ERC-4626
max*overrides:maxDeposit,maxMint,maxWithdraw, andmaxRedeemclamp to the source vault's limits and the currently trackedprincipal. sweep(token, to)rescues accidentally-sent tokens on the redirector. Source-vault shares are explicitly excluded (CannotSweepSourceShares).SafeERC20for all token operations;forceApprove(warehouse, 0)after every ERC-20 batch deposit.- Checks-Effects-Interactions pattern throughout.
- Custom errors for gas efficiency.
_disableInitializers()on implementation contracts.
Invariants
Strategy
totalWeight == sum(allocations[i].weight).allocations.length > 0 && allocations.length <= 50.- All recipients non-zero; no recipient equals the strategy itself.
- While a call to
distributeis executing,block.timestamp >= distributeUnlockAt.
YieldRedirector4626
totalAssets() == principal(wrapper share price stays 1:1 with the underlying asset).harvestable() == max(sourceVault.convertToAssets(vaultShares) - principal, 0).principalis denominated in asset units and is decremented by the delivered amount onwithdrawandharvest(never by the shares burned).- Under source-vault losses,
principalis best-effort: wrapper depositors share the loss proportionally viamax*clamps.
Known Limitations
| Limitation | Risk | Path to Resolution |
|---|---|---|
| No minimum distribution | Gas griefing via tiny distributions | MIN_DISTRIBUTION threshold on distribute |
| Pause authority is a single key | Owner-key compromise can halt distributions | Multisig + timelock ownership of strategies and factories |
| Fee-on-transfer tokens | Balance calculations incorrect | Token allowlist or explicit warning at deposit-time |
| Rebasing tokens | Share accounting breaks | Not supported |
| Source-vault losses | Redirector principal is best-effort | Allowlist audited source vaults; document per-vault risk |
| External reward tokens | Rewards on the source vault are not harvested | Add reward-token harvesting path |
Token Compatibility
| Token Type | Status |
|---|---|
| Standard ERC-20 | Supported |
| Native ETH | Supported |
| Fee-on-transfer | Not supported |
| Rebasing | Not supported |
| ERC-777 | Untested |
Production Roadmap
Shipped items reflect the audit-fix pass.
Security hardening
- Rebalance → distribute cooldown (configurable per factory)
- Pausable
StrategyandYieldRedirector4626 - Factory-owned strategy / redirector registries + spoof-resistant routing
- Sender-bound deterministic CREATE2 salts
- Child strategies: push-only transfer from parent (no nested
distribute) - Internal audit pass
- Minimum-distribution threshold
- Multisig + timelock ownership for factory / strategy
pauseauthority - External security audit by a reputable firm
Yield Redirector
- Live-value accounting (
totalAssets()returnsprincipal,harvestable()returns live surplus) -
minHarvestAmountandminHarvestInterval -
sweep()for non-source tokens - ERC-4626
max*overrides - External reward-token harvesting
- Additional vault standards beyond ERC-4626
Token support
- Allowlist or runtime detection for non-standard tokens
- Wrapper strategies for fee-on-transfer tokens
- Documented deposit warnings in the UI
Governance
- Governor-controlled strategies
- Multi-sig approval on large rebalances
- Time-weighted allocation changes
Social layer
- Rate limiting on REST endpoints
- Spam prevention for comments/reactions
- Notification delivery (email, push) beyond polling
- Comment moderation tools
Ecosystem
- Base Sepolia multi-tenant deployment
- Additional L2 deployments (Optimism, Arbitrum)
- Integration with streaming distribution (Drips)
- On-chain curator reputation / scoring
- Additional tenants beyond
support.eth
Technical Specifications
Contract Standards
- Solidity
^0.8.20 - OpenZeppelin Contracts (Upgradeable)
- EIP-1167 Minimal Proxy Clones
- ERC-6909 Multi-Token Warehouse (via 0xSplits)
- ERC-4626 Vault Standard (Yield Redirector)
- ERC-7528 ETH Address Convention
Gas (approximate)
| Operation | Gas |
|---|---|
| Create strategy | ~150,000 |
| Rebalance (5 allocations) | ~80,000 |
| Distribute (5 recipients) | ~120,000 |
| Withdraw from warehouse | ~50,000 |
| Create yield redirector | ~200,000 |
| Harvest yield | ~100,000 |
Networks
| Network | Chain ID | Role |
|---|---|---|
| Hardhat | 31337 | Local development |
| Sepolia | 11155111 | Testnet |
| Base Sepolia | 84532 | Multi-tenant testnet |
| Mainnet | 1 | Planned |
External Dependencies
- SplitsWarehouse (0xSplits)
- ENS (Ethereum Name Service)
- Ponder (indexer)
- viem / wagmi (SDK)
Ownership of strategies and factories is currently a single key. For production use, move factory ownership to a multisig (with a timelock where appropriate) before deploying to mainnet.