From f6ade2e42400aa76a558af033e084950aaaa19b8 Mon Sep 17 00:00:00 2001 From: Shane Earley Date: Mon, 23 Oct 2023 14:24:14 -0400 Subject: [PATCH] Add docs app --- apps/docs/.gitignore | 2 + apps/docs/.vitepress/config.mts | 61 + {common => apps}/docs/README.md | 0 apps/docs/package.json | 20 + apps/docs/scripts/generate.ts | 7 + apps/docs/src/guide/accounts.md | 5 + apps/docs/src/guide/operating.md | 7 + apps/docs/src/guide/staking.md | 7 + apps/docs/src/index.md | 17 + apps/docs/src/introduction/architecture.md | 176 + apps/docs/src/public/casimir.svg | 9 + apps/docs/src/public/favicon.ico | Bin 0 -> 15406 bytes apps/docs/src/reference/.gitignore | 2 + apps/docs/src/reference/contract-addresses.md | 5 + .../src/troubleshooting/operator-issues.md | 5 + .../docs/src/troubleshooting/wallet-issues.md | 5 + apps/docs/templates/solidity/common.hbs | 36 + apps/landing/public/favicon.ico | Bin 0 -> 15406 bytes apps/landing/src/pages/landing/Landing.vue | 4 +- apps/web/public/favicon.ico | Bin 0 -> 15406 bytes common/docs/book.toml | 9 - common/docs/package.json | 12 - common/docs/public/mermaid-init.js | 1 - common/docs/public/mermaid.min.js | 1282 ---- common/docs/scripts/doc.ts | 33 - common/docs/src/README.md | 4 - common/docs/src/SUMMARY.md | 3 - common/events/package.json | 28 +- contracts/ethereum/README.md | 183 - contracts/ethereum/book.toml | 9 - contracts/ethereum/docs/index.md | 6684 ----------------- contracts/ethereum/foundry.toml | 4 - contracts/ethereum/hardhat.config.ts | 11 + contracts/ethereum/package.json | 5 +- contracts/ethereum/scripts/docs.ts | 40 - infrastructure/cdk/package.json | 2 +- .../cdk/src/interfaces/StackProps.ts | 5 + infrastructure/cdk/src/providers/config.ts | 1 + infrastructure/cdk/src/providers/docs.ts | 84 + infrastructure/cdk/test/all.test.ts | 31 +- package-lock.json | 3254 ++++++-- package.json | 4 +- services/blog/package.json | 2 +- services/functions/package.json | 20 +- services/oracle/package.json | 4 +- services/users/package.json | 2 +- 46 files changed, 3082 insertions(+), 9003 deletions(-) create mode 100644 apps/docs/.gitignore create mode 100644 apps/docs/.vitepress/config.mts rename {common => apps}/docs/README.md (100%) create mode 100644 apps/docs/package.json create mode 100644 apps/docs/scripts/generate.ts create mode 100644 apps/docs/src/guide/accounts.md create mode 100644 apps/docs/src/guide/operating.md create mode 100644 apps/docs/src/guide/staking.md create mode 100644 apps/docs/src/index.md create mode 100644 apps/docs/src/introduction/architecture.md create mode 100644 apps/docs/src/public/casimir.svg create mode 100644 apps/docs/src/public/favicon.ico create mode 100644 apps/docs/src/reference/.gitignore create mode 100644 apps/docs/src/reference/contract-addresses.md create mode 100644 apps/docs/src/troubleshooting/operator-issues.md create mode 100644 apps/docs/src/troubleshooting/wallet-issues.md create mode 100644 apps/docs/templates/solidity/common.hbs create mode 100644 apps/landing/public/favicon.ico create mode 100644 apps/web/public/favicon.ico delete mode 100644 common/docs/book.toml delete mode 100644 common/docs/package.json delete mode 100644 common/docs/public/mermaid-init.js delete mode 100644 common/docs/public/mermaid.min.js delete mode 100644 common/docs/scripts/doc.ts delete mode 100644 common/docs/src/README.md delete mode 100644 common/docs/src/SUMMARY.md delete mode 100644 contracts/ethereum/book.toml delete mode 100644 contracts/ethereum/docs/index.md delete mode 100644 contracts/ethereum/scripts/docs.ts create mode 100644 infrastructure/cdk/src/providers/docs.ts diff --git a/apps/docs/.gitignore b/apps/docs/.gitignore new file mode 100644 index 000000000..0c100c4f4 --- /dev/null +++ b/apps/docs/.gitignore @@ -0,0 +1,2 @@ +cache +dist \ No newline at end of file diff --git a/apps/docs/.vitepress/config.mts b/apps/docs/.vitepress/config.mts new file mode 100644 index 000000000..58f968e5f --- /dev/null +++ b/apps/docs/.vitepress/config.mts @@ -0,0 +1,61 @@ +import { withMermaid } from 'vitepress-plugin-mermaid' + +// https://vitepress.dev/reference/site-config +export default withMermaid({ + title: "Casimir Docs", + head: [['link', { rel: 'icon', href: '/favicon.ico' }]], + rewrites: { + 'index.md': 'introduction/what-is-casimir.md', + }, + cleanUrls: true, + markdown: { + math: true + }, + srcDir: 'src', + outDir: './dist', + themeConfig: { + // https://vitepress.dev/reference/default-theme-config + logo: '/casimir.svg', + search: { + provider: 'local' + }, + sidebar: [ + { + text: 'Introduction', + base: '/introduction', + items: [ + { text: 'What is Casimir?', link: '/what-is-casimir' }, + { text: 'Architecture', link: '/architecture' } + ] + }, + { + text: 'Guide', + base: '/guide', + items: [ + { text: 'Accounts', link: '/accounts' }, + { text: 'Staking', link: '/staking' }, + { text: 'Operating', link: '/operating' } + ] + }, + { + text: 'Reference', + base: '/reference', + items: [ + { text: 'Contract Addresses', link: '/contract-addresses' }, + { text: 'Solidity API', link: '/solidity-api' } + ] + }, + { + text: 'Troubleshooting', + base: '/troubleshooting', + items: [ + { text: 'Operator Issues', link: '/operator-issues' }, + { text: 'Wallet Issues', link: '/wallet-issues' } + ] + } + ], + socialLinks: [ + { icon: 'github', link: 'https://github.com/consensusnetworks/casimir' } + ] + } +}) diff --git a/common/docs/README.md b/apps/docs/README.md similarity index 100% rename from common/docs/README.md rename to apps/docs/README.md diff --git a/apps/docs/package.json b/apps/docs/package.json new file mode 100644 index 000000000..4a277b49e --- /dev/null +++ b/apps/docs/package.json @@ -0,0 +1,20 @@ +{ + "name": "@casimir/docs", + "private": "true", + "scripts": { + "dev": "npx vitepress dev", + "build": "npx vitepress build", + "generate": "npx esno -r dotenv/config ./scripts/generate.ts", + "preview": "npx vitepress preview", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "devDependencies": { + "@types/node": "^17.0.38", + "dotenv": "^16.3.1", + "esno": "^0.17.0", + "markdown-it-mathjax3": "^4.3.2", + "mermaid": "^10.5.1", + "vitepress": "^1.0.0-rc.23", + "vitepress-plugin-mermaid": "^2.0.15" + } +} diff --git a/apps/docs/scripts/generate.ts b/apps/docs/scripts/generate.ts new file mode 100644 index 000000000..fcce1dc11 --- /dev/null +++ b/apps/docs/scripts/generate.ts @@ -0,0 +1,7 @@ +import { run } from '@casimir/shell' + +void async function () { + const referenceDir = `${process.cwd()}/src/reference` + const solidityTemplatesDir = `${process.cwd()}/templates/solidity` + await run(`DOCS_OUTPUT_DIR=${referenceDir} DOCS_TEMPLATE_DIR=${solidityTemplatesDir} npm run generate --workspace @casimir/ethereum`) +}() \ No newline at end of file diff --git a/apps/docs/src/guide/accounts.md b/apps/docs/src/guide/accounts.md new file mode 100644 index 000000000..b2cfda29a --- /dev/null +++ b/apps/docs/src/guide/accounts.md @@ -0,0 +1,5 @@ +::: warning +🚧 This page is incomplete. +::: + +# Accounts \ No newline at end of file diff --git a/apps/docs/src/guide/operating.md b/apps/docs/src/guide/operating.md new file mode 100644 index 000000000..eb082a36a --- /dev/null +++ b/apps/docs/src/guide/operating.md @@ -0,0 +1,7 @@ +::: warning +🚧 This page is incomplete. +::: + +# Operating + +Operators owners will need to [set up an SSV node with RockX](https://github.com/consensusnetworks/ssv-dkg) and register with Casimir. The operators page in the Casimir app guides an owner through the process and provides an easy interface for registration and operator management. \ No newline at end of file diff --git a/apps/docs/src/guide/staking.md b/apps/docs/src/guide/staking.md new file mode 100644 index 000000000..d1a3c7fdd --- /dev/null +++ b/apps/docs/src/guide/staking.md @@ -0,0 +1,7 @@ +::: warning +🚧 This page is incomplete. +::: + +# Staking + +Users can deposit any amount of ETH to the manager contract. Their deposits are staked to validators run by SSV operators (see [Operators](#operators)). Rewards are auto-compounded into stake and users can withdraw their principal plus any earned proportion of new stake (or a partial amount of their choice) at any time. \ No newline at end of file diff --git a/apps/docs/src/index.md b/apps/docs/src/index.md new file mode 100644 index 000000000..57a08a27b --- /dev/null +++ b/apps/docs/src/index.md @@ -0,0 +1,17 @@ +::: warning +🚧 This page is incomplete. +::: + +# What is Casimir? + +Casimir is a platform... TODO + +## Casimir Ethereum Staking + +Currlently stakers either need to solo-stake (and have least 32 Ether), or they need to pool their assets in a liquid staking protocol (LSD). While the former choice is a reliably secure choice for Ether holders (if they have solid infrastructure), the latter, LSDs, often present an inherent counterparty risk to the user because of their centralized control of staking node operators (see [The Risks of LSD](https://notes.ethereum.org/@djrtwo/risks-of-lsd)). + +Casimir is designed to offer users the experience and security of solo-staking while pooling their assets. The Casimir contracts seamlessly connect stakers with any amount of Ether to a permissionless registry of high-performing node operators. Casimir aims to minimize counterparty risk for users and improve decentralization in Ethereum staking: + +- Validators duties are performed by registered (collateralized) operators running distributed validator technology (DVT) +- Keys are created and reshared using distributed key generation (DKG) +- Balance and status reports are reported by a decentralized oracle network (DON) \ No newline at end of file diff --git a/apps/docs/src/introduction/architecture.md b/apps/docs/src/introduction/architecture.md new file mode 100644 index 000000000..255888fff --- /dev/null +++ b/apps/docs/src/introduction/architecture.md @@ -0,0 +1,176 @@ +::: warning +🚧 This page is incomplete. +::: + +# Architecture + +Casimir distributes user deposits to Ethereum validators operated by SSV. Validator keys are shared with zero-coordination distributed key generation. Chainlink nodes report from the Beacon chain and SSV to sync balances and rewards, manage collateral recovery, and automate validator creation and exits. + +```mermaid +graph LR + + subgraph Contracts + B(Manager Contract) + C(Beacon Deposit Contract) + D(SSV Contract) + H(Functions Contract) + I(Automation Contract) + end + + subgraph Oracle Dao + G(Oracle) + end + G --> B + + A((User)) --> B + + B --> C + B --> D + + C --> E1(Ethereum Validator 1) + C --> E2(Ethereum Validator 2) + + subgraph Validator 1 + E1 --> F11(SSV Operator 1) + E1 --> F12(SSV Operator 2) + E1 --> F13(SSV Operator 3) + E1 --> F14(SSV Operator 4) + end + + subgraph Validator 2 + E2 --> F21(SSV Operator 5) + E2 --> F22(SSV Operator 6) + E2 --> F23(SSV Operator 7) + E2 --> F24(SSV Operator n) + end + + I --> B + H <--> I + + subgraph Chainlink + J1(Chainlink Node 1) + J2(Chainlink Node 2) + J3(Chainlink Node 3) + J4(Chainlink Node n) + end + + J1 --> H + J2 --> H + J3 --> H + J4 --> H + + J1 --> I + J2 --> I + J3 --> I + J4 --> I +``` + +## Distributed Key Generation + +Casimir distributes validator key shares to operators using SSV nodes with [RockX DKG support](https://github.com/RockX-SG/rockx-dkg-cli). The [@casimir/oracle service](https://github.com/consensusnetworks/casimir/blob/master/services/oracle) uses a DKG messenger server to interact with SSV nodes and perform DKG operations. Before running tests, the [@casimir/oracle generate script](https://github.com/consensusnetworks/casimir/blob/master/services/oracle/scripts/generate.ts) is used to pregenerate DKG keys and the [oracle helper scripts](https://github.com/consensusnetworks/casimir/blob/master/contracts/ethereum/helpers/oracle) completes tests with the pregenerated DKG keys. While running the development environment, a local instance of the @casimir/oracle service is used. + +## Oracles + +The contract uses two oracles to automate the Casimir staking experience and ensure the security of user funds. The automated upkeep contract reports total validator balance, swept balance, and validator actions once per day using [Chainlink Functions](https://docs.chain.link/chainlink-functions) and [Chainlink Automation](https://docs.chain.link/chainlink-automation/introduction). The [@chainlink/functions service](https://github.com/consensusnetworks/casimir/blob/master/services/functions) is used for two request types per report period, balances and details, to overcome the current Chainlink DON constraints. The [Casimir DAO oracle](https://github.com/consensusnetworks/casimir/blob/master/services/oracle) watches the manager contract events and automatically executes zero-coordination distributed key generation (DKG) operations: validator creation, validator resharing, and validator exiting. The DAO oracle also submits verifiable report details in response to reported validator details (such as one or more new exited validators). + +## Users + +Users can deposit any amount of ETH to the manager contract. Their deposits are staked to validators run by SSV operators (see [Operators](#operators)). Rewards are auto-compounded into stake and users can withdraw their principal plus any earned proportion of new stake (or a partial amount of their choice) at any time. + +### User Fees + +The contract charges a user fee on deposits and rewards to cover operational expenses. + +**Fee Distribution Calculation:** + +Let: + +- $F_t$ be the total fee percentage, which is a sum of the required ETH, LINK, and SSV fees. +- $D$ be the amount of ETH deposited by the user. +- $E$ be the amount of ETH to be allocated for the contract's operations. +- $F_a$ be the ETH amount to be swapped for LINK and SSV to facilitate the contract's functions. + +Given the 5% fee, the ETH to be allocated for the contract's operations is calculated as: +$E = D \times \frac{100}{100 + F_t}$ + +The amount to be converted to LINK and SSV is: +$F_a = D - E$ + +Where: + +- $F_t$ typically equals 5%. +- $D$ is the amount of ETH the user wants to deposit. +- $E$ represents the actual ETH amount that will be added to the contract after deducting the fee. +- $F_a$ is the remaining ETH that will be used to acquire LINK and SSV. + +### User Stake + +The manager contract adjusts a user's stake based on the change in the total reward-to-stake ratio sum since their last interaction with the contract. Each time new rewards are reported, the ratio sum is updated to include the new rewards-to-stake ratio. The ratio sum is used to calculate a user's current stake, including compounded rewards, at any time. + +**Current Stake Calculation:** + +Let: + +- $S$ be the calculated current stake of the user, including compounded rewards. +- $S_0$ be the initial stake of the user at the time of their last deposit or stake update. +- $R_s$ be the current cumulative sum of reward-to-stake ratios in the contract. +- $R_{s0}$ be the cumulative sum of reward-to-stake ratios at the time the user made their last deposit or update to their stake. + +The user's current compounded stake at any time is calculated as: +$S = S_0 \times \frac{R_s}{R_{s0}}$ + +Where: + +- $S$ corresponds to **`users[userAddress].stake`** in the contract. +- $S_0$ also corresponds to **`users[userAddress].stake`** in the contract, but it's accessed before settling the user's current stake. +- $R_s$ is represented by **`stakeRatioSum`** in the contract. +- $R_{s0}$ is represented by **`users[userAddress].stakeRatioSum0`** in the contract. + +### User Withdrawals + +Users can request a withdrawal of any amount of their stake at any time. If the requested amount is available in the buffered balance (prepooled balance plus withdrawn balance), the withdrawal is fulfilled immediately. Otherwise, the withdrawal is added to the pending withdrawals queue and fulfilled when the requested amount is available (usually within 1-4 days, depending on the amount). + +## Operators + +Each Casimir validator is run by four selected operators holding the key shares to perform duties with threshold signatures on SSV. Registration is open to any SSV operator (see [Operator Registration](#operator-registration). Operators are selected by an algorithm that ensures high-performance but emphasizes decentralization (see [Operator Selection](#operator-selection)) as user's deposit stake and new validators are required. + +### Operator Registration + +Operators can join the contract registry with a deposit of 4 ETH for collateral (see [Operator Collateral](#operator-collateral)) and a lightweight SSV node config add-on (see [Operator Onboarding](#operator-onboarding)). + +### Operator Selection + +Operators are chosen to run validators based on metrics fetched and derived directly from the SSV network. These metrics are mainly unused collateral (1 ETH per operator per validator), SSV performance, Casimir pool count, and requested fees. + +If an operator owner would like to deregister their operator and free up their collateral, they can request a reshare via the Casimir registry. Casimir removes the operator from existing operator groups by resharing or exiting. The latter is only required in the case that a validator has already undergone more than two reshares to avoid leaving the full key recoverable outside of the currently selected operators. + +### Operator Collateral + +Collateral is used to recover lost validator effective balance at the time of completing an exit. An operator must have at least 1 ETH of available collateral (1 ETH collateral becomes unavailable per each validator that an operator joins) to be selected for a new pool validator. When an operator is removed from a pool, either when resharing or after a completed exit, they are held responsible for up to 1 ETH of the validator's effective balance if any is lost below the 32 ETH minimum. The potential nonzero amount an operator owes in this case is called the blame amount. + +**Blame Amount Calculation:** + +Let: + +- $E$ be the total ETH lost, where $0 \leq E \leq 4$. +- $P_i$ be the performance percentage of the $i^{th}$ operator, where $0 \leq P_i \leq 100$ for $i = 1, 2, 3, 4$. +- $B_i$ be the blame amount for the $i^{th}$ operator. + +If all operators have equal performance, the blame is evenly distributed: +$B_i = \frac{E}{4} \quad \text{for all } i$ + +Otherwise, the blame is distributed inversely proportional to performance: +First, calculate the inverse of each performance: +$I_i = 100 - P_i$ + +Then, the sum of all inverses: +$S = \sum_{i=1}^{4} I_i$ + +Now, the blame for each operator is: +$B_i = \left( \frac{I_i}{S} \right) \times E$ + +The blame amounts are submitted by the DAO oracle in response to a completed validator reshare or exit. + +### Operator Onboarding + +Operators owners will need to [set up an SSV node with RockX](https://github.com/consensusnetworks/ssv-dkg) and register with Casimir. The operators page in the Casimir app guides an owner through the process and provides an easy interface for registration and operator management. \ No newline at end of file diff --git a/apps/docs/src/public/casimir.svg b/apps/docs/src/public/casimir.svg new file mode 100644 index 000000000..19b5fcc26 --- /dev/null +++ b/apps/docs/src/public/casimir.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/docs/src/public/favicon.ico b/apps/docs/src/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..4633ba207d7a2be7060e4c85accc8f8dae56eccb GIT binary patch literal 15406 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 {