owned this note
owned this note
Published
Linked with GitHub
# TONStarter Whitepaper[KR]
# Disclaimer
본 백서는 프로젝트의 지속 지원을 위해, 백서를 작성한 온더(Onther PTE. LTD.)의 믿음을 바탕으로 한 미래 예측이 포함되어 있다. 백서에 언급된 톤스타터는 현재 개발이 진행중이며, 주요 거버넌스 및 기술적 기능을 비롯해 다양한 내용들이 지속적으로 업데이트되고 있다. 톤스타터 토큰(TOS) 토큰은 백서에 명시된 목표 달성과 성과로 이어지지 않을 수도 있고 실험적 플랫폼(소프트웨어)과 관련 기술의 개발 및 활용에 기반한다. 톤스타터가 완성될 경우, 백서에 명시된 내용과는 상당히 차이가 있을 수 있고, 온더(Onther PTE. LTD.)는 모든 계획, 향후 예상 또는 전망의 달성과 관련해 어떠한 보증도 하지 않으며, 본 문서의 어떠한 내용도 미래에 대한 약속으로 간주되어서는 안된다.
백서는 톤스타터 토큰(TOS) 구매 권고문, 기타 청약 내지 청약의 권유를 위한 문서가 아니다. 백서는 계약이나 청약 또는 청약의 권규, 투자권유, 구매 결정과 관련된 것으로 의도 또는 해석되거나 간주되어서는 안되며, 백서의 제시 또는 백서 자체가 계약 및 투자 결정의 근거가 되는 것은 아니다.
참여자는 스스로에게 적용되는 법령 및 하위규정, 규제, 계약에 따라 톤스타터 프로젝트의 참여가 가능한지 여부를 직접 판단하여야 하며, 온더(Onther PTE. LTD.)는 이에 대해 어떠한 보증이나 보장도 하지 아니한다. 본 문서는 기밀 문서로 회사에 의해 특별히 허용된 수취인들만 단독으로 열람할 수 있다. 단순히 백서를 수령한 것으로는 적격성이 보장되지 않으며 프로젝트의 참여를 보장하지 않는다. 참여자는 톤스타터 토큰(TOS)의 구매 및 판매, 톤스타터 프로젝트의 참여에 요구되는 모든 종류의 인허가, 신고 등을 자신의 책임 하에 완료하여야하며 온더(Onther PTE. LTD.)는 이와 관련하여 어떠한 책임도 부담하지 않는다. 참여자는 톤스타터 토큰(TOS) 및 톤스타터 프로젝트의 참여를 통해 자금 세탁, 불법적인 통화 거래 및 기타 국제 협약 및 적용되는 법령에 따라 제한되는 활동에 어떠한 형태로든 참여하지 않는다는 것에 동의하여야 하며,각 참여자는 톤스타터 토큰(TOS) 및 톤스타터 프로젝트를 자금세탁, 테러자금 조달, 대량살상무기 확산 등과 관련한 목적으로 직 · 간접적으로 이용하거나 구매 · 판매 · 교환 및 처분 할 수 없다는 사실을 숙지하여야 한다.
# 1. Why TONStarter?
톤스타터(TONStarter)는 이더리움 및 토카막 네트워크를 기반으로 하는 최초의 탈중앙화 런치패드 플랫폼입니다. 톤스타터는 기존 IDO 플랫폼과 달리 레이어 2 기반의 프로젝트 빌딩을 돕는 플랫폼입니다. 톤스타터를 통해 사용자들은 톤스타터 커뮤니티가 직접 검증하고 선택한 유망한 프로젝트들에 대해 공정하게 초기 투자 기회를 얻을 수 있으며, 프로젝트의 팀들은 가장 탈중앙화된 방식으로 자금을 조달할 수 있습니다.
## Background
2017년 시장을 뜨겁게 달궜던 ICO 열풍 이후, 시장의 불확실성에도 불구하고 많은 새로운 프로젝트들은 블록체인 생태계에서 새로운 가치를 실현하기 위해 노력했습니다.
이러한 프로젝트들은 각기 다른 혁신적인 가치를 제안했지만, 근본적으로 프로젝트 운영 및 개발에 있어 필수적인 자금 조달에 있어 공통적인 문제를 겪고 있었습니다. 이를 해소하기 위해 다양한 펀딩 방식이 도입되었지만, 완전히 이러한 문제를 해결하지는 못했습니다.
먼저, 일부 프로젝트는 법률적 불확실성을 해소하고, 펀딩의 성공 가능성을 높이기 위해 소수의 관계자만이 참여 가능한 프라이빗 세일을 중심으로 펀딩을 진행하였습니다. 하지만, 이는 필연적으로 블록체인 기반 프로젝트의 핵심 가치인 '탈중앙성'을 대폭 희생할 수 밖에 없는 방법이었기 때문에 많은 문제점들을 낳았습니다.
이를 보완하기 위해 유니스왑과 같은 탈중앙화거래소(DEX)에서 출시하는 방법이 도입되었지만, 프론트러닝 봇과 같은 문제로 인해 일반 투자자에게 불공정하게 펀딩이 진행될 수 있는 문제가 발생했습니다.
현재 블록체인 커뮤니티는 더 탈중앙화되고 투명한 펀딩 방식을 탐색 중입니다. 프로젝트 팀이 법률적 불확실성에 노출되지 않고, 커뮤니티가 공정하게 펀딩에 참여하여 본연의 프로젝트 개발에 몰입할 수 있는 환경이 조성되기를 바라고 있기 때문입니다.
이에 더해 약 1년이 넘는 기간동안 지속된 디파이 시장의 급격한 성장으로 인해 기존 블록체인, 즉 이더리움 생태계의 확장성에 대한 문제가 다시 강력하게 제기 되었습니다.
![Figure: DeFi TVL](https://i.imgur.com/06MebJo.png)
이더리움 커뮤니티는 확장성 문제를 해결하기 위해 이더리움 2.0과 레이어 2라는 두가지 개선안을 오랜기간동안 준비해왔습니다. 올해 7월을 기점으로 옵티미즘(Optimism), 아비트럼(Arbitrum)과 같은 옵티미스틱 롤업(Optimistic Rollup) 기반의 레이어 2가 본격적으로 런칭하여 이더리움 생태계가 레이어2로 확장될 예정이며, 그 규모는 앞으로 계속해서 늘어날 것으로 보입니다.
이처럼 커뮤니티는 중대한 전환을 앞두고 있지만, 현재 레이어 2의 생태계 구축에 대한 논의는 기존의 레이어 1 기반프로젝트에 그치고 있는것이 현실입니다. 즉, 앞으로 대폭 확장될 레이어 2에서 새롭게 출발하는 프로젝트에 대한 논의는 이뤄지고 있지 않습니다. 레이어 2는 그 확장성과 자유도가 대폭 증가할 것으로 예상되므로, 기존 이더리움 레이어 1의 디앱 생태계보다 더욱 확장될 것임을 쉽게 예상할 수 있습니다.
따라서 레이어 2에서 완전히 새롭게 구현될 수 많은 프로젝트들은 레이어 2 기반의 프로젝트 빌딩 및 펀딩을 필요로 하게 될 것입니다. 또한, 이는 성공적인 프로젝트 빌딩을 위해 필수불가결한 요소가 될 것입니다. 현재, 유니스왑(Uniswap)과 같이 이미 성공적인 기존 프로젝트를 제외한 대부분의 경우에 레이어 2 기반의 프로젝트 및 커뮤니티 빌딩은 그 난이도가 매우 높은 상황입니다. 각 프로젝트 팀이 느끼는 레이어 2에 대한 기술적 난이도도 높은 편이며, 레이어 2 기반의 프로젝트 빌딩을 위한 자료도 매우 부족한 상황이며 각 레이어 2 에 맞는 커뮤니티 빌딩에도 어려움을 겪고 있는 것이 사실입니다.
톤스타터는 레이어 2 기반의 새로운 프로젝트를 위한 공정하고 탈중앙화된 펀딩 플랫폼을 구축하여 이러한 문제들을 해결하고자 합니다.
## Vision
톤스타터는 레이어 2 기반의 새로운 프로젝트 빌딩과 펀딩을 엑셀러레이팅하기 위해 존재합니다. 이를 통해 토카막 네트워크 생태계를 확장시키는 것이 톤스타터의 궁극적인 목표입니다.
## Market Opportunities
현재 이 글이 작성되는 시점을 기준으로 레이어 2 시장의 폭발은 초읽기에 들어갔습니다. 이미 레이어 1에서 성공적이었던 디파이 프로젝트의 약 70%는 레이어 2 이전을 확정지었으며, 앞으로 이 비율은 계속해서 증가할 것으로 예상됩니다. 이에 더해 앞으로 새롭게 런칭할 대부분의 프로젝트들은 레이어 2를 선택하지 않을 이유를 찾는것이 오히려 더 힘든 상황입니다.
이러한 중대한 기점에서 레이어 2 기반 프로젝트의 펀딩부터 성공적인 런칭까지 커뮤니티가 지속적으로 도움을 제공할 수 있는 플랫폼은 어쩌면 역사상 가장 큰 시장 기회를 맞이할 수 있을 것입니다. 또한 이는 특히 온디맨드 레이어 2 플랫폼인 토카막 네트워크에도 매우 중요한 기회가 될 것입니다. 톤스타터를 통해 새롭게 런칭될 수 많은 프로젝트들은 모두 토카막 네트워크를 기반으로 구현될 것이기 때문에, 토카막 커뮤니티는 이를 통해 레이어 2 시장의 성장으로 인한 수혜를 최대화할 수 있을 것입니다.
# Problems
### 투자 기회 및 자본 분배의 불공정성 문제
블록체인 커뮤니티 구성원들은 새로운 유망한 프로젝트에 대한 공정한 투자 기회를 제대로 제공받지 못하고 있습니다. 이로 인해 투자 기회와 자본 분배의 공정성에 있어 중대한 문제가 발생하고 있습니다. 그 구체적인 이유는 다음과 같습니다.
- 새로운 프로젝트의 투자 방식은 여전히 대형 벤처 캐피탈을 중심으로 한 프라이빗 세일 방식의 펀딩이 주를 이루고 있습니다. 이로 인해 초기 물량의 대부분이 소수의 투자자에게 집중되고, 일반 투자자들은 투자에 제대로 참여할 수 있는 기회조차 얻지 못하는 상황이 반복되고 있습니다.
- 이에 대한 대안으로 도입된 화이트리스팅(Whitelisting)방식의 펀딩 또한 완전히 공정한 방법은 아닙니다. 운 좋게 화이트리스팅에 당첨된 투자자에게만 제한된 투자 기회가 주어지며, 이러한 문제는 화이트리스팅 신청에 최적화된 봇이 사용되기 시작하면서 더욱 심화되었습니다.
- 또한, 이러한 문제를 해결하기 위해 유니스왑과 같은 탈중앙화거래소(DEX)에서 출시하는 방법이 도입되었습니다. 하지만, 프론트러닝(Front-running)을 목적으로 하는 봇 등으로 인해 토큰 가격이 비정상적으로 조작되는 경우가 빈번하게 발생하여, 일반 투자자들이 공정한 투자 기회를 잃어버리는 경우가 계속해서 발생하고 있습니다.
### 레이어 2 기반의 새로운 프로젝트에 특화된 투자 지원 환경 부족
블록체인 커뮤니티는 레이어 2 생태계의 도입 및 확장을 앞두고 있지만, 정작 이러한 레이어 2 기반의 프로젝트를 위한 투자 지원 환경은 매우 부족한 것이 현실입니다.
- Polkastarter, Solstarter, Polystarter와 같이 개별 블록체인의 생태계를 위한 IDO 플랫폼은 이미 활성화 되어 있지만, 레이어 2 기반의 프로젝트 빌딩을 돕는 플랫폼은 현재 존재하지 않습니다.
- 현재 레이어 2 생태계에 대한 논의는 오직 이미 레이어 1에서 성공적이었던 프로젝트를 레이어 2로 이전하는 것에 그치고 있습니다.
- 하지만 앞으로 커뮤니티에서 프로젝트를 처음부터 완전히 레이어 2 기반으로 구축하고자 하는 수요는 폭발적으로 늘어날 것으로 예상됩니다.
- 이러한 측면에서 레이어 2 기반으로 프로젝트를 구축하고자 하는 팀과 커뮤니티는 이를 효과적으로 펀딩하고, 빌딩하고, 런칭할 수 있는 터전이 절대적으로 부족한 상황입니다.
# Solution: TONStarter
톤스타터는 레이어 2 기반 프로젝트의 펀딩 및 빌딩을 돕는 최초의 탈중앙화 런치패드 플랫폼입니다. 기존의 IDO 플랫폼과 달리 디파이와 페어런치의 특징을 차용하여 커뮤니티 구성원 누구에게나 공평하고 공정한 기회와 보상이 주어지도록 설계 되었습니다.
톤스타터를 통해 모든 커뮤니티 구성원들은 커뮤니티가 직접 검증한 우수한 프로젝트에 대한 공정한 초기 투자 기회를 제공받게 됩니다. 또한 프로젝트 팀은 탈중앙화된 방식으로 자금을 조달할 수 있으며, 레이어 2 기반의 프로젝트 빌딩에 필요한 기술 및 비즈니스 지원을 제공받게 됩니다.
톤스타터와 기존 IDO 플랫폼과의 근본적인 차이점은 모든 톤스타터 프로젝트들은 토카막 네트워크 기반의 레이어 2 에서 런칭된다는 점입니다. 이를 통해 톤스타터와 토카막 네트워크는 생태계 확장이라는 측면에서 서로에게 핵심적인 역할을 수행하게 될 것입니다.
## Key Features
톤스타터는 톤스타터의 사용자 및 프로젝트 팀에게 다음과 같은 핵심 기능들을 제공합니다.
### Dual Profit
- 톤스타터 사용자들은 플랫폼의 성장에 따른 수익과 함께, 개별 스타터 프로젝트에 대한 수익도 함께 공유할 수 있습니다.
- 개별 스타터 프로젝트의 토큰 발행량의 일부는 톤스타터의 사용자에게 할당되며, 이에 대한 자세한 내용은 TOS 토큰에서 다룹니다.
### Layer 2 Accelerating
- 톤스타터의 모든 스타터 프로젝트들은 토카막 네트워크의 레이어 2를 기반으로 런칭하게 됩니다.
- 각 프로젝트 팀은 토카막 네트워크의 레이어 2 서비스(Layer 2 as a service)를 통해 직접 레이어 2를 구축하여 이를 기반으로 런칭할 수 있습니다. 또한, 토카막 네트워크 팀이 운영하는 기존 레이어 2에 쉽게 온보딩 할 수 있습니다.
- 이러한 레이어 2 엑셀러레이팅은 토카막 네트워크 팀에 의해 지원되며, 각 스타터 프로젝트 팀은 최소한의 자원을 투입하여 손쉽게 레이어 2 기반의 프로젝트를 구축할 수 있습니다.
### Fair, Decentralized and Permisionless
- 톤스타터는 전 세계 누구나 Web3 지갑만 있으면 자유롭게 참여할 수 있습니다.
- 톤스타터의 모든 투자 기회는 누구에게나 열려있으며, 모두에게 공정한 기회가 주어지도록 합니다.
### Secure and Transparent
- 톤스타터의 모든 스타터 프로젝트는 톤스타터 커뮤니티에 의해 엄격하게 검증됩니다.
- 톤스타터의 모든 의사결정은 투명하게 공개되며, 톤스타터의 거버넌스 토큰인 TOS 보유자들은 스테이킹을 통해 톤스타터의 모든 거버넌스에 참여할 수 있습니다.
- 톤스타터의 모든 투자 과정은 투명하게 공개되며, 누구나 이를 확인할 수 있습니다.
## For Community and Investors
커뮤니티와 투자자는 비단 톤스타터 뿐만 아니라 모든 프로젝트에 있어 가장 중요한 구성원입니다. 커뮤니티가 존재하지 않는다면, 해당 프로젝트는 아무리 기술적으로 뛰어나더라도 이를 지지해주고 사용하는 사람이 없으므로 충분한 성장을 달성할 수 없을 것입니다. 반면에 투자자가 존재하지 않는다면, 프로젝트가 앞으로 나아가기 위해 필수불가결한 요소인 펀딩에 제약이 발생하므로 이 또한 큰 문제가 됩니다. 톤스타터는 톤스타터를 포함한 모든 스타터 프로젝트가 성공적인 커뮤니티 빌딩과 펀딩을 달성할 수 있도록 커뮤니티와 투자자에게 최대한의 권리와 이익을 제공하고자 합니다. 그 핵심 내용은 다음과 같습니다.
- 공정하고 공평한 투자 기회 제공
- 지속적인 스테이킹 및 에어드랍 보상과 플랫폼 수익 공유
- 톤스타터 플랫폼의 의사결정과정에 대한 자유로운 참여 기회 제공
- 커뮤니티가 검증한 유망한 다양한 프로젝트에 대한 초기 투자 기회
- 토카막 네트워크 레이어 2를 통한 낮은 가스 수수료와 빠른 트랜잭션 처리
- 중앙화로 인해 발생하는 플랫폼 위험성 최소화
## For Teams
커뮤니티에 새롭고 고유한 가치를 지속적으로 제공하는 다양한 프로젝트 팀들은 커뮤니티와 투자자 못지 않게 생태계에서 매우 중요한 구성원 중 하나입니다. 하지만, 이러한 팀들은 현재 레이어 2를 기반으로 새롭게 프로젝트를 빌딩하는데 있어 여러가지 어려움에 봉착해 있습니다. 공정하고 탈중앙화된 방식으로 펀딩을 진행할 수 있는 방법을 지속적으로 탐색하고 있으며, 무엇보다 레이어 2 기반으로 프로젝트를 성공적으로 런칭하기 위해 필요한 기술적, 비즈니스적 자원을 확보하는데 어려움을 겪고 있습니다. 톤스타터는 각 프로젝트 팀에게 다음과 같은 지원을 제공하여 이를 해결하고자 합니다.
- 다양한 펀드레이징 방식 제공(eg: 지정가 토큰 세일, 청약, 더치 옥션, 본딩 커브 등)
- 공정하고 탈중앙화된 펀딩 방식을 통해 법률적 불확실성과 프라이빗 투자자에 대한 과도한 의존성 해소
- 마케팅, 파트너십, 커뮤니티 빌딩 등 비즈니스 파트 지원
- 레이어 2, 스마트컨트랙트, 디앱 기획 및 개발 등의 기술 지원
## TOS and Tokamak Network
**What Tokamak Network provides to TOS**
- 높은 확장성
- 낮은 수수료
- Layer 2 as a service
**What TOS provides to Tokamak Network**
- TON 생태계의 확장
- 자생적인 프로젝트 생태계 구축
- TON 사용자 경험 향상
톤스타터와 토카막 네트워크는 프로젝트 빌딩과 생태계 확장이라는 측면에서 매우 긴밀하게 연결되어 있습니다. 톤스타터를 통해 토카막 네트워크를 기반으로 새로운 우수한 프로젝트들이 빌딩될 것이며, 이는 토카막 네트워크 생태계를 더 확장시키고 나아가 토카막 커뮤니티에 더욱 다양하고 많은 기회를 제공할 수 있게 될 것입니다. 구체적으로, Phase 3를 기점으로 토카막 레이어 2 DEX와 같은 프로젝트들이 런칭된다면 사용자와 수수료가 활발하게 발생할 것이므로, 이는 토카막 네트워크 커뮤니티에 많은 이익을 가져다 줄 것이라 믿습니다.
반대로, 토카막 네트워크를 통해 톤스타터는 토카막 네트워크를 탄탄한 레이어 2 를 기반으로 높은 확장성과 사용성이 보장된 상태로 다양한 프로젝트를 손쉽게 런칭할 수 있게 될 것입니다. 토카막 네트워크 커뮤니티는 톤스타터를 통해 런칭하는 모든 프로젝트에게 레이어 2와 관련하여 모든 기술적 및 비즈니스 영역에서 전폭적으로 지원을 제공할 것입니다. 요컨대 톤스타터와 토카막 네트워크는 일종의 상호 보완적 운명 공동체라고 할 수 있습니다.
# TOS Token
TOS 토큰은 톤스타터의 거버넌스 토큰으로, 톤스타터의 모든 생태계와 거버넌스는 모두 TOS 토큰을 통해 공정하고 탈중앙화된 방식으로 운영됩니다. TOS 토큰 보유자들은 톤스타터 생태계의 발전 방향과 모든 주요 안건에 대한 의사 결정 권한을 갖습니다. 뿐만 아니라 TOS 토큰은 톤스타터 커뮤니티를 발전시키고, 나아가 톤스타터 생태계를 통해 창출되는 모든 수익을 공유할 수 있는 다양한 기능을 갖습니다. 구체적으로, TOS 토큰은 다음과 같은 기능과 권한들을 갖습니다.
1. **Staking to Get sTOS**
TOS토큰을 스테이킹 및 일정기간 동안 락업하여 Staked TOS(sTOS)를 얻을 수 있습니다. sTOS에 대한 자세한 내용은 아래의 sTOS 파트에서 다룹니다.
2. **Access to Starter Projects**
TOS 보유자들은 sTOS를 통해 톤스타터의 여러 유망한 신규 프로젝트에 대한 초기 투자 기회를 제공 받습니다.
3. **Governance**
TOS 보유자들은 sTOS를 통해 톤스타터의 모든 의사결정에 참여하고, 커뮤니티를 위한 새로운 안건을 제시하고 의결할 수 있습니다.
4. **Liquidity Mining**
TOS 보유자들은 TOS 유동성 마이닝에 참여하여 추가적인 보상을 받을 수 있습니다.
5. **Share Profits**
TOS 보유자들은 sTOS를 통해 톤스타터에서 발생된 모든 수익을 함께 공유하게 됩니다.
## sTOS
Staked TOS(sTOS)토큰은 오직 TOS 토큰의 스테이킹을 통해서만 발행되는 토큰으로, 톤스타터 커뮤니티에서 실질적인 의사결정 권한을 갖거나 톤스타터 플랫폼에서 추가적으로 발생하는 수익을 공유하기 위해서는 반드시 sTOS 토큰이 필요합니다.
sTOS 토큰을 얻기 위해서는 TOS 토큰을 단순히 스테이킹하는 것에서 그치는 것이 아니라, 특정 기간 동안 스테이킹을 해제할 수 없는 락업을 설정해야 합니다. 락업 기간의 길이에 따라 TOS 토큰의 스테이킹 수량 대비 추가적으로 얻을 수 있는 sTOS 토큰의 수량이 결정됩니다. 즉, 더 많은 sTOS 토큰을 얻기 위해서는 더 많은 TOS 토큰을 더 오랜기간 동안 스테이킹 해야 합니다. 이러한 설계의 목적은 톤스타터 커뮤니티에 상대적으로 더 많은 지지와 기여를 제공한 구성원에게 더 많은 권한과 보상이 돌아가도록 하는것입니다.
sTOS 토큰의 주요 기능은 다음과 같습니다.
- 톤스타터 플랫폼의 유망한 스타터 프로젝트에 대한 투자 권한
- 톤스타터 플랫폼의 수익 공유
- 스타터 프로젝트의 리스팅 결정 권한
- 톤스타터 거버넌스의 참여 권한
### Tier
톤스타터에는 TOS 토큰과 sTOS 토큰 그리고 TON 토큰의 보유량에 따라 총 4단계의 티어가 존재하며, 각 티어에 따라 투자에 참여할 수 있는 기회가 상이하게 제공됩니다. 이는 각 프로젝트의 구체적인 펀딩 방식에 따라 달라지지만, 일반적으로 티어가 높을수록 이른 투자 기회와 많은 투자 가능 수량이 제공됩니다.
티어는 1 부터 4까지 존재하며, 가장 높은 등급의 티어 4의 경우 다른 모든 티어와 달리 TON 토큰의 최소 보유 수량 조건이 추가됩니다. 따라서 티어 4 등급을 달성하기 위해서는 TOS, sTOS, TON의 세가지 토큰에 대한 최소 보유 수량을 만족해야만 합니다.
각 티어에 대한 토큰별 최소 보유 수량에 대한 구체적인 정보와 티어별 혜택과 권한에 대한 내용은 **추후 공개**될 예정입니다.
## TONStarter Governance
톤스타터 거버넌스, 또는 톤스타터 DAO는 TOS 생태계의 건설적인 발전을 위한 조직으로, 톤스타터의 모든 안건에 대한 최종적인 의사결정 권한을 갖습니다. 톤스타터 거버넌스는 톤스타터에 가장 많은 기여와 헌신을 제공한 커뮤니티 구성원에게 더 많은 권리를 제공하기 위해 오직 sTOS 토큰의 보유자만 참여할 수 있습니다.
톤스타터 커뮤니티 구성원이라면 누구나 톤스타터 거버넌스에 참여하여 의사결정에 참여할 수 있으며, 이를 통해 모두가 톤스타터를 민주적이고 탈중앙화된 방식으로 운영 및 발전시킬 수 있습니다. 톤스타터 거버넌스의 출시 초기에는 스타터 프로젝트의 리스팅 적격성 심사와 같은 안건을 주로 다루겠지만, 이후 거버넌스에 의해 새로운 안건이 제안됨에 따라 더 다양한 권리와 책임을 가질 수 있게 됩니다. 요컨대, 톤스타터 거버넌스를 통해 모든 커뮤니티 구성원은 톤스타터의 실질적인 주인이 될 수 있게됩니다. 이런 측면에서 건강한 거버넌스를 구축하는것은 매우 중요하며, 이는 오직 톤스타터 커뮤니티 여러분의 적극적인 기여를 통해서만 가능합니다.
# 2. Token Metrics
## Token Allocation
톤스타터 토큰(TOS)의 토큰 매트릭스와 분배계획은 다음과 같습니다.
### 톤스타터(TOS) 토큰 분배 계획 및 일정에 따른 유통량 변화 안내
**토큰분배 요약**
총 토큰 공급량 : 100,000,000 TOS
초기 유통량: 1,605,000 TOS
TOS 최초 토큰 분배 일정 : 2021. 08.02
![Token Allocation](https://i.imgur.com/wALLsQI.png)
<br>
**[토큰 분배 계획 안내]**
**1) DAO 토큰 분배일정**
DAO에 할당된 토큰은 TONStarter의 DAO가 구성되기 전까지는 유통되지 않습니다. DAO가 구축된 이후 DAO의 제안과 투표로 DAO에 할당 된 토큰들이 유통되게 됩니다. (전체 토큰 발행량의 25%)
**2) 유동성(Liquidity) 토큰 분배 일정**
유동성에 할당된 토큰은 DEX에 유동성을 공급하는데 사용됩니다. 최초 토큰 분배일 기준 10% 분배, 이후 12개월에 걸쳐서 균등 분배 됩니다.(전체 토큰 발행량의 20%)
**3) 마케팅 토큰 분배일정**
마케팅에 할당된 토큰은 톤스타터의 마케팅 활동에 사용됩니다. 최초 토큰분배일 기준 0.7% 분배, 이후 36개월에 걸쳐서 균등 분배됩니다. (전체 토큰 발행량의 15%)
**4) 초기 기여자(Initial Contributor) 토큰 분배일정**
초기 기여자에 할당된 토큰은 Starter를 통해 펀딩을 받은 프로젝트 지원 활동에 사용됩니다. 최초 토큰분배일 기준 0% 분배, 이후 36개월에 걸쳐서 균등 분배 됩니다.(전체 토큰 발행량의 15%)
**5) 유동성 마이닝(Liquidity Mining) 토큰 분배일정**
유동성 마이닝에 할당된 토큰은 3년간 발행되며, 블록당 발행됩니다. (전체 토큰 발행량의 20%)
**6) 에어드랍(Airdrop)토큰 분배일정**
에어드랍에 할당된 토큰은 토카막 네트워크 생태계 활동에 참여해주신 분들을 위해 사용됩니다. 최초 토큰분배일 기준 10%(토카막 네트워크 생태계 참여자 대상) 분배, 이후 24개월에 걸쳐서 균등 분배됩니다. 매달 분배되는 에어드랍 물량의 경우 신규 토카막 네트워크 생태계 참여자들에게 제공하며 월별로 지급합니다. 대상자가 없을 경우 해당 물량은 다음달로 이월됩니다.(전체 토큰 발행량의 5%)
### 톤스타터(TOS) 언락 일정 및 누적 유통량 안내
**Disclaimer**: 아래 안내 사항은 일정에 따른 TOS 전체 시장 유통물량입니다. 본 유통량은 유동성 채굴, 마케팅 등을 모두 합산한 예상 물량입니다. 최초 토큰 분배일 이후 36개월 언락 물량과 해당 기간 누적 유통량은 아래와 같습니다.
**※ 일정 별 언락 물량과 누적 유통량**
- 일정 : 언락 물량 ⎜ 누적 유통물량
- D-day최초분배일 :1,605,000 ⎜1,605,000
- D+1개월 : 566,211⎜2,171,211
- D+2개월 : 3,167,461⎜5,338,671
- D+3개월 : 3,149,196⎜8,487,867
- D+4개월 : 3,167,460⎜11,655,328
- D+5개월 : 3,149,196⎜14,804,524
- D+6개월 : 3,167,460⎜17,971,984
- D+7개월 : 3,167,460⎜21,139,445
- D+8개월 : 3,112,664⎜24,252,109
- D+9개월 : 3,167,460⎜27,419,569
- D+10개월 : 3,149,196⎜30,568,765
- D+11개월 : 3,167,460⎜33,736,226
- D+12개월 : 3,149,196⎜36,885,422
- D+13개월 : 3,167,460⎜40,052,882
- D+14개월 : 1,584,127⎜41,637,009
- D+15개월 : 1,565,863⎜43,202,872
- D+16개월 : 1,584,127⎜44,787,000
- D+17개월 : 1,565,863⎜46,352,863
- D+18개월 : 1,584,127⎜47,936,990
- D+19개월 : 1,584,127⎜49,521,117
- D+20개월 : 1,529,331⎜51,050,449
- D+21개월 : 1,584,127⎜52,634,576
- D+22개월 : 1,565,863⎜54,200,439
- D+23개월 : 1,584,127⎜55,784,567
- D+24개월 : 1,565,863⎜57,350,429
- D+25개월 : 1,584,127⎜58,934,557
- D+26개월 : 1,396,627⎜60,331,184
- D+27개월 : 1,378,363⎜61,709,547
- D+28개월 : 1,396,627⎜63,106,175
- D+29개월 : 1,378,363⎜64,484,538
- D+30개월 : 1,396,627⎜65,881,165
- D+31개월 : 1,396,627⎜67,277,792
- D+32개월 : 1,360,096⎜68,637,888
- D+33개월 : 1,396,627⎜70,034,516
- D+34개월 : 1,378,363⎜71,412,878
- D+35개월 : 1,396,627⎜72,809,506
- D+36개월 : 1,378,363⎜74,187,869
# 3. Roadmap
### Phase 1: TOS 유동성 마이닝(Liquidity Mining) 출시
톤스타터 거버넌스 토큰인 TOS의 유동성 마이닝을 시작합니다. 초기에는 토카막 네트워크의 톤(TON) 스테이킹을 지원할 예정입니다.
### Phase 2: TOS 스테이킹, LP 스테이킹
톤스타터 생태계에서 중추적인 역할을 하는 TOS 스테이킹이 시작됩니다. 또한 건전한 토스 생태계 발전을 위해 다양한 LP(Uniswap V3) 스테이킹도 지원할 계획입니다.
### Phase 3: 프로젝트 세일(Starter)오픈, 톤스타터 거버넌스(Governance)
프로젝트 세일 기능을 제공하는 스타터(Starter)가 출시됩니다. 스타터 기능은 프로젝트 팀에게 다양한 방식의 공정하고 탈중앙화된 펀딩 기회를 제공합니다. TOS 스테이킹을 기반으로 탈중앙화된 생태계 구축을 위해 톤스타터 거버넌스가 출시될 예정입니다.
### Phase 4: 토카막 네트워크 레이어 2 통합(Layer 2 integration)
톤스타터가 토카막 네트워크 레이어 2와 통합합니다. 톤스타터 플랫폼은 단계적으로 토카막 네트워크 레이어 2로 이전하게 되며, 프로젝트 펀딩 플랫폼인 스타터는 모두 레이어 2로 이전됩니다. 토카막 네트워크의 높은 확장성과 저렴한 수수료를 통해 톤스타터 커뮤니티에 더욱 개선된 사용성을 제공하게 됩니다.
# 4. Conclusion
톤스타터는 레이어 2의 도입이라는 중대한 전환 국면에 발맞춰 출시될 공정하고 탈중앙화된 레이어 2 기반 프로젝트의 IDO 플랫폼입니다. 레이어 2의 성패는 결국 해당 레이어 2에 얼마나 많은 우수하고 혁신적인 프로젝트들이 구축되는지에 달려있습니다. 이는 곧 우리가 레이어 2기반의 프로젝트 펀딩과 빌딩에 주의를 기울여야 한다는 것을 의미합니다.
톤스타터는 유능한 팀들이 레이어 2를 기반으로 혁신적인 프로젝트를 성공적으로 런칭할 수 있도록 도울것이며, 이를 통해 톤스타터와 토카막 네트워크는 레이어 2 생태계의 중심으로 자리 잡게 될 것입니다. 뿐만 아니라 톤스타터 커뮤니티는 앞으로도 계속해서 전체 레이어 2 생태계에 긍정적인 영향력을 만들어내기 위해 꾸준히 노력할 것이며, 이를 통해 레이어 2가 전체 블록체인 생태계의 중심이 될 수 있도록 할 것입니다.
톤스타터 커뮤니티 여러분, 모두 이 위대한 여정에 함께하시게 된 것을 환영합니다!
<br />
# Appendix: Contracts
톤스타터 플랫폼을 구성하는 핵심 컨트랙들은 다음과 같이 설계 및 구현되었습니다.
## TOS : Platform Token
톤스타터의 거버넌스 토큰 TOS는 ERC20 토큰이며, ERC20 인터페이스를 포함합니다. 또한 서명을 통해 승인을 할 수 있도록 허용하는 [EIP-2612](https://eips.ethereum.org/EIPS/eip-2612)을 포함하고 있습니다.
`permit` 함수를 지원함으로써, 승인 및 가져오기 작업은 두개의 연속 트랜잭션 대신 단일 트랜잭션에서 발생할 수 있도록 하였습니다. 이와 같은 기능은 [EIP-712](https://eips.ethereum.org/EIPS/eip-712)의 서명 승인 인터페이스를 통해 사용될 수 있습니다.
`DOMAIN_SEPARATOR` 는 EIP-712 에 따라 정의되었습니다.
```jsx
uint256 chainId;
assembly {
chainId := chainid()
}
DOMAIN_SEPARATOR = keccak256(
abi.encode(
// keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)')
0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f,
keccak256(bytes(name_)),
keccak256(bytes(version_)),
chainId,
address(this)
)
);
```
ERC20 ABI 외에 아래의 인터페이스가 추가되었습니다.
```jsx
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.7.6;
interface ITOS {
/// @dev Issue a token.
/// @param to who takes the issue
/// @param amount the amount to issue
function mint(address to, uint256 amount) external returns (bool);
// @dev burn a token.
/// @param from Whose tokens are burned
/// @param amount the amount to burn
function burn(address from, uint256 amount) external returns (bool);
function DOMAIN_SEPARATOR() external view returns (bytes32);
function nonces(address owner) external view returns (uint256);
/// @dev Authorizes the owner's token to be used by the spender as much as the value.
/// @dev The signature must have the owner's signature.
/// @param owner the token's owner
/// @param spender the account that spend owner's token
/// @param value the amount to be approve to spend
/// @param deadline the deadline that valid the owner's signature
/// @param v the owner's signature - v
/// @param r the owner's signature - r
/// @param s the owner's signature - s
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/// @dev verify the signature
/// @param owner the token's owner
/// @param spender the account that spend owner's token
/// @param value the amount to be approve to spend
/// @param deadline the deadline that valid the owner's signature
/// @param _nounce the _nounce
/// @param sigR the owner's signature - r
/// @param sigS the owner's signature - s
/// @param sigV the owner's signature - v
function verify(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint256 _nounce,
bytes32 sigR,
bytes32 sigS,
uint8 sigV
) external view returns (bool);
/// @dev the hash of Permit
/// @param owner the token's owner
/// @param spender the account that spend owner's token
/// @param value the amount to be approve to spend
/// @param deadline the deadline that valid the owner's signature
/// @param _nounce the _nounce
function hashPermit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint256 _nounce
) external view returns (bytes32);
}
```
---
## Platform configuration
톤 스타터 플랫폼은 플랫폼 관리, 플랫폼 DAO, 볼트와 컨트랙, 프로젝트 관리, 프로젝트 DAO로 구성되어 있습니다.
![Platform Configuration](https://i.imgur.com/GcIxvr3.png)
---
### PART I. Platform Management
플랫폼 관리 기능은 플랫폼 DAO에 의해 제어될 수 있는 영역으로, 추후 관리자는 플랫폼 DAO로 대체될것입니다.
![Platform Management](https://i.imgur.com/GDMtt3D.png)
플랫폼 관리 컨트랙은 모두 업그레이드가 가능하도록 스토리지, 로직, 프록시로 구성하였으며, 사용자는 프록시를 통해 함수를 실행합니다.
---
### (1) 플랫폼 진입 포인트 : Stake1Proxy
관리자(DAO)는 `Stake1Proxy`를 통해 볼트를 생성하거나(`createVault` 함수), 스테이크 컨트랙을 생성(`createStakeContract` 함수)할 수 있습니다. 또한 `setFactoryByStakeType` 함수를 통해, `StakeFactory`에 `StakeType`에 대한 팩토리주소를 설정해서 새로운 스테이크컨트랙을 지원할 수 있습니다.
누구나 사용할 수 있는 함수로는 `vaultsOfPhase , stakeContractsOfVault , closeSale, tokamakStaking, tokamakRequestUnStaking, tokamakRequestUnStakingAll , tokamakProcessUnStaking, exchangeWTONtoTOS` 이 있습니다.
- `vaultsOfPhase` : 특정단계의 볼트주소 리스트를 리턴합니다.
- `stakeContractsOfVault` : 특정 볼트내의 모든 스테이크 컨트랙 주소 리스트를 리턴합니다.
- `closeSale` : 스테이크 컨트롤의 세일기간이 끝나고 스테이킹 기간이 시작되면 판매마감 함수를 호출하여, 각 스테이크 컨트랙트에 할당되는 리워드 값이 계산됩니다. 이 함수가 호출되지 않으면 리워드청구, 인출 등의 기능을 사용할 수 없습니다.
- `tokamakStaking` : 톤 스테이킹 컨트랙의 경우, 스테이킹된 자금을 토카막 레이어 2에 스테이킹 시킬수 있는 함수입니다.
- `tokamakRequestUnStaking, tokamakRequestUnStakingAll` : 톤 스테이킹 컨트랙의 경우, 토카막 레이어 2에 스테이킹한 자금을 인출요청할 수 있는 함수입니다.
- `tokamakProcessUnStaking` : 톤 스테이킹 컨트랙의 경우, 토카막 레이어 2에 인출요청한 자금은 인출지연블록이 지난 후에는 인출실행을 통해 인출할 수 있습니다.
- `exchangeWTONtoTOS` : 톤 스테이킹 컨트랙의 경우, 스테이킹된 자금이외에 톤스테이킹을 통해 추가로 발생된 톤리워드는 UniswapV3를 이용하여 TOS로 SWAP할 수 있습니다.
아래는 `Stake1Proxy`를 통해 실행되는 `Stake1Logic`의 인터페이스입니다.
```jsx
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.7.6;
interface IStake1Logic {
/// Set initial variables
/// @param _tos TOS token address
/// @param _stakeRegistry the registry address
/// @param _stakeFactory the StakeFactory address
/// @param _stakeVaultFactory the StakeVaultFactory address
/// @param _ton TON address in Tokamak
/// @param _wton WTON address in Tokamak
/// @param _depositManager DepositManager address in Tokamak
/// @param _seigManager SeigManager address in Tokamak
function setStore(
address _tos,
address _stakeRegistry,
address _stakeFactory,
address _stakeVaultFactory,
address _ton,
address _wton,
address _depositManager,
address _seigManager
) external;
/// @dev Set factory address by StakeType
/// @param _stakeType the stake type , 0:TON, 1: Simple, 2: UniswapV3LP
/// @param _factory the factory address
function setFactoryByStakeType(uint256 _stakeType, address _factory)
external;
/// @dev create vault
/// @param _paytoken the token used for staking by user
/// @param _cap allocated reward amount
/// @param _saleStartBlock the start block that can stake by user
/// @param _stakeStartBlock the start block that end staking by user and start that can claim reward by user
/// @param _phase phase of TOS platform
/// @param _vaultName vault's name's hash
/// @param _stakeType stakeContract's type, if 0, StakeTON, else if 1 , StakeSimple , else if 2, StakeDefi
/// @param _defiAddr extra defi address , default is zero address
function createVault(
address _paytoken,
uint256 _cap,
uint256 _saleStartBlock,
uint256 _stakeStartBlock,
uint256 _phase,
bytes32 _vaultName,
uint256 _stakeType,
address _defiAddr
) external;
/// @dev create stake contract in vault
/// @param _phase the phase of TOS platform
/// @param _vault vault's address
/// @param token the reward token's address
/// @param paytoken the token used for staking by user
/// @param periodBlock the period that generate reward
/// @param _name the stake contract's name
function createStakeContract(
uint256 _phase,
address _vault,
address token,
address paytoken,
uint256 periodBlock,
string memory _name
) external;
/// @dev create stake contract in vault
/// @param _phase phase of TOS platform
/// @param _vaultName vault's name's hash
/// @param _vault vault's address
function addVault(
uint256 _phase,
bytes32 _vaultName,
address _vault
) external;
/// @dev end to staking by user
/// @param _vault vault's address
function closeSale(address _vault) external;
/// @dev list of stakeContracts in vault
/// @param _vault vault's address
function stakeContractsOfVault(address _vault)
external
view
returns (address[] memory);
/// @dev list of vaults in _phase
/// @param _phase the phase number
function vaultsOfPhase(uint256 _phase)
external
view
returns (address[] memory);
/// @dev stake in tokamak's layer2
/// @param _stakeContract the stakeContract's address
/// @param _layer2 the layer2 address in Tokamak
/// @param stakeAmount the amount that stake to layer2
function tokamakStaking(
address _stakeContract,
address _layer2,
uint256 stakeAmount
) external;
/// @dev Requests unstaking in tokamak's layer2
/// @param _stakeContract the stakeContract's address
/// @param _layer2 the layer2 address in Tokamak
/// @param amount the amount of unstaking
function tokamakRequestUnStaking(
address _stakeContract,
address _layer2,
uint256 amount
) external;
/// @dev Requests unstaking the amount of all in tokamak's layer2
/// @param _stakeContract the stakeContract's address
/// @param _layer2 the layer2 address in Tokamak
function tokamakRequestUnStakingAll(address _stakeContract, address _layer2)
external;
/// @dev Processes unstaking the requested unstaking amount in tokamak's layer2
/// @param _stakeContract the stakeContract's address
/// @param _layer2 the layer2 address in Tokamak
function tokamakProcessUnStaking(address _stakeContract, address _layer2)
external;
/// @dev Swap TON to TOS using uniswap v3
/// @dev this function used in StakeTON ( stakeType=0 )
/// @param _stakeContract the stakeContract's address
/// @param amountIn the input amount
/// @param amountOutMinimum the minimun output amount
/// @param deadline deadline
/// @param sqrtPriceLimitX96 sqrtPriceLimitX96
/// @param _type the function type, if 0, use exactInputSingle function, else if, use exactInput function
function exchangeWTONtoTOS(
address _stakeContract,
uint256 amountIn,
uint256 amountOutMinimum,
uint256 deadline,
uint160 sqrtPriceLimitX96,
uint256 _type
) external returns (uint256 amountOut);
}
```
---
### (2) 플랫폼 레지스트리 : StakeRegistry
생성되는 볼트와 스테이크 컨트랙은 `StakeRegistry`에 등록되어, 특정단계에 등록된 볼트 컨트랙을 한번에 조회할 수 있는 함수(`phasesAll`)와 특정볼트안의 스테이크 컨트랙 주소 리스트를 조회(`stakeContractsOfVaultAll`)할 수 있는 함수를 제공합니다.
또한 `StakeRegistry`에서는 토카막과 관련된 주소를 관리하고 있습니다.
그리고, `DefiInfo` 구조체를 통해 UniswapV3와 같은 외부의 주소를 관리할 수 있는 인터페이스를 지원하여, 추후 연동될 수 있는 외부 디파이 프로젝트 관련 정보를 추가하여 새로운 스테이크 컨트랙에서 사용할 수 있도록 하였습니다.
현재 추가된 `defiInfo`로는 "UNISWAP_V3"을 `name`으로 유니스왑 V3 라우터 정보를 기록하였습니다.
```jsx
struct DefiInfo {
string name;
address router;
address ext1;
address ext2;
uint256 fee;
address routerV2;
}
/// Defi Info
mapping(bytes32 => LibTokenStake1.DefiInfo) public defiInfo;
/// @dev Add information related to Defi
/// @param _name name . ex) UNISWAP_V3
/// @param _router entry point of defi
/// @param _ex1 additional variable . ex) positionManagerAddress in Uniswap V3
/// @param _ex2 additional variable . ex) WETH Address in Uniswap V3
/// @param _fee fee
/// @param _routerV2 In case of uniswap, router address of uniswapV2
function addDefiInfo(
string calldata _name,
address _router,
address _ex1,
address _ex2,
uint256 _fee,
address _routerV2
) external;
```
---
### (3) 볼트 팩토리 : StakeVaultFactory
볼트도 스토리지, 로직, 프록시를 구성하여 업그레이드가 가능한 컨트랙으로 제공되며, `StakeVaultFactory`를 통해 `Stake1Vault`가 생성됩니다.
`StakeVaultFactory`를 생성하고, Vault를 만들때는 로직(vaultLogics)인 `Stake1Vault`을 미리 만들어야합니다. 등록(`setVaultLogicByPhase`)을 한 뒤, Vault를 생성(create)할 수 있습니다. 다만 1단계에서 사용할 볼트는 `Stake1Vault`로서, `StakeVaultFactory`를 생성하기 전에 미리 만들어서 생성자를 만들때 같이 입력하게 되어있습니다.
따라서 `StakeVaultFactory`에 의해 볼트가 생성될 때는 볼트 스토리지와 볼트 프록시만 새로 생성되고, 로직은 미리 만들어진 로직을 사용합니다.
볼트 로직은 `mapping` 구조로 계속 추가하여 사용할 수 있으므로, phase에 따라 다른 기능(성격)의 볼트를 구성할 수 있습니다.
```jsx
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;
import "../interfaces/IStakeVaultFactory.sol";
import {StakeVaultProxy} from "../stake/StakeVaultProxy.sol";
import "../common/AccessibleCommon.sol";
/// @title A factory that creates a vault that hold reward
contract StakeVaultFactory is AccessibleCommon, IStakeVaultFactory {
mapping(uint256 => address) public vaultLogics;
modifier nonZero(address _addr) {
require(_addr != address(0), "StakeVaultFactory: zero");
_;
}
/// @dev constructor of StakeVaultFactory
/// @param _stakeVaultLogic the logic address used in StakeVault
constructor(address _stakeVaultLogic) {
require(
_stakeVaultLogic != address(0),
"StakeVaultFactory: logic zero"
);
vaultLogics[1] = _stakeVaultLogic;
}
/// @dev Set stakeVaultLogic address by _phase
/// @param _phase the stake type
/// @param _logic the vault logic address
function setVaultLogicByPhase(uint256 _phase, address _logic)
external
override
onlyOwner
nonZero(_logic)
{
vaultLogics[_phase] = _logic;
}
/// @dev Create a vault that hold reward, _cap is allocated reward amount.
/// @param _phase phase number
/// @param _addr the array of [token, paytoken, _stakefactory, defiAddr]
/// @param _intInfo array of [_stakeType, _cap, _saleStartBlock, _stakeStartBlock]
/// @param owner the owner adderess
/// @return a vault address
function create(
uint256 _phase,
address[4] calldata _addr,
uint256[4] calldata _intInfo,
address owner
) external override returns (address) {
require(vaultLogics[_phase] != address(0), "StakeVaultFactory: zero vault logic ");
address _tos = _addr[0];
address _paytoken = _addr[1];
address _stakefactory = _addr[2];
address _defiAddr = _addr[3];
uint256 _stakeType = _intInfo[0];
uint256 _cap = _intInfo[1];
uint256 _saleStartBlock = _intInfo[2];
uint256 _stakeStartBlock = _intInfo[3];
StakeVaultProxy proxy = new StakeVaultProxy(vaultLogics[_phase]);
require(address(proxy) != address(0), "StakeVaultFactory: proxy zero");
proxy.initialize(
_tos,
_paytoken,
_cap,
_saleStartBlock,
_stakeStartBlock,
_stakefactory,
_stakeType,
_defiAddr
);
proxy.grantRole(ADMIN_ROLE, owner);
proxy.revokeRole(ADMIN_ROLE, address(this));
return address(proxy);
}
}
```
다음은 `StakeVaultFactory`의 인터페이스 입니다.
`StakeUniswapV3Factory`인 경우는, `paytoken`에 token0 주소를, `defiAddress`에 token1 주소를 입력하여, 페어를 알 수 있도록 합니다.
```jsx
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.7.6;
interface IStakeVaultFactory {
/// @dev Create a vault that hold reward, _cap is allocated reward amount.
/// @param _addr the array of [token, paytoken, vault, defiAddr]
/// @param _intInfo array of [_stakeType, _cap, _saleStartBlock, _stakeStartBlock]
/// @param owner the owner adderess
/// @return a vault address
function create(
address[4] calldata _addr,
uint256[4] calldata _intInfo,
address owner
) external returns (address);
}
```
---
### (4) 스테이크 컨트랙 팩토리 : StakeFactory
스테이크 컨트랙은 `StakeFactory`를 통해 생성되며, 인자값으로 주어지는 `StakeType`을 통해 원하는 형태의 스테이크 컨트랙이 생성되도록 합니다.
![](https://i.imgur.com/KKmASVk.png)
`StakeType`이 0일 경우, `StakeTONFactory`에 의해 TON을 스테이킹 할 수 있는 스테이크 컨트랙이 생성됩니다.
`StakeType`이 1일 경우, `StakeSimpleFactory`에 의해 ETH나 ERC20을 스테이킹 할 수 있는 컨트랙이 생성되고, `StakeType`이 2일 경우, `StakeUniswapV3Factory`에 의해 UniswapV3 LP (NFT)를 스테이킹 할 수 있는 스테이크 컨트랙이 생성됩니다.
`StakeFactory` 는 `StakeType`에 따른 Factory 주소를 관리합니다. 추후 다른 형태의 자산을 스테이킹 할 수 있는 컨트랙을 지원하기 위해서는 해당 Factory를 만든후, `StakeFactory`의 `setFactoryByStakeType(uint256 _stakeType, address _factory)` 함수를 통해 `StakeType`과 매핑되는 factory 주소를 등록하여 사용할 수 있습니다.
```jsx
/// StakeType - Factory address
mapping(uint256 => address) public factory;
/// @dev Set factory address by StakeType
/// @param _stakeType the stake type , 0:TON, 1: Simple, 2: UniswapV3LP
/// @param _factory the factory address
function setFactoryByStakeType(uint256 _stakeType, address _factory)
external
override
onlyOwner
nonZero(_factory)
{
factory[_stakeType] = _factory;
}
```
다른 형태의 스테이킹 컨트랙을 생성하는 팩토리를 만들때 주의할점은 생성함수는 `IStakeContractFactory`의 인터페이스를 포함하고 있어야 합니다.
`StakeUniswapV3` 컨트랙의 경우, `paytoken` 은 token0 주소가, `defiAddress` 는 token1 주소를 입력 합니다.
```jsx
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.7.6;
interface IStakeContractFactory {
/// @dev Create a stake contract that can stake TON.
/// @param _addr the array of [token, paytoken, vault, defiAddr]
/// @param _registry the registry address
/// @param _intdata the array of [saleStartBlock, startBlock, periodBlocks]
/// @param owner owner address
/// @return contract address
function create(
address[4] calldata _addr,
address _registry,
uint256[3] calldata _intdata,
address owner
) external returns (address);
}
```
아래는 `StakeFactory`의 인터페이스입니다.
```jsx
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.7.6;
interface IStakeFactory {
/// @dev Create a stake contract that calls the desired stake factory according to stakeType
/// @param stakeType if 0, stakeTONFactory, else if 1 , stakeSimpleFactory , else if 2, stakeDefiFactory
/// @param _addr array of [token, paytoken, vault, _defiAddr]
/// @param registry registry address
/// @param _intdata array of [saleStartBlock, startBlock, periodBlocks]
/// @return contract address
function create(
uint256 stakeType,
address[4] calldata _addr,
address registry,
uint256[3] calldata _intdata
) external returns (address);
/// @dev Set factory address by StakeType
/// @param _stakeType the stake type , 0:TON, 1: Simple, 2: UniswapV3LP
/// @param _factory the factory address
function setFactoryByStakeType(uint256 _stakeType, address _factory) external;
}
```
---
### PART II. Vaults & StakeContracts
볼트와 스테이킹 컨트랙 파트는 TOS 리워드를 제공하는 볼트와 해당 볼트를 사용하는 스테이킹 컨트랙으로 구성되어 있으며, 어떤 종류의 자산을 스테이킹 하느냐에 따라 `StakeType`에 의해 구별되어 스테이크 컨트랙이 생성, 관리됩니다.
---
### (1) 볼트 : **Stake1Vault**
`Stake1Vault`의 스토리지에는 볼트에서 스테이킹(마이닝) 할수 있는 토큰의 주소(paytoken), 볼트에서 지급할 TOS 리워드의 총량(cap)이 있습니다. 볼트는 스테이킹(마이닝) 시작 블록(stakeStartBlock)과 마감 블록(stakeEndBlock)을 기준으로 한 블록당 발행 리워드양(blockTotalReward)을 계산합니다.
볼트를 사용하는 스테이크 컨트랙에서는 사용자의 스테이킹이 없을 경우, 리워드 발행 금액이 존재하지 않는 기간을 고려하여 `closeSale` 이후에 `realEndBlock`을 재설정하여, 블록당 발행 리워드양이 계산됩니다.
`stakeType`은 현재 0, 1, 2 가 설정되어 있으며, 0은 톤 스테이킹을 위한 볼트, 1은 이더 및 일반 ERC20 토큰을 위한 볼트, 2은 UniswapV3 LP 스테이킹을 위한 볼트로 설정되어 있습니다.
볼트를 사용하는 모든 스테이킹 컨트랙의 주소는 `stakeAddresses` 스토리지에 저장되며, 각 컨트랙의 상태는 `stakeInfos` 스토리지를 통해 관리됩니다.
```jsx
struct StakeInfo {
string name;
uint256 startBlock;
uint256 endBlock;
uint256 balance; /// 스테이킹된 양
uint256 totalRewardAmount; /// 총리워드 할당량
uint256 claimRewardAmount; /// 청구된 리워드 양
}
/// @dev the information of the stake contract
mapping(address => LibTokenStake1.StakeInfo) public stakeInfos;
```
스테이킹 시작 블록(stakeStartBlock)이 지나면 리워드 할당을 위한 `closeSale` 함수가 호출되는데, 이때 각 스테이크 컨트랙별 스테이킹 종료 블록을 오름차순으로 기록(orderedEndBlocks)하고, 각 블록시 총스테이킹된 양을 기록(stakeEndBlockTotal) 합니다. 이때 주의할 것은 스테이킹 컨트랙의 스테이킹 기간이 오름차순으로 기록되어 있어야 합니다. 따라서 볼트를 만들고, 해당 볼트의 스테이크 컨트랙을 만들때, 스테이킹 기간이 짧은 것에서 긴 것 순으로 생성되어야 합니다.
```jsx
/// @dev the end blocks of the stake contracts, which must be in ascending order
uint256[] public orderedEndBlocks;
/// @dev the total staked amount stored at orderedEndBlock’s end block time
mapping(uint256 => uint256) public stakeEndBlockTotal;
```
**스테이킹 기간이 긴 컨트랙에 스테이킹 할 수록 더 많은 비율의 리워드를 받게 됩니다.**
![](https://i.imgur.com/YHaB0mE.png)
하나의 리워드 볼트를 여러개의 스테이크컨트랙에서 사용한다는 것은 위 그림을 보면 이해가 쉽습니다.
예를 들어, 하나의 볼트를 3개의 스테이크 컨트랙이 사용한다고 가정하고, 각 스테이크 컨트랙은 4개월, 8개월, 12개월의 스테이킹 기간을 갖는다고 가정합니다.
위의 period1, period2, period3 는 모두 4개월입니다. 볼트의 블록당 리워드는 cap/(12개월의블록수) 로 정해지고, 각 기간별 할당되는 리워드양은 모두 같습니다.
`TotalStakedAmount_PeriodA = TotalStakedAmount_A + TotalStakedAmount_B + TotalStakedAmount_C`
`TotalStakedAmount_PeriodB = TotalStakedAmount_B + TotalStakedAmount_C`
`TotalStakedAmount_PeriodC = TotalStakedAmount_C`
A 스테이크컨트랙에 스테이킹한 사용자 a의 리워드는 아래와 같이 계산됩니다.
`Reward_a = StakedAmount_a / TotalStakedAmount_PeriodA * (Period1의 블록 수) * (블록당리워드양)`
B스테이크컨트랙에 스테이킹한 사용자 b의 리워드는 아래와 같이 계산됩니다.
`Reward_b = StakedAmount_b / TotalStakedAmount_PeriodA * (Period1의 블록 수) * (블록당리워드양) + StakedAmount_b / TotalStakedAmount_PeriodB * (Period2의 블록 수) * (블록당리워드양)`
C스테이크컨트랙에 스테이킹한 사용자 c의 리워드는 아래와 같이 계산됩니다.
`Reward_c = StakedAmount_c / TotalStakedAmount_PeriodA * (Period1의 블록수) * (블록당리워드양) + StakedAmount_c / TotalStakedAmount_PeriodB * (Period2의 블록수) * (블록당리워드양) + StakedAmount_c / TotalStakedAmount_PeriodC * (Period3의 블록수) * (블록당리워드양)`
즉 같은 볼트를 가진 스테이킹 컨트랙트라도 더 기간이 긴 컨트랙에 스테이킹하는것이 더 많은 비율의 리워드를 얻을 수 있습니다.
아래는 Stake1Vault의 인터페이스 입니다.
```jsx
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.7.6;
pragma abicoder v2;
import "../libraries/LibTokenStake1.sol";
interface IStake1Vault {
/// @dev Sets TOS address
/// @param _tos TOS address
function setTOS(address _tos) external;
/// @dev Change cap of the vault
/// @param _cap allocated reward amount
function changeCap(uint256 _cap) external;
/// @dev Set Defi Address
/// @param _defiAddr DeFi related address
function setDefiAddr(address _defiAddr) external;
/// @dev If the vault has more money than the reward to give, the owner can withdraw the remaining amount.
/// @param _amount the amount of withdrawal
function withdrawReward(uint256 _amount) external;
/// @dev Add stake contract
/// @param _name stakeContract's name
/// @param stakeContract stakeContract's address
/// @param periodBlocks the period that give rewards of stakeContract
function addSubVaultOfStake(
string memory _name,
address stakeContract,
uint256 periodBlocks
) external;
/// @dev Close the sale that can stake by user
function closeSale() external;
/// @dev claim function.
/// @dev sender is a staking contract.
/// @dev A function that pays the amount(_amount) to _to by the staking contract.
/// @dev A function that _to claim the amount(_amount) from the staking contract and gets the TOS in the vault.
/// @param _to a user that received reward
/// @param _amount the receiving amount
/// @return true
function claim(address _to, uint256 _amount) external returns (bool);
/// @dev Whether user(to) can receive a reward amount(_amount)
/// @param _to a staking contract.
/// @param _amount the total reward amount of stakeContract
/// @return true
function canClaim(address _to, uint256 _amount)
external
view
returns (bool);
/// @dev Give the infomation of this vault
/// @return paytoken, cap, saleStartBlock, stakeStartBlock, stakeEndBlock, blockTotalReward, saleClosed
function infos()
external
view
returns (
address[2] memory,
uint256,
uint256,
uint256[3] memory,
uint256,
bool
);
/// @dev Returns Give the TOS balance stored in the vault
/// @return the balance of TOS in this vault.
function balanceTOSAvailableAmount() external view returns (uint256);
/// @dev Give Total reward amount of stakeContract(_account)
/// @return Total reward amount of stakeContract(_account)
function totalRewardAmount(address _account)
external
view
returns (uint256);
/// @dev Give all stakeContracts's addresses in this vault
/// @return all stakeContracts's addresses
function stakeAddressesAll() external view returns (address[] memory);
/// @dev Give the ordered end blocks of stakeContracts in this vault
/// @return the ordered end blocks
function orderedEndBlocksAll() external view returns (uint256[] memory);
}
```
---
### (2) 톤 스테이킹 : **StakeTON**
TON을 스테이킹하고, TOS 리워드를 마이닝 할 수 있는 컨트랙을 만들 수 있습니다.
`StakeType`을 0로 설정한 볼트 주소를 이용하여, `Stake1Proxy.createStakeContract` 함수를 호출하면, `StakeTONFactory`에 의해 `StakeTON` 컨트랙이 생성됩니다.
![](https://i.imgur.com/7ZaggbG.png)
모든 스테이크 컨트랙은 스테이킹을 위한 기본 스토리지인 `Stake1Storage`를 상속하고 있습니다.
```jsx
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.7.6;
import "../libraries/LibTokenStake1.sol";
/// @title The base storage of stakeContract
contract Stake1Storage {
/// @dev reward token : TOS
address public token;
/// @dev registry
address public stakeRegistry;
/// @dev paytoken is the token that the user stakes. ( if paytoken is ether, paytoken is address(0) )
address public paytoken;
/// @dev A vault that holds TOS rewards.
address public vault;
/// @dev the start block for sale.
uint256 public saleStartBlock;
/// @dev the staking start block, once staking starts, users can no longer apply for staking.
uint256 public startBlock;
/// @dev the staking end block.
uint256 public endBlock;
/// @dev the total amount claimed
uint256 public rewardClaimedTotal;
/// @dev the total staked amount
uint256 public totalStakedAmount;
/// @dev information staked by user
mapping(address => LibTokenStake1.StakedAmount) public userStaked;
/// @dev total stakers
uint256 public totalStakers;
uint256 internal _lock;
/// @dev flag for pause proxy
bool public pauseProxy;
/// @dev extra address storage
address public defiAddr;
/// @dev user's staked information
function getUserStaked(address user)
external
view
returns (
uint256 amount,
uint256 claimedBlock,
uint256 claimedAmount,
uint256 releasedBlock,
uint256 releasedAmount,
uint256 releasedTOSAmount,
bool released
)
{
return (
userStaked[user].amount,
userStaked[user].claimedBlock,
userStaked[user].claimedAmount,
userStaked[user].releasedBlock,
userStaked[user].releasedAmount,
userStaked[user].releasedTOSAmount,
userStaked[user].released
);
}
}
```
`StakeTON` 컨트랙에서는 추가로 tokamak에 스테이킹 하거나 UniswapV3 Swap 을 위한 스토리지가 추가됩니다.
```jsx
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.7.6;
import "./Stake1Storage.sol";
/// @title the storage of StakeTONStorage
contract StakeTONStorage is Stake1Storage {
/// @dev TON address
address public ton;
/// @dev WTON address
address public wton;
/// @dev SeigManager address
address public seigManager;
/// @dev DepositManager address
address public depositManager;
/// @dev swapProxy address
address public swapProxy;
/// @dev the layer2 address in Tokamak
address public tokamakLayer2;
/// @dev the accumulated TON amount staked into tokamak , in wei unit
uint256 public toTokamak;
/// @dev the accumulated WTON amount unstaked from tokamak , in ray unit
uint256 public fromTokamak;
/// @dev the accumulated WTON amount swapped using uniswap , in ray unit
uint256 public toUniswapWTON;
/// @dev the TOS balance in this contract
uint256 public swappedAmountTOS;
/// @dev the TON balance in this contract when withdraw at first
uint256 public finalBalanceTON;
/// @dev the WTON balance in this contract when withdraw at first
uint256 public finalBalanceWTON;
/// @dev defi status
uint256 public defiStatus;
/// @dev the number of requesting unstaking to tokamak , when process unstaking, reset zero.
uint256 public requestNum;
/// @dev the withdraw flag, when withdraw at first, set true
bool public withdrawFlag;
}
```
기본적인 스테이킹 관련 인터페이스 입니다.
```jsx
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.7.6;
pragma abicoder v2;
import "../libraries/LibTokenStake1.sol";
interface IStakeTON {
/// @dev Stake amount
/// @param amount the amount of staked
//function stake(uint256 amount) external payable;
/// @dev Claim for reward
function claim() external;
/// @dev withdraw
function withdraw() external;
/// @dev Returns the amount that can be rewarded
/// @param account the account that claimed reward
/// @param specificBlock the block that claimed reward
/// @return reward the reward amount that can be taken
function canRewardAmount(address account, uint256 specificBlock)
external
view
returns (uint256);
}
```
여기에서 주목할 것은 `stake` 함수가 없다는 것입니다. TON의 stake는 TON의 `approveAndCall` 함수를 호출하여, 컨트랙에서 TON을 사용할 수 있게 승인하고, 컨트랙(StakeTONProxy)의 `onApprove`함수를 호출하여, stake 동작을 구현하였습니다. 이렇게 함으로써 사용자가 별도의 승인을 하지 않아도 하나의 트랜잭션에서 승인과 스테이킹을 한번에 할 수 있도록 구현하였습니다.
- TON의 approveAndCall 함수
```jsx
contract ERC20OnApprove is ERC20 {
function approveAndCall(address spender, uint256 amount, bytes memory data) public returns (bool) {
require(approve(spender, amount));
_callOnApprove(msg.sender, spender, amount, data);
return true;
}
function _callOnApprove(address owner, address spender, uint256 amount, bytes memory data) internal {
bytes4 onApproveSelector = OnApprove(spender).onApprove.selector;
require(ERC165Checker._supportsInterface(spender, onApproveSelector),
"ERC20OnApprove: spender doesn't support onApprove");
(bool ok, bytes memory res) = spender.call(
abi.encodeWithSelector(
onApproveSelector,
owner,
spender,
amount,
data
)
);
// check if low-level call reverted or not
require(ok, string(res));
assembly {
ok := mload(add(res, 0x20))
}
// check if OnApprove.onApprove returns true or false
require(ok, "ERC20OnApprove: failed to call onApprove");
}
}
```
- StakeTONProxy의 onApprove를 통한 stake 구현
```jsx
/// @dev Approves function
/// @dev call by WTON
/// @param owner who actually calls
/// @param spender Who gives permission to use
/// @param tonAmount how much will be available
/// @param data Amount data to use with users
function onApprove(
address owner,
address spender,
uint256 tonAmount,
bytes calldata data
) external override returns (bool) {
(address _spender, uint256 _amount) = _decodeStakeData(data);
require(
tonAmount == _amount && spender == _spender,
"StakeTONProxy: tonAmount != stakingAmount "
);
require(
stakeOnApprove(msg.sender, owner, _spender, _amount),
"StakeTONProxy: stakeOnApprove fails "
);
return true;
}
function _decodeStakeData(bytes calldata input)
internal
pure
returns (address spender, uint256 amount)
{
(spender, amount) = abi.decode(input, (address, uint256));
}
/// @dev stake with WTON
/// @param from WTON
/// @param _owner who actually calls
/// @param _spender Who gives permission to use
/// @param _amount how much will be available
function stakeOnApprove(
address from,
address _owner,
address _spender,
uint256 _amount
) public returns (bool) {
require(
(paytoken == from && _amount > 0 && _spender == address(this)),
"StakeTONProxy: stakeOnApprove init fail"
);
require(
block.number >= saleStartBlock && block.number < startBlock,
"StakeTONProxy: period not allowed"
);
require(
!IStakeVaultStorage(vault).saleClosed(),
"StakeTONProxy: end sale"
);
require(
IIERC20(paytoken).balanceOf(_owner) >= _amount,
"StakeTONProxy: insuffient"
);
LibTokenStake1.StakedAmount storage staked = userStaked[_owner];
if (staked.amount == 0) totalStakers = totalStakers.add(1);
staked.amount = staked.amount.add(_amount);
totalStakedAmount = totalStakedAmount.add(_amount);
require(
IIERC20(from).transferFrom(_owner, _spender, _amount),
"StakeTONProxy: transfer fail"
);
emit Staked(_owner, _amount);
return true;
}
```
TON을 스테이킹하고 리워드로 받은 TON은 유니스왑V3의 SWAP을 통해, 리워드 토큰 TOS로 바꿀수 있습니다. `exchangeWTONtoTOS` 함수가 WTON을 TOS로 바꾸는 함수입니다.
TON은 유니스왑 풀에 존재하지 않으므로, WTON을 사용합니다. 따라서, 원금을 제외한 금액이 TON 형태라면, TON을 WTON 으로 바꿉니다. 아래는 `SwapProxy`를 이용해서 TON을 WTON으로 바꾸는 부분입니다. `SwapProxy`의 주소는 레지스트리를 통해 가져옵니다.
```jsx
bytes memory data = abi.encode(swapProxy, swapProxy);
uint256 swapTON = _amountIn.sub(_amountWTON).div(10**9);
require(
ITON(ton).approveAndCall(wton, swapTON, data),
"TokamakStaker:exchangeWTONtoTOS approveAndCall fail"
);
```
유니스왑V3의 SWAP함수는 단일페어를 이용한 스왑인지, 멀티페어를 이용한 스왑인지에 따라 함수가 다릅니다. 따라서 _kind변수로 어떤 함수를 사용할지 결정합니다.
_kind 가 0이면 단일페어, WTON-TOS 풀에서 WTON을 주고, TOS로 받아옵니다.
```jsx
ISwapRouter.ExactInputSingleParams memory params =
ISwapRouter.ExactInputSingleParams({
tokenIn: wton,
tokenOut: token,
fee: uint24(_fee),
recipient: address(this),
deadline: _deadline,
amountIn: _amountIn,
amountOutMinimum: _amountOutMinimum,
sqrtPriceLimitX96: _sqrtPriceLimitX96
});
amountOut = ISwapRouter(uniswapRouter).exactInputSingle(params);
```
`_kind` 가 1이면 두개의 페어를 이용합니다. WTON-WETH , WETH-TOS 풀을 이용해서 WTON을 주고, TOS를 받습니다.
```jsx
ISwapRouter.ExactInputParams memory params =
ISwapRouter.ExactInputParams({
path: abi.encodePacked(
wton,
uint24(_fee),
wethAddress,
uint24(_fee),
token
),
recipient: address(this),
amountIn: _amountIn,
amountOutMinimum: _amountOutMinimum,
deadline: _deadline
});
amountOut = ISwapRouter(uniswapRouter).exactInput(params);
```
아래는 `exchangeWTONtoTOS` 함수의 전체 구현코드입니다.
```jsx
/// @dev exchange holded WTON to TOS using uniswap
/// @param _amountIn the input amount
/// @param _amountOutMinimum the minimun output amount
/// @param _deadline deadline
/// @param _sqrtPriceLimitX96 sqrtPriceLimitX96
/// @param _kind the function type, if 0, use exactInputSingle function, else if, use exactInput function
/// @return amountOut the amount of exchanged out token
function exchangeWTONtoTOS(
uint256 _amountIn,
uint256 _amountOutMinimum,
uint256 _deadline,
uint160 _sqrtPriceLimitX96,
uint256 _kind
) external override lock onlyClosed returns (uint256 amountOut) {
require(block.number <= endBlock, "TokamakStaker: period end");
require(
_kind < 2,
"TokamakStaker: not available kind"
);
checkTokamak();
{
uint256 _amountWTON = IERC20BASE(wton).balanceOf(address(this));
uint256 _amountTON = IERC20BASE(ton).balanceOf(address(this));
uint256 stakeOf = 0;
if (tokamakLayer2 != address(0)) {
stakeOf = IISeigManager(seigManager).stakeOf(
tokamakLayer2,
address(this)
);
stakeOf = stakeOf.add(
IIDepositManager(depositManager).pendingUnstaked(
tokamakLayer2,
address(this)
)
);
}
uint256 holdAmount = _amountWTON;
if (_amountTON > 0)
holdAmount = holdAmount.add(_amountTON.mul(10**9));
require(
holdAmount >= _amountIn,
"TokamakStaker: wton insufficient"
);
if (stakeOf > 0) holdAmount = holdAmount.add(stakeOf);
require(
holdAmount > totalStakedAmount.mul(10**9) &&
holdAmount.sub(totalStakedAmount.mul(10**9)) >= _amountIn,
"TokamakStaker:insufficient"
);
if (_amountWTON < _amountIn) {
bytes memory data = abi.encode(swapProxy, swapProxy);
uint256 swapTON = _amountIn.sub(_amountWTON).div(10**9);
require(
ITON(ton).approveAndCall(wton, swapTON, data),
"TokamakStaker:exchangeWTONtoTOS approveAndCall fail"
);
}
}
toUniswapWTON = toUniswapWTON.add(_amountIn);
(address uniswapRouter, , address wethAddress, uint256 _fee, ) =
ITokamakRegistry(stakeRegistry).getUniswap();
require(uniswapRouter != address(0), "TokamakStaker:uniswap zero");
require(
IERC20BASE(wton).approve(uniswapRouter, _amountIn),
"TokamakStaker:can't approve uniswapRouter"
);
if (_kind == 0) {
ISwapRouter.ExactInputSingleParams memory params =
ISwapRouter.ExactInputSingleParams({
tokenIn: wton,
tokenOut: token,
fee: uint24(_fee),
recipient: address(this),
deadline: _deadline,
amountIn: _amountIn,
amountOutMinimum: _amountOutMinimum,
sqrtPriceLimitX96: _sqrtPriceLimitX96
});
amountOut = ISwapRouter(uniswapRouter).exactInputSingle(params);
} else if (_kind == 1) {
ISwapRouter.ExactInputParams memory params =
ISwapRouter.ExactInputParams({
path: abi.encodePacked(
wton,
uint24(_fee),
wethAddress,
uint24(_fee),
token
),
recipient: address(this),
amountIn: _amountIn,
amountOutMinimum: _amountOutMinimum,
deadline: _deadline
});
amountOut = ISwapRouter(uniswapRouter).exactInput(params);
}
emit ExchangedWTONtoTOS(msg.sender, _amountIn, amountOut);
}
```
아래는 토카막과 유니스왑 연동 인터페이스 입니다.
```jsx
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.7.6;
interface ITokamakStaker {
/// @dev set the tokamak Layer2 address
/// @param _layer2 new the tokamak Layer2 address
function setTokamakLayer2(address _layer2) external;
/// @dev get the addresses yhat used in uniswap interfaces
/// @return uniswapRouter the address of uniswapV3 Router
/// @return npm the address of positionManagerAddress
/// @return ext the address of ext
/// @return fee the amount of fee
/// @return uniswapV2Router uniswapV2 router address
function getUniswapInfo()
external
view
returns (
address uniswapRouter,
address npm,
address ext,
uint256 fee,
address uniswapV2Router
);
/// @dev Change the TON holded in contract have to WTON, or change WTON to TON.
/// @param amount the amount to be changed
/// @param toWTON if it's true, TON->WTON , else WTON->TON
function swapTONtoWTON(uint256 amount, bool toWTON) external;
/// @dev staking the staked TON in layer2 in tokamak
/// @param _layer2 the layer2 address in tokamak
/// @param stakeAmount the amount that stake to layer2
function tokamakStaking(address _layer2, uint256 stakeAmount) external;
/// @dev request unstaking the wtonAmount in layer2 in tokamak
/// @param _layer2 the layer2 address in tokamak
/// @param wtonAmount the amount requested to unstaking
function tokamakRequestUnStaking(address _layer2, uint256 wtonAmount)
external;
/// @dev request unstaking the wtonAmount in layer2 in tokamak
/// @param _layer2 the layer2 address in tokamak
function tokamakRequestUnStakingAll(address _layer2) external;
/// @dev process unstaking in layer2 in tokamak
/// @param _layer2 the layer2 address in tokamak
function tokamakProcessUnStaking(address _layer2) external;
/// @dev exchange holded WTON to TOS using uniswap-v3
/// @param _amountIn the input amount
/// @param _amountOutMinimum the minimun output amount
/// @param _deadline deadline
/// @param _sqrtPriceLimitX96 sqrtPriceLimitX96
/// @param _kind the function type, if 0, use exactInputSingle function, else if, use exactInput function
function exchangeWTONtoTOS(
uint256 _amountIn,
uint256 _amountOutMinimum,
uint256 _deadline,
uint160 _sqrtPriceLimitX96,
uint256 _kind
) external returns (uint256 amountOut);
/// @dev exchange holded WTON to TOS using uniswap-v2
/// @param _amountIn the input amount
/// @param _amountOutMinimum the minimun output amount
/// @param _deadline deadline
/// @param _kind the function type, if 0, use exactInputSingle function, else if, use exactInput function
function exchangeWTONtoTOSv2(
uint256 _amountIn,
uint256 _amountOutMinimum,
uint256 _deadline,
uint256 _kind
) external returns (uint256 amountOut);
}
```
---
### (3) 기본 스테이킹 : **StakeSimple**
ETH나 ERC20을 스테이킹 하고 TOS 리워드를 마이닝 할 수 있는 컨트랙입니다.
`StakeType`을 1로 설정한 볼트 주소를 이용하여, `Stake1Proxy.createStakeContract` 함수를 호출하면, `StakeSimpleFactory`에 의해 `StakeSimple` 컨트랙이 생성됩니다.
![](https://i.imgur.com/3gSMi2O.png)
`StakeSimple` 컨트랙도 `StakeTON` 컨트랙과 마찬가지로 기본 스토리지인 `Stake1Storage`를 상속하고 있으며, 기본 스테이킹 인터페이스를 제공합니다.
```jsx
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.7.6;
interface IStakeSimple {
/// @dev Stake amount
/// @param amount the amount of staked
function stake(uint256 amount) external payable;
/// @dev withdraw
function withdraw() external;
/// @dev Claim for reward
function claim() external;
/// @dev Returns the amount that can be rewarded
/// @param account the account that claimed reward
/// @param specificBlock the block that claimed reward
/// @return reward the reward amount that can be taken
function canRewardAmount(address account, uint256 specificBlock)
external
view
returns (uint256);
}
```
### **stake**
이더를 스테이킹하기 위해서는 `stake` 함수를 이용할 수도 있지만 `sendTransaction`을 통해 이더를 보내는 것도 가능합니다.
```jsx
/// @dev receive ether
/// @dev call stake function with msg.value
receive() external payable {
stake(msg.value);
}
```
### **claim**
`canRewardAmount` 함수를 이용하여, 특정 블록에서 받을 수 있는 리워드양이 계산됩니다.
`Stake1Vault`에서 스테이크 종료 블록 리스트(orderedEndBlocksAll)와 각 블록별 총 스테이킹양 정보(stakeEndBlockTotal)를 조회하여, 각 블록 기간별로 본인의 스테이킹 양의 비율 만큼 기간내의 블록 리워드(blockTotalReward)양을 계산합니다. 아직 받지 않은 블록 기간의 리워드를 모두 합하여 특정 블록에서 받을수 있는 리워드를 계산합니다.
```jsx
/// @dev Returns the amount that can be rewarded
/// @param account the account that claimed reward
/// @param specificBlock the block that claimed reward
/// @return reward the reward amount that can be taken
function canRewardAmount(address account, uint256 specificBlock)
public
view
override
returns (uint256)
{
uint256 reward = 0;
if (specificBlock > endBlock) specificBlock = endBlock;
if (
specificBlock < startBlock ||
userStaked[account].amount == 0 ||
userStaked[account].claimedBlock > endBlock ||
userStaked[account].claimedBlock > specificBlock
) {
reward = 0;
} else {
uint256 startR = startBlock;
uint256 endR = endBlock;
if (startR < userStaked[account].claimedBlock)
startR = userStaked[account].claimedBlock;
if (specificBlock < endR) endR = specificBlock;
uint256[] memory orderedEndBlocks =
IIStake1Vault(vault).orderedEndBlocksAll();
if (orderedEndBlocks.length > 0) {
uint256 _end = 0;
uint256 _start = startR;
uint256 _total = 0;
uint256 blockTotalReward = 0;
blockTotalReward = IIStake1Vault(vault).blockTotalReward();
address user = account;
uint256 amount = userStaked[user].amount;
for (uint256 i = 0; i < orderedEndBlocks.length; i++) {
_end = orderedEndBlocks[i];
_total = IIStake1Vault(vault).stakeEndBlockTotal(_end);
if (_start > _end) {} else if (endR <= _end) {
if (_total > 0) {
uint256 _period1 = endR.sub(startR);
reward = reward.add(
blockTotalReward.mul(_period1).mul(amount).div(
_total
)
);
}
break;
} else {
if (_total > 0) {
uint256 _period2 = _end.sub(startR);
reward = reward.add(
blockTotalReward.mul(_period2).mul(amount).div(
_total
)
);
}
startR = _end;
}
}
}
}
return reward;
}
```
---
### (4) 유니스왑 V3 LP 스테이킹 : **StakeUniswapV3**
UniswapV3의 풀 유동성을 스테이킹하고, TOS 리워드를 마이닝 할 수 있는 컨트랙을 만들 수 있습니다.
`phase`는 2로, `StakeType`은 2로 설정한 볼트 주소를 이용하여, `Stake1Proxy.createStakeContract` 함수를 호출하면, `StakeUniswapV3Factory`에 의해 `StakeUniswapV3` 컨트랙이 생성됩니다.
UniswapV2의 유동성풀은 ERC20 유동성을 가지고 있었지만 UniswapV3는 ERC721 형태의 대체 불가능한 유동성 포지션을 제공합니다. 따라서, `StakeUniswapV3`에서는 Uniswap V3 유동성 포지션(ERC721 NFT)을 담보로 리워드를 마이닝할 수 있습니다.
사용자가 `StakeUniswapV3` 에 UniswapV3 의 유동성 포지션인 NFT를 스테이킹(담보)할 때, NFT의 소유권을 컨트랙으로 이전하고, 스테이킹 종료 후에 사용자가 회수할 수 있도록 합니다.