lido
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Write
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Help
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Write
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    1
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # 📝 Community Staking Module Spec ![image](https://hackmd.io/_uploads/S1G7OO6JR.png) The [Community Staking Landscape](https://hackmd.io/@lido/Byp775Ay6) describes the motivation for the Community Staking Module (CSM) development. A detailed description of the CSM design principles can be found in the [CSM Architecture](https://hackmd.io/@lido/rJMcGj0Ap) document. This document provides a detailed description of the technical implementation of CSM and related pieces of software, which should be considered an extension of the [CSM Architecture](https://hackmd.io/@lido/rJMcGj0Ap). > Terms validator, key, validator key, and deposit data meanings are the same within the document - [Project repo](https://github.com/lidofinance/community-staking-module) - Written in [Solidity 0.8.24](https://github.com/ethereum/solidity/tree/v0.8.24) - Developed in [Foundry](https://github.com/foundry-rs/foundry) ## General Architecture ![image](https://hackmd.io/_uploads/B1KYh2RW0.png) As depicted in the scheme above, CSM is a set of Ethereum smart contracts and off-chain tools, namely: ### Contracts **`CSmodule.sol`** (CSM on the scheme) - a core module contract conforming to the `IStakingModule` interface. Stores information about Node Operators and deposit data (DD). This contract is used as an entry point for the Node Operators. It is responsible for all interactions with the `StakingRouter`, namely, the DD queue management and Node Operator's params. **`CSAccounting.sol`** (Accounting in the scheme) - a supplementary contract responsible for the management of bond, rewards, and penalties. It stores bond tokens in the form of `stETH` shares, provides information about the bond required, and provides interfaces for the penalties. **`CSVerifier.sol`** (Verifier on the scheme) - a utility contract responsible for the validation of the CL data proofs using EIP-4788. It accepts proof of the validator withdrawal and slashing events and reports these facts to the `CSModule.sol` if the proof is valid. **`CSEarlyAdoption.sol`** (EarlyAdoption on the scheme) - a supplementary contract responsible for the Early Adoption members' verification. It validates if the address is eligible to create a Node Operator during the Early Adoption period or if the address is eligible to create a Node Operator with the discounted bond curve once the Early Adoption period is over. It stores information about eligible addresses that have already been used to create a Node Operator. **`CSFeeDistributor.sol`** (FeeDistributor on the scheme) - a supplementary contract that stores non-claimed and non-distributed Node Operator rewards on balance and the latest root of a rewards distribution Merkle tree root. It accepts calls from `CSAccounting.sol` with reward claim requests and stores data about already claimed rewards by the Node Operator. It receives non-distributed rewards from the `CSModule.sol` each time the `StakingRouter` mints the new portion of the Node Operators' rewards. **`CSFeeOracle.sol`** (FeeOracle on the scheme) - a utility contract responsible for the execution of the CSM Oracle report once the consensus is reached in the `HashConsensus.sol` contract, namely, transforming non-distributed rewards to non-claimed rewards stored on the `CSFeeDistributor.sol`, and reporting the latest root of rewards distribution Merkle tree to the `CSFeeDistributor.sol`. Inherited from the `BaseOracle.sol` from LoE. **`HashConsensus.sol`** - a utility contract responsible for reaching consensus between CSM Oracle members. Uses the standard code of the `HashConsensus` contract from the LoE. **`EasyTrack`** - a utility contract responsible for the application of the reported EL stealing penalties. A part of the common `EasyTrack` setup within LoE. **`GateSeal`** - a utility contract responsible for the one-time pause of the `CSModule.sol` and `CSAccounting.sol` contracts to prevent possible exploitation of the module through zero-day vulnerability. Uses the standard code of the `GateSeal` contract from LoE. ### Off-chain tools **`CSM Bot`** - a daemon application responsible for monitoring and reporting the withdrawal and slashing events associated with the CSM validators. **`EL stealing detector`** - a daemon application or EOA responsible for detecting and reporting the EL stealing facts by the CSM validators. Assumed to be EOA controlled by the dev team at the early stages of the MEV monitoring software maturity and later converted to the automated bot to avoid false-positive activations. **`CSM Oracle`** - a module in the common LoE oracle set. Operated by the existing oracles set alongside Accounting Oracle and Validator Exit Bus Oracle. It is responsible for the calculation of the CSM Node Operators' rewards distribution based on their attestation performance on the CL. ## Main flows ### Create Node Operator ![image](https://hackmd.io/_uploads/HJrHTH2JC.png) Node Operator creation is done using `CSModule.sol`. To avoid flooding of the module with empty Node Operators, at least one deposit data and corresponding bond amount is required to create a Node Operator. Prior to Node Operator creation, a required bond amount should be fetched from the `CSAccounting.sol`. Depending on the selected token, this amount should be: - attached as a payment to the transaction (ETH); - approved to be transferred by `CSAccounting.sol` (stETH, wstETH); - included in permit data approving transfers by `CSAccounting.sol` (stETH, wstETH); ### Upload deposit data ![image](https://hackmd.io/_uploads/r1_U6H2kR.png) Node Operators can upload deposit data after Node Operator creation. Prior to uploading, the required bond amount should be fetched from `CSAccounting.sol`, and corresponding approvals, permits, or direct attachments as a payment should be performed the same way as for the [Node Operator creation](#Create-Node-Operator). ### Delete deposit data ![image](https://hackmd.io/_uploads/BJpltIp1C.png) The node operator can request deposit data deletion if it has not been deposited yet. The Node Operator requests deposit data deletion from `CSModule.sol` to do it. `CSModule.sol` validates that deposit data has not yet been deposited. If deletion is possible `CSAccounting.sol` confiscate `deletionFee` from the Node Operator's bond. ### Top-up bond without deposit data upload ![image](https://hackmd.io/_uploads/HJv6hhAW0.png) CSM Node Operators can top-up bond at any time to have excess bond in advance or compensate for the penalties. Top-up is done via `CSModule.sol`. Once funds are transferred to `CSAccounting.sol`, `CSModule.sol` is informed about the bond amount change and corresponding changes in the depositable keys for the Node Operator to account for it. ### Stake allocation To determine the next portion of the validator keys to be deposited, CSM utilizes the FIFO queue. A description of the queue can be found in the [CSM Architecture](https://hackmd.io/gGRgZ0yeTnm-9SSFuHrXwg#Stake-allocation-queue) document and in a [separate spec](https://hackmd.io/@lido/ryw2Qo5ia). #### Basic flow ![image](https://hackmd.io/_uploads/BksD6ShkR.png) Once uploaded, deposit data is placed in the queue. To allocate stake to the CSM Node Operators, `StakingRouter` calls the [`obtainDepositData(depositsCount)`](https://github.com/lidofinance/lido-dao/blob/master/contracts/0.8.9/interfaces/IStakingModule.sol#L140) method to get the next `depositsCount` [depositable keys](https://hackmd.io/gGRgZ0yeTnm-9SSFuHrXwg#Depositable-keys) from the keys queue. #### Invalid keys ![image](https://hackmd.io/_uploads/B1xFpr3J0.png) Due to [optimistic vetting approach](https://hackmd.io/gGRgZ0yeTnm-9SSFuHrXwg#Deposit-data-validation-and-invalidation-aka-vetting-and-unvetting), invalid keys might be present in the queue. DSM is responsible for the detection and reporting of invalid keys through `StakingRouter`. If invalid keys are detected, a call to `decreaseOperatorVettedKeys` is expected from `StakingRouter` to `CSModule.sol`. ### Rewards distribution ![image](https://hackmd.io/_uploads/SJI9aS2JC.png) `StakingRouter` mint rewards for CSM Node Operators on each report of the `AccountingOracle`. `CSModule.sol` bypasses minted rewards to the `CSFeeDistributor.sol`. Once the report slot is reached for the next CSM Oracle report, the rewards distribution tree is [calculated](https://hackmd.io/gGRgZ0yeTnm-9SSFuHrXwg#Performance-Oracle) by each Oracle member. After reaching the quorum, a new Merkle tree root is submitted to the `CSFeeDistributor.sol`, and the corresponding portion of the rewards is transferred from the non-distributed to the non-claimed state. ### Rewards claim ![image](https://hackmd.io/_uploads/S1Jh1SbgA.png) Total rewards for the CSM Node Operators are comprised of [bond rewards and staking fees](https://hackmd.io/gGRgZ0yeTnm-9SSFuHrXwg#%F0%9F%A4%91-Step-2-Rewards). To claim the total rewards, the Node Operator needs to bring proof of the latest `cumulativeFeeShares` in the rewards tree. With that proof `CSAccounting.sol` pulls the Node Operator's portion of the staking fees from the `CSFeeDistributor.sol` and combines it with the Node Operator's bond. After that, all bond funds exceeding the bond required for the currently active keys are available for claim. Node Operator can transfer staking rewards to the bond without transferring it to the reward address by passing `0` as the amount requested for the claim. If there are no new rewards to pull from the `CSFeeDistributor.sol` Node Operator can still claim excess bond using the same flow. ### EL stealing penalty ![image](https://hackmd.io/_uploads/SJ0TI8-gR.png) If the Node Operator commits EL rewards stealing (or violates the [Lido MEV policy](https://research.lido.fi/t/discussion-draft-lido-on-ethereum-block-proposer-rewards-policy-2-0/3132/13)) the fact of stealing and stolen amount are reported to the `CSModule.sol` by the EL stealing detector actor. The corresponding amount of the bond funds is locked by the `CSAccounting.sol`. Node Operator can compensate for the stolen funds and fixed fee voluntarily. If the Node Operator does not compensate for the stolen funds, `EasyTrack` is started to confirm the penalty application. Once enacted, a penalty is applied (locked funds are burned), and the bond curve for the Node Operator is reset to the default one. ### Slashing reporting ![image](https://hackmd.io/_uploads/HyXHgUaJA.png) If one of the CSM validators is slashed, the CSM Bot will report it. The report is submitted to the `CSVerifier.sol` to validate proof against beaconBlockRoot. If the proof is valid, the report is bypassed to the `CSModule.sol`. `CSModule.sol` marks the validator as slashed and requests bond penalization for the Node Operator by `CSAccounting.sol`. ### Withdrawal reporting ![image](https://hackmd.io/_uploads/SkhtbUpJC.png) Once the CSM validator is withdrawn, the CSM Bot will report it. The report is submitted to the `CSVerifier.sol` to validate proof against beaconBlockRoot. If the proof is valid, the report is bypassed to the `CSModule.sol`. `CSModule.sol` marks the validator as withdrawn and requests bond penalization for the Node Operator by `CSAccounting.sol` if the withdrawal balance is lower than 32 ETH. If the withdrawn validator is slashed, the bond curve is reset to the default one for the Node Operator by `CSAccounting.sol`. ## Contracts specifications ### [`CSModule.sol`](https://github.com/lidofinance/community-staking-module/blob/main/docs/src/src/CSModule.sol/contract.CSModule.md) ### [`CSAccounting.sol`](https://github.com/lidofinance/community-staking-module/blob/main/docs/src/src/CSAccounting.sol/contract.CSAccounting.md) ### [`CSVerifier.sol`](https://github.com/lidofinance/community-staking-module/blob/main/docs/src/src/CSVerifier.sol/contract.CSVerifier.md) ### [`CSEarlyAdoption.sol`](https://github.com/lidofinance/community-staking-module/blob/main/docs/src/src/CSEarlyAdoption.sol/contract.CSEarlyAdoption.md) ### [`CSFeeDistributor.sol`](https://github.com/lidofinance/community-staking-module/blob/main/docs/src/src/CSFeeDistributor.sol/contract.CSFeeDistributor.md) ### [`CSFeeOracle.sol`](https://github.com/lidofinance/community-staking-module/blob/main/docs/src/src/CSFeeOracle.sol/contract.CSFeeOracle.md) ## Administrative actions Community Staking Module contracts support a set of administrative actions, including: - Changing the configuration options. - Upgrading the system's code. Each of these actions can only be performed by a designated admin (`DEFAULT_ADMIN_ROLE`) (set by a configuration option). ## Roles to actors mapping ### `CSModule.sol` | Role | Assignee | | ----------------------------------------- | ---------------------- | | `DEFAULT_ADMIN_ROLE` | Aragon Agent | | `PAUSE_ROLE` | Gate Seal contract | | `RESUME_ROLE` | Aragon Agent | | `MODULE_MANAGER_ROLE` | Aragon Agent | | `STAKING_ROUTER_ROLE` | StakingRouter contract | | `REPORT_EL_REWARDS_STEALING_PENALTY_ROLE` | CS Multisig or Bot EOA | | `SETTLE_EL_REWARDS_STEALING_PENALTY_ROLE` | Dedicated EasyTrack | | `VERIFIER_ROLE` | `CSVerifier.sol` | | `RECOVERER_ROLE` | Aragon Agent | ### `CSAccounting.sol` | Role | Assignee | | ----------------------------- | ------------------------------ | | `DEFAULT_ADMIN_ROLE` | Aragon Agent | | `PAUSE_ROLE` | Gate Seal contract | | `RESUME_ROLE` | Aragon Agent | | `ACCOUNTING_MANAGER_ROLE` | Aragon Agent | | `MANAGE_BOND_CURVES_ROLE` | Aragon Agent | | `SET_BOND_CURVE_ROLE` | CS Multisig and `CSModule.sol` | | `RESET_BOND_CURVE_ROLE` | CS Multisig and `CSModule.sol` | | `RECOVERER_ROLE` | Aragon Agent | ### `CSFeeDistributor.sol` | Role | Assignee | | -------------------- | ----------------- | | `DEFAULT_ADMIN_ROLE` | Aragon Agent | | `RECOVERER_ROLE` | Aragon Agent | ### `CSFeeOracle.sol` | Role | Assignee | | -------------------------------- | ----------------------- | | `DEFAULT_ADMIN_ROLE` | Aragon Agent | | `CONTRACT_MANAGER_ROLE` | Aragon Agent | | `SUBMIT_DATA_ROLE` | Not assigned by default | | `PAUSE_ROLE` | GateSeal contract | | `RESUME_ROLE` | Aragon Agent | | `RECOVERER_ROLE` | Aragon Agent | | `MANAGE_CONSENSUS_CONTRACT_ROLE` | Aragon Agent | | `MANAGE_CONSENSUS_VERSION_ROLE` | Aragon Agent | ### `HashConsensus.sol` | Role | Assignee | | -------------------------------- | ------------- | | `DEFAULT_ADMIN_ROLE` | Aragon Agent | | `MANAGE_MEMBERS_AND_QUORUM_ROLE` | Aragon Agent | | `DISABLE_CONSENSUS_ROLE` | Aragon Agent | | `MANAGE_FRAME_CONFIG_ROLE` | Aragon Agent | | `MANAGE_FAST_LANE_CONFIG_ROLE` | Aragon Agent | | `MANAGE_REPORT_PROCESSOR_ROLE` | Aragon Agent | ### `CSEarlyAdoption.sol` Roles are not used for this contract. ### `CSVerifier.sol` Roles are not used for this contract. ## Upgradability `CSmodule.sol`, `CSAccounting.sol`, `CSFeeOracle.sol`, and `CSFeeDistributor.sol` are upgradable using [OssifiableProxy](https://github.com/lidofinance/community-staking-module/blob/main/src/lib/proxy/OssifiableProxy.sol) contracts. `CSVerifier.sol` is not upgradable and should be re-deployed if needed. ## Security considerations ### Bond exposure to negative stETH rebase The bond stored in stETH inevitably inherits all stETH features, including the possibility of a negative rebase. The effective bond amount (counted in ETH) will decrease in case of a negative rebase. This can lead to the case when a relatively large Node Operator might end up with unbonded keys. Also, it might result in an effective bond being lower than the bond required. Hence, Node Operators will lose part of their rewards. ### Malicious Oracles can steal all unclaimed CSM rewards A single updatable Merkle tree approach to the rewards distribution allows malicious Oracles to collude and submit a version of the Merkle tree, indicating that all rewards should be allocated to a single Node Operator (previously created by malicious actors). The worst-case scenario is when all unclaimed rewards stored on the CSM contract will be available for claim by a single Node Operator. ## Known issues ### Permissionless withdrawal reporting vulnerability It is crucial to distinguish partial and full withdrawals to accept permissionless reports about validator withdrawals. For this purpose, the following condition is used (also used by Rocket Pool in [some form](https://github.com/rocket-pool/rocketpool/blob/6a9dbfd85772900bb192aabeb0c9b8d9f6e019d1/contracts/contract/minipool/RocketMinipoolDelegate.sol#L515)): ``` solidity if (!witness.slashed && gweiToWei(witness.amount) < 8 ether) { revert PartialWitdrawal(); } ``` Unfortunately, there is a chance to trick this approach in the following way: - Wait for full validator withdrawal & sweep - Be lucky enough that no one provides proof for this withdrawal for at least 1 sweep cycle (~8 days with the network of 1M active validators) - Deposit 1 ETH for slashed or 8 ETH for non-slashed validator - Wait for a sweep of this deposit - Provide proof of the last withdrawal As a result, the Node Operator's bond will be penalized for 32 ETH - `additional deposit value`. However, all ETH involved, including 1 or 8 ETH deposited by the attacker will remain in the Lido on Ethereum protocol. Hence, the only consequence of the attack is an inconsistency in the bond accounting that can be resolved through the bond deposit approved by the corresponding DAO decision. #### Resolution Given no losses for the protocol, a significant cost of attack (1 or 8 ETH), and lack of feasible ways to mitigate it in the smart contract's code, it is proposed to acknowledge the possibility of the attack and be ready to propose a corresponding vote to the DAO if it will ever happen

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully