아키텍처
프로토콜은 두 체인에 걸쳐 있습니다. Ethereum에는 런치패드, 풀, 볼트가 있고, Robinhood Chain에는 토큰화 주식이 있습니다.
컨트랙트
이 표는 프로토콜을 결정된 대로 설명합니다. 런칭 부분은 2026-09-28부터 코드에 있고, 에어드랍
분배는 2026-10-04부터 코드에 있지만 배포되지 않았습니다; 주식의 LayerZero 어댑터는 저장소에
없습니다. 공식 스왑 라우터 외의 라우터와 어댑터는 이 표에 넣지 않았습니다: Ethereum의
UniswapV4StockRouter, Robinhood Chain의 RobinhoodStockRouter, 그리고 UsdgOftAdapter.
진행 상황을 참고하십시오. 2026-10-02부터 이 표의 모든 컨트랙트는 토큰, 유동성 락,
배포자 컨트랙트를 제외하고 업그레이드할 수 있습니다: 아래를 참고하십시오. 다이어그램의 비율은
택스의 기본 설정입니다.
| 컨트랙트 | 역할 | 개수 |
|---|---|---|
StockFunFactory |
마켓을 생성하고, 생성 수수료를 징수하며, 레지스트리와 새 마켓 및 볼트를 위한 오너의 설정을 관리; 모든 Ethereum 모듈의 업그레이드 권한자 | 1개 |
MarketDeployer · VaultDeployer |
토큰의 생성 코드와 볼트 프록시의 생성 코드를 담아 EIP-170 제한을 우회; 상태를 보유하지 않으며, 업그레이드되는 대신 팩토리를 통해 교체됨 | 각 1개 |
StockFunToken |
업그레이드할 수 없는 단순한 ERC-20으로, 민트도 소각도 없음; 프로토콜 오너를 위한 setter 하나, setRecorder와 레스큐 외에는 자체 로직이 없음; 모든 잔액 변경을 보유량 기록기에 보고 |
마켓당 1개 |
HoldingRecorder |
모든 토큰에 대해 각 주소의 보유량을 시간에 따라 기록하고, Uniswap PoolManager 밖의 공급량도 기록; 기록이 실패하면 그 전송도 실패하며, 이는 아래 격리 규칙의 의도된 유일한 예외 |
1개 |
TreasuryVault |
ETH, USDC, 이어서 주식을 보유; 매수 레그를 각각 따로 실행하는 전환, 로컬 레일에서 주식을 각각 따로 에어드랍에 넘기기(sendToAirdrop), 즉시 이루어지는 긴급 회수 |
마켓당 프록시 1개 |
LiquidityLock |
풀을 생성하고 두 포지션을 예치해 잠그며, 각 풀의 LP 수수료와 틱 간격을 보관; 수수료 수취분 중 거부된 몫을 그 수령인을 위해 보관; 업그레이드할 수 없음; 유일한 출구는 공지 후 30일이 지나야 쓸 수 있는 종료 모드 | 1개 |
StockFunHook |
모든 스왑에서 수수료를 떼고, 안티 스나이프를 적용하며, 택스 설정과 각 풀의 화이트리스트를 관리하고, 락 외에는 누구의 유동성도 받지 않으며, 크리에이터, 팀, 바이백 잔액을 청구될 때까지 보관하고, 자신의 몫을 거부한 볼트에 지급해야 할 것도 보관 | 1개 |
StockFunSwapRouter |
거래용 공식 라우터로, 화이트리스트 주소가 안티 스나이프를 면제받을 수 있는 유일한 라우터 | 1개, 오너가 교체 가능 |
StockFunLens |
읽기 전용; 각 볼트를 따로 읽어, 디앱을 위해 마켓 상태를 집계; 2026-10-06부터는 그 페이지에 볼트의 레일, 일시 중지 상태, 브리지로 보낸 USDC, 풀의 잠금 상태, 토큰의 보유량 기록기, 그리고 훅과 락이 볼트에 지급해야 할 것도 담김 | 1개 |
TreasuryOracle |
한 번만 기록되는 Chainlink 피드; 설정할 수 있는 것은 그 하트비트와, 2026-10-06부터는 Robinhood Chain에서의 두 가드: Chainlink가 그 체인의 업타임 피드를 공개할 때까지 꺼져 있는 시퀀서 검사, 그리고 기업 행위 동안 그 주식의 가격을 보류하는 주식별 오라클 일시 중지 검사 | 체인당 1개 |
BuybackBurner |
$STOCKFUN을 매수해 소각 주소로 보냄. $STOCKFUN 풀이 잠겨 있는 동안 다른 경로는 존재하지 않음 |
1개 |
BridgeHub · RemoteHub · RemoteTreasuryVault |
크로스체인 레일; 리모트 허브는 Robinhood Chain의 업그레이드 권한자이며 에어드랍 경로와 각 주식의 어댑터를 지정; 미러 볼트는 자신의 주식을 에어드랍으로 보냄(sendToAirdrop) |
1개, 1개, 마켓당 프록시 1개 |
StockFunProtocolToken |
마켓 토큰처럼 업그레이드할 수 없는 $STOCKFUN; 전체 공급량이 잠긴 포지션에 들어감 |
1개 |
AirdropDistributor |
각 트레저리가 매수한 주식을 Ethereum에서 해당 토큰의 홀더에게 일일 주기로 분배; 마켓 자신의 볼트에서 온 것만 적립; 각 홀더가 청구하며, 청구는 지급할 수 있는 모든 주식을 지급; 팩토리에 묶여 있으며, 팩토리가 이를 지정 | 1개 |
프록시와 업그레이드
2026-10-02부터 모든 모듈은 구현 컨트랙트 앞에 놓인 ERC-1967 프록시이며, UUPS 방식으로 업그레이드됩니다. 프록시는 주소와 상태를 보유하고, 구현 컨트랙트는 코드를 보유하며, 업그레이드는 구현 컨트랙트를 교체합니다. 구현 컨트랙트는 결코 초기화될 수 없습니다: 각 프록시는 자신의 생성자 안에서 초기화됩니다.
모든 모듈은 누가 자신을 업그레이드할 수 있는지를 하나의 컨트랙트, 즉 구현 컨트랙트에 고정된 자신의
업그레이드 권한자에게 묻습니다: Ethereum에서는 팩토리이며, 팩토리는 자신의 오너로 답합니다.
Robinhood Chain에서는 리모트 허브이며, 리모트 허브는 자신의 긴급 관리자, 즉 마지막 브리지 배치가
전달한 Ethereum 오너로 답합니다. 따라서 팩토리의 소유권을 이전하면 모든 Ethereum 모듈의 업그레이드
권한이 한 번에 옮겨지고, Robinhood Chain 모듈의 업그레이드 권한은 다음 배치와 함께 옮겨집니다. 새
구현 컨트랙트가 다른 권한자를 지정하면 업그레이드는 거부됩니다. 훅과 보유량 기록기의 새 구현
컨트랙트는 같은 PoolManager도 유지해야 합니다.
업그레이드할 수 없는 것은 토큰, 유동성 락, 그리고 Robinhood Chain의 미러 볼트 배포자입니다. 모든
미러 볼트의 주소는 미러 볼트 배포자의 주소에서 도출됩니다. MarketDeployer와 VaultDeployer는
상태를 보유하지 않습니다: 팩토리는 이들을 업그레이드하는 대신 교체합니다(setDeployers).
스토리지는 끝에 추가될 뿐, 순서가 바뀌지 않습니다: 각 모듈의 레이아웃은
contracts/storage-layouts/에 기록되어 있으며, contracts/script/check-storage-layouts.sh가 매
업그레이드 전에 이를 비교합니다. 2026-10-05부터 이 스크립트는 모든 구조체를 모든 깊이까지, 크기를
포함해 비교하며, 스토리지 배열의 원소인 구조체는 어떤 변경이든 거부합니다: 커질 수 있는 것은
매핑의 값인 구조체나 마지막 상태 변수뿐이며, 그 끝에서만 커질 수 있습니다.
구현 컨트랙트는 결코 직접 사용되지 않지만, 실수로 그 주소로 보내진 것에도 조치 수단이 있습니다. 대부분의 구현 컨트랙트는 자신의 권한자를 immutable에서 읽으므로 프로토콜 오너가 이를 꺼냅니다; 팩토리와 리모트 허브의 구현 컨트랙트는 관리자를 프록시의 스토리지에 두므로, 그 구현 컨트랙트에서는 그것을 배포한 주소가 조치 수단입니다.
누가 업그레이드할 수 있는지, 그리고 얼마나 빨리 할 수 있는지는 신뢰 모델을 참고하십시오.
에어드랍 컨트랙트
2026-10-04부터 AirdropDistributor가 Ethereum에서 각 트레저리의 주식을 분배합니다. 다른 모듈과
마찬가지로 업그레이드 가능한 모듈이며, 업그레이드 권한자가 팩토리인 프록시입니다. 팩토리가 이를
지정하고(setAirdropDistributor) 볼트는 이를 실시간으로 읽습니다. 교체된 분배 컨트랙트도 자신의
모든 주기를 그곳에서 청구할 수 있는 상태로 유지합니다. 보유량은 보유량 기록기에서 읽으며, 그
규칙은 에어드랍에 있습니다.
두 경로가 이 컨트랙트에 닿으며, 그 밖의 어떤 것도 주기에 적립되지 않습니다:
- 브리지 레일. 미러 볼트의
sendToAirdrop은 목록의 각 주식을, 리모트 허브가 지정한(setStockAdapter) 그 주식의 LayerZero 어댑터를 통해, 리모트 허브가 지정한(setAirdrop) 분배 컨트랙트로, 마켓 ID를 페이로드로 담아 보냅니다. Ethereum에서는 주식의 OFT가 래핑된 주식을 분배 컨트랙트에 민트하고, LayerZero의 엔드포인트가 이를 호출합니다. 분배 컨트랙트는 오너가 등록한 주식 OFT로부터, Robinhood Chain에서, 브리지 허브가 그 마켓에 대해 도출한 미러 볼트가 보낸 전달만 적립합니다. - 로컬 레일. Ethereum 볼트의
sendToAirdrop은 정확한 금액을 승인하고, 분배 컨트랙트는 마켓 자신의 볼트에서만 이를 가져가 받은 양을 적립하며, 승인은 다시 닫힙니다. 브리지 허브에 연결된 볼트는 이 호출을 거부합니다.
키퍼는 두 경로를 모두 트리거하며, 시점만 정합니다; 브리지 레일에서는 2026-10-06부터 각 전달이 Ethereum에서 받는 가스도 정하며, 리모트 허브는 이 가스를 StockFun의 오너가 그곳에서 정하는 범위 안으로 유지합니다.
구조적 특성
훅은 싱글턴입니다. 컨트랙트 하나가 모든 풀을 담당하므로 마켓마다 주소를 채굴할 필요가 없습니다 — v4 훅의 주소는 하위 비트에 권한을 인코딩하며, 그런 주소를 찾는 데에는 연산 비용이 듭니다. 2026-10-02부터 훅은 주소에 v4 권한 14개가 모두 담긴 프록시입니다: 업그레이드해도 그 주소는 유지되며, 이후의 구현 컨트랙트는 어떤 콜백이든 사용할 수 있습니다.
유동성은 NFT가 아닙니다. 락의 주소를 키로 하여 PoolManager에 직접 보관됩니다. 이전할
포지션도, 취소할 approve도, 잃어버릴 tokenId도 없습니다. 이 컨트랙트가 수행할 수 있는 유동성
작업은 수수료를 수취하기 위한, 델타가 정확히 0인 modifyLiquidity, 그리고 종료가 공지된 지 30일
뒤 종료 모드를 통해 한 풀의 모든 포지션을 제거하는 것뿐입니다.
락만 유동성을 추가합니다. 2026-10-01부터 훅은 StockFun 풀에 대한 다른 모든 포지션을 거부하므로, 모든 거래는 잠긴 포지션을 상대로 한 스왑이며, 택스를 냅니다.
볼트에는 인출이 없습니다. 누구든 볼트의 자산을 자신이 고른 주소로 보낼 수 있게 하는 함수는 없습니다. 출구는 두 가지입니다: 프로토콜이 지정한 에어드랍 컨트랙트만을 목적지로 하며, 그 컨트랙트가 누구도 선택하지 않는 규칙에 따라 토큰 홀더에게 비례 배분으로 지급하는 에어드랍, 그리고 StockFun의 오너가 자산을 어떤 주소로든 즉시 이동하는 긴급 모드입니다. 이는 현재 구현 컨트랙트의 규칙입니다: StockFun의 오너는 볼트를 업그레이드할 수 있으며, 업그레이드는 즉시 효력을 가집니다.
한도는 설정이고, 피드는 그렇지 않습니다. 기본값 50과 200 베이시스 포인트인 볼트의 가격 한도는 StockFun 오너의 설정이며, 모든 볼트가 실시간으로 읽습니다; 키퍼는 한도를 좁힐 수만 있습니다. 피드 레지스트리는 각 피드를 한 번만 기록합니다; 피드의 하트비트는 설정이며, 2026-10-06부터는 가격을 보류할 수만 있을 뿐 결코 바꾸지 못하는 레지스트리의 두 가드도 설정입니다: 시퀀서 검사(Chainlink가 Robinhood Chain의 업타임 피드를 공개할 때까지 그 체인에서는 꺼져 있음)와 주식별 오라클 일시 중지 검사(Robinhood Chain의 모든 주식에서 켜져 있음; Robinhood 레일 참고). 긴급 모드는 둘 다 건드리지 않습니다: 자산을 이동할 뿐, 실행 규칙을 바꾸지는 않습니다. 레지스트리는 볼트와 마찬가지로 StockFun의 오너가 업그레이드할 수 있습니다.
격리와 조치 수단
2026-10-05에 창업자는 하나의 설계 규칙을 정했습니다: 무언가가 한 기능을 실패하게 만들 때, 다른 기능들이 그 대가를 치러서는 안 되며, 모든 것이 계속 작동할 수 있어야 하고, 모든 것에는 잃어버린 자금을 회수하고 수정을 받아들일 조치 수단이 있어야 합니다. 그날의 네 번째 감사 루프가 이를 코드에 반영했고, 그 이후 감사 루프는 이 규칙의 두 부분 중 어느 쪽이든 어기는 것을 결함으로 셉니다.
격리. 한 기능, 마켓, 주식, 주기, 기록, 수령인에서의 실패는 결코 다른 것들을 막지 않습니다: 그 항목은 건너뛰거나, 지급해야 할 것으로 보관하거나, 연기하며, 이벤트를 발생시키고, 나머지는 계속 진행됩니다.
- 브리지 배치는 볼트가 현금을 내줄 수 없는 마켓을 제외하며(
MarketSkipped), 다른 마켓들은 건너갑니다. 현금 토큰이 미러 볼트 하나에 대해 거부하는 전달은 그 마켓에 지급해야 할 것으로 리모트 허브에 남으며(DeliveryRefused), 다른 마켓들은 지급받습니다 - 매수는 각 주식의 레그를 따로 실행하며(
LegFailed), 에어드랍으로의 전송은 각 주식을 따로 보냅니다(AirdropSendFailed) - 청구는 지급할 수 있는 모든 주식을 지급하고 나머지는 연기합니다(
ClaimDeferred) - ETH를 거부하는 볼트는 더 이상 그 마켓의 거래를 멈추지 않습니다: 훅은 지급할 수 없었던 것을 그
볼트에 지급해야 할 것으로 보관하며(
treasuryOwed), 락은 수수료 수취분 중 거부된 몫을 그 수령인을 위해 보관합니다(vaultOwed,creatorOwed). 수령인이 다시 ETH를 받게 되면 누구나 이를 지급하며(payTreasury,payOwed), 키퍼는 매 주기 그렇게 합니다 - Lens는 각 볼트를 별도의 호출로 읽으므로, 응답할 수 없는 볼트가 있어도 다른 볼트들은 읽을 수
있습니다(
vaultReadable). 2026-10-06부터 그 호출은 볼트의 레일, 일시 중지 상태, 브리지로 보낸 USDC도 담고, 페이지는 훅과 락이 볼트에 지급해야 할 것도 담습니다: 앱의 데이터 서비스는 마켓별로 그 밖의 아무것도 읽지 않으므로, 가스를 모두 소진하는 볼트가 있어도 실패하는 것은 그 볼트 자신의 수치뿐입니다. 일곱 번째 감사 루프 이후로 데이터 서비스는 업그레이드 가능한 각 모듈(훅, 오라클, 에어드랍 컨트랙트, 두 허브)과 프로토콜 토큰도 각각 별도의 그룹으로 읽으므로, 잘못된 업그레이드 이후 가스를 모두 소진하는 모듈이 있어도 실패하는 것은 그 모듈 자신의 수치뿐입니다
조치 수단. 지나가는 동안이든 실수로든 ETH나 토큰을 보유할 수 있는 모든 컨트랙트에는 묶인 것을 꺼낼 조치 수단이 있으며, 모든 모듈은 수정을 받아들일 수 있습니다: 업그레이드, 또는 모듈을 교체하는 setter.
- 트랜잭션 사이에 누구의 것도 보관하지 않는 모듈 — 팩토리, Lens, 오라클, 보유량 기록기, 주식
라우터, 공식 스왑 라우터, 브리지 어댑터 — 에는 프로토콜 오너의
rescue(asset, amount, to)가 있습니다 - 자체 장부를 관리하는 모듈 — 볼트, 두 허브, 에어드랍 컨트랙트 — 에는 긴급 모드가 있습니다
- 훅의
rescue는 실수로 훅에 보내진 것만 가져갈 뿐, 훅이 지급해야 할 것은 결코 가져가지 않습니다. 락의 레스큐는 락이 보관하는 몫도, 종료 모드만 닿을 수 있는 포지션도 결코 가져가지 않습니다.BuybackBurner의 레스큐는 어떤 소각도 그 ETH를 쓸 수 없게 된 뒤에만 ETH를 가져갑니다. 업그레이드할 수 없는 토큰은 자신의 주소로 보내진 것을 꺼낼 수 있습니다 - 설계상 조치 수단이 없는 것: Ethereum의 두 배포자 컨트랙트와 미러 볼트 배포자로, 상태를 보유하지 않고, ETH를 받지 않으며, 오너가 없습니다
자세한 내용은 긴급 모드에 있습니다.
의도된 유일한 예외. 토큰이 보유량 기록기에 하는 보고는 여전히 차단 방식입니다: 보고가 실패하면
전송도 실패합니다. 보고가 실패해도 되게 하면, 홀더가 자신의 전송에서 보고에 가스를 모자라게 주어
기록이 그 움직임을 건너뛰게 하고 자신의 에어드랍 몫을 키울 수 있을 것입니다. 조치 수단은 즉시
작동하며, 각각 트랜잭션 하나입니다: 토큰의 기록을 멈추는 토큰의 setRecorder(0), 또는 기록기를
제자리에서 업그레이드하는 것. 에어드랍을 참고하십시오.
오프체인 패키지
| 패키지 | 역할 |
|---|---|
shared/ |
생성된 ABI, 프로토콜 상수, 포매팅. 나머지 모든 패키지가 공유하는 단일 소스 |
backend/ |
가격 서비스. 디앱의 유일한 외부 의존성을 격리 |
keeper/ |
전환, 브리지 배치, 원격 라우팅, 전달 모니터링, 그리고 2026-10-05부터는 에어드랍 단계, 훅과 락이 수령인을 위해 보관하는 것의 지급, LP 수수료 수취 |
cairn-app/ |
앱, 즉 2026-10-02에 React + Vite 디앱을 대체한 뒤로 projet/ 옆에 있는 Next.js 프런트엔드: 디앱 참고 |
cairn-worker/ |
앱의 데이터 에지: 열려 있는 모든 페이지를 위해 프로토콜을 한 번 읽고 변경 사항을 푸시 |