diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..62abf6fb --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +# macOS +.DS_Store + +# Text file backups +**/*.rs.bk + +# Build results +target/ + +# IDEs +.vscode/ +.idea/ +*.iml + +# Auto-gen +.cargo-ok + +# Build artifacts +*.wasm +hash.txt +contracts.txt +artifacts/ + +# code coverage +tarpaulin-report.* + +packages/*/schema +contracts/*/schema diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..2e25a708 --- /dev/null +++ b/LICENSE @@ -0,0 +1,23 @@ +Mesh Security: Proof of Stake redesigned for the Interchain Era + +MIT License + +Copyright (c) 2023 Confio GmbH + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..7a87923a --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# Mesh Security + +An implementation of Sunny's [Mesh Security](https://youtu.be/Z2ZBKo9-iRs?t=4937) talk from +Cosmoverse 2022. + +Please check out the [architectural documentation](./docs/README.md), which gives a +thorough view of all the components in a completed system. + +## Status + +This is work towards a production version of **Mesh Security**. As of April 2023, this is very +early stage, not even MVP. The goal is to have something for testnets by July 2023. +Please [see the roadmap](./ROADMAP.md) for more info. + +The original version from HackWasm Medellin can be found at +[CosmWasm/mesh-security-hackathon](https://github.com/CosmWasm/mesh-security-hackathon). +That is actually full-stack end-to-end, but made a lot of design trade-offs for a hackathon. +You can refer to the code as an idea of how the production version will look and if you +learn better from working code than architecture documents. diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 00000000..7377b110 --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,101 @@ +# Development Roadmap + +Created: Apr 17, 2023 +Status: Draft + +## High Level Milestones + +### MVP + +Target Date: June/July 2023 + +The first milestone will be design containing all major features in a solid foundation to build on. +It is designed to be launched on two testnets along with a usable UI, so we can begin getting real +feedback from users and relayers, while building out the more complex features and polishing off some +tougher issues. + +### V1 + +Target date: Early Q4 2023 ?? + +This will include a feature complete version of all code (eg including slashing), but may not have all +extensions (such as remote staking not providing gov voting power). The provider side should +be well reviewed and free from any security holes (safe to deploy on larger chains). The consumer side +(which includes a custom SDK module) will be as solid as possible, but not recommended to run on serious +chains. At this points, Osmosis could provide security to a small market cap chain. + +### V2 + +Target date: Early Q1 2024 ?? + +This will include all optional features we consider feasible in a realistic time frame and most importantly +will have much deeper security model, and have received some external reviews (maybe audit if someone pays). +This should be stable enough such that chains with solid market caps could provide peer security (being both +provider and consumer). + +## Plan for MVP + +A higher-level backlog for what it takes to create the MVP. +This may be too many points and we need to review this. Currently in draft form. + +**Part 1: Foundation** + +* Start new repo for production mesh-security (port ideas from prototype, but we don't need to build on it) +* Finalize the documentation for provider side +* Define all contract interfaces / APIs as Rust files +* Produce stub-contracts with proper APIs (all `unimplemented!()`) + +**Part 2: The Vault** + +* Produce mock contracts with proper APIs (all with dummy testing implementation) + * Mocks should also be usable for UI testing with eg 1 minute unbonding +* Write and test vault contract (that calls mock local and remote staking via multi-test) + * Ensure vault accepts both native and cw20 tokens + * All configuration options should be implemented +* Finalize the documentation for the consumer side, including the custom SDK modules +* Create initial designs (wireframes) for the UI, focusing around vault and local/remote staking + +**Part 3: Provider Staking** + +* Implement local staking module (simple version - no optional features) + * Ensure user can withdraw rewards from local staking +* Implement remote staking module, provider side (simple version - no optional features) +* Implement mock converter, consumer side (connects via IBC properly, but ) +* Full stack IBC test from `token -> vault -> remote staking -> converter`, bonding and unbonding +* Usable UI for provider side (mocking out remote providers), with bonding and unbonding + +**Part 4: Consumer Staking** + +* Implement minimal Virtual Staking contract using minimal modifications to Osmosis' [superfluid staking](https://github.com/osmosis-labs/osmosis/tree/main/x/superfluid#messages) and [lockup](https://github.com/osmosis-labs/osmosis/blob/main/x/lockup/README.md#lock-tokens) modules. + * Try to keep changes minimal to work without LP shares (rather using 1:1, or the converter rate) + * Doesn't have to work properly in all cases, but should allow multiple bondings and unbondings to different validators +* Implement proper converter that uses governance set rate but talks IBC fully and calls into Virtual Staking to make stake +* Cross-chain full stack tests with IBC and staking (using osmosisd testnet config for consumer side, wasmd for provider side) +* Rewards not yet implemented +* Improve provider-side UI + +**Part 5: Rewards** + +* Make custom Virtual Staking module if needed + * Minimal changes to Osmosis contracts if possible, so can be backported + * Custom SDK app with this module (based on wasmd) for consumer side + * Handles reward withdrawl + * Handles multi bonding/unbonding in quick succession (not blocked by 7 pending unbonds issue) +* Add reward flow to Converter +* Send rewards over IBC to RemoteStaking contract +* Add reward withdrawal to remote staking +* Add cross-chain staking to UI + +**Part 6: Finalize Binaries** + +* Close any TODOs or known issues / bugs that fit in MVP scope + * Create well-defined GitHub issues for items to be addressed later (label V1 or V2) + +* At least one: + * Create fork of (wasmd? junod?) that supports virtual staking + * Create fork of osmosisd that supports virtual staking (ideally reusing as much as possible) +* Provide dev net environment showing this is working with a usable UI (for internal testing) + +**Part 7: Bring to testnet** + +* Pass code to the chains' testnets, so they can bring to the masses diff --git a/docs/MeshSecurity.png b/docs/MeshSecurity.png new file mode 100644 index 00000000..a5324c3d Binary files /dev/null and b/docs/MeshSecurity.png differ diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..6387ee37 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,122 @@ +# Mesh Security Architecture + +This is an architectural overview of the various components of Mesh Security. +These attempt to explain a full-refined v2 state. As needed, we will include +simplifications used for MVP (testnet) or v1 (production-ready, feature-limited) +as footnotes in the documents. + +```mermaid +flowchart TD + %%{init: {'theme': 'forest'}}%% + + subgraph Osmosis + A{{$OSMO}} -- User Deposit --> B(Vault); + B -- $OSMO --> C(Local Staker); + C -- $OSMO --> D[Native Staking] + B -- Lein --> E(External Staker Juno); + B -- Lein --> G(External Staker Stars); + end + + E -. IBC .-> M; + H -. IBC .-> N; + + subgraph Akash + H{{Akash Provider}}; + end + + subgraph Juno + M(Osmosis Converter) -- virtual stake --> O(Virtual Staking 1); + N(Akash Converter) -- virtual stake --> P(Virtual Staking 2); + O & P -- $JUNO --> Q[Native Staking]; + end + + G -. IBC .-> R; + + subgraph Stargaze + R{{Osmosis Receiver}} -- virtual stake --> S(Virtual Staking); + S -- $STARS --> T[Native Staking]; + end + +``` + +You can get a good overview of the whole system flow in the above diagram. +The design should allow one chain to provide security to multiple chains, while +at the same time receiving security from multiple chains. + +A key to understanding the design, is that the whole system is _delegator-centeric_ +not _validator-centric_. This means that we don't try to match the same validators on +multiple chains, or even subsets, but rather focus on leveraging the security +provided by staking tokens to secure validators on multiple chains. This provides a way to +scale bidirectionally, while avoiding some sort of infinite-loop recursion, as +chains can only provide security in their native token(s). + +## Use Cases + +Before we dig into the architecture of Mesh Security, please take a look at +the [use cases we aim to serve](./UseCases.md). + +## Definitions + +* **Pairing** - a trust relationship between two chains, such that one promises to lock up slashable + stake, while the other leverages this promise to issue validation power in the dPoS system. + Notably, chain A and chain B may have two pairings in opposite directions at the same time. +* **Provider** _(also "Provider Chain")_ - when looking at one pairing, this is the chain which + locks up tokens and provides them as staking collateral to the other chain. +* **Consumer** _(also "Consumer Chain")_ - when looking at one pairing, this is the chain which + adjusts validator weights and provides rewards based on remote collateral. +* **Collateral** - Tokens locked up on the provider chain, which are providing security + guarantees to one or more providers. These tokens may be slashed upon any slashing event + in the consumers. +* **Staking** _(also "Delegation")_ - The act of providing collateral for a validator, such that the + staker receives a portion of the block rewards if they behave correctly, as well as the slashing risk + if they misbehave. +* **Unstaking** _(also "Unbonding")_ - The act of initialing the removal of stake from a system. During + the "unbonding period", the collateral will not receive any rewards, but will be liable to slashing + based on misbehavior of that validator. +* **Unbonding period** - The time delay between initiating unbonding and having free access to the + underlying collateral. Once this time has passed after unstaking, all claims on the underlying + collateral are released and +* **Rewards** - Block rewards are issued to validators in the native token of the consumer chain. + A portion of these rewards goes to the stakers and is collected cross-chain. +* **Slashing** - If a validator misbehaves, the tokens delegated to it, which provided the + voting power, can be slashed. The percentage of slashing for each misbehavior depends on the chain. + This slash must be reflected by stakers on the provider chain, but we may make some approximations + as needed to implement this efficiently. +* **Jailing** - If a validator misbehaves, it may be jailed, or removed from the validator set and + prevented from returning. Tokens staked to it would be partially slashed and should be unstaked + as soon as possible, as they will receive no more rewards. Stake to a jailed validator still must + wait the unbonding period to be liquid. +* **Latency** - Time delay from an action being initiated and the effects being reflected in + another contract or chain. This doesn't refer to the unbonding period, but rather the delay between + initiating bonding or unbonding on the provider and the equivalent action occurring on the consumer. + +## Sections + +Below are links to detailed documents on various sub-systems: + +[Provider](./provider/Provider.md): + * [Vault](./provider/Vault.md) + * [Local Staking](./provider/LocalStaking.md) + * [External Staking](./provider/ExternalStaking.md) + * TODO - Rust interfaces + +[Consumer](./consumer/Consumer.md): + * [Converter](./consumer/Converter.md) + * [Virtual Staking](./consumer/VirtualStaking.md) + * SDK Changes + +[IBC Protocol](./ibc/Overview.md): + * [Cross-Chain Staking](./ibc/Staking.md) + * [Reward Flow](./ibc/Rewards.md) + * [Handling Slashing Evidence](./ibc/Slashing.md) + +## Limitations + +**Unbonding Limits** + +As we are focused on the standard implementation of the Cosmos SDK, one account may only have +a fixed number of simultaneous unbondings on a given delegator. By default, that number is 7. +We must batch together multiple unbonding requests so there are never more than that number +active at once. The typical solution for LSDs is to divide the unbonding period into segments, +so if the unbonding period is 3 weeks, the contract initiates unbonding every 3 days for all +requests that occurred in that period. diff --git a/docs/UseCases.md b/docs/UseCases.md new file mode 100644 index 00000000..a1d79a39 --- /dev/null +++ b/docs/UseCases.md @@ -0,0 +1,83 @@ +# Use Cases + +## Sibling Chains + +Two chains of similar size want to support each other. +There can be a difference of the strength, such that +larger -> smaller has more weight than smaller -> larger. + +```mermaid +flowchart LR + %%{init: {'theme': 'forest'}}%% + A(Juno) -- 40% --> B(Star); + B -- 20% --> A; +``` + +## Full Mesh + +This is meant for multiple chains with market caps within +say an order of magnitude that all have supportive relations +and want to enter a joint security zone. They can all provide meaningful levels of security +to each other. This leads to many bidirectional flows and +even loops (yes, the algorithm handles this). + +Note that Osmosis is about 5x the size of the other two, +so the weights are proportional to their relative market caps. + +```mermaid +flowchart LR + %%{init: {'theme': 'forest'}}%% + A(OSMO) == 50% ==> B(Juno); + A == 50% ==> C(Akash); + B -- 20% --> C; + C -- 20% --> B; + B -- 10% --> A; + C -- 10% --> A; +``` + +## DAOs migrating to own chain + +A number of Juno DAOs launching their own chains. They want to inherit most of their security from Juno, +but keep governance to their own token. + +```mermaid +flowchart TD + %%{init: {'theme': 'forest'}}%% + Juno -- 75%, no gov --> DAO1; + Juno -- 75%, no gov --> DAO2; + Juno -- 75%, no gov --> DAO3; +``` + +## Almost Replicated Security + +Mesh Security is not ICSv1 "Replicated Security". We do not map validators from provider to consumer, but rather delegators. +And the power is the subset of staked tokens that opt-in, so will always be lower than full stake. By design we always require +a native staking token in the consumer chain, but we can approximate "replciated security" for the "fully owned subsidiary" +use case. + +You can launch a chain with a governance token with minimal distribution to bootstrap the chain. Then accept another chain as a +provider, locking it's token 1:1 with the native token (hardcoded "oracle" feed), and allow it to control up to 99% of the voting power +of the chain, including gov votes. The end effect is the new chain is almost 100% controlled by the cross-stakers of the parent chain: + +```mermaid +flowchart TD + %%{init: {'theme': 'forest'}}%% + Juno -- 99%, gov --> Eve; +``` + + +## Credibly Neutral Common Good + +There are some items that should be neutral or independent of multiple chains, +like a shared name service. In this case, we allow multiple controlling chains to +control the staking and governance, even without any native staking power. + +```mermaid +flowchart TD + %%{init: {'theme': 'forest'}}%% + A(OSMO) --> |25%| N[Name Service]; + B(Juno) -- 25% --> N; + C(Stargaze) -- 25% --> N; + D(Cosmos Hub) -- 25% --> N; +``` + diff --git a/docs/consumer/Consumer.md b/docs/consumer/Consumer.md new file mode 100644 index 00000000..93d3c7d5 --- /dev/null +++ b/docs/consumer/Consumer.md @@ -0,0 +1,73 @@ +# Consumer + +The consumer side of the system receives "virtual tokens" from +some trusted providers and uses those to update the local staking weights. +It then provides rewards to those providers in exchange for +the security promise it receives. + +This is the other half of [the provider flow](../provider/Provider.md) + +```mermaid +flowchart LR + %%{init: {'theme': 'forest'}}%% + + A{{Osmosis Provider}}; + B{{Juno Provider}}; + + A -. IBC .-> D; + + subgraph Stargaze + C(Osmosis Price feed) --> D(Osmosis Converter) + D -- virtual stake --> E(Virtual Staking 1); + K(Juno Converter) -- virtual stake --> L(Virtual Staking 2); + M(Juno Price feed) --> K; + E & L -- $STARS --> G[Native Staking]; + end + + B -. IBC .-> K; +``` + +## Converting Foreign Stake + +Not all providers are treated equally. (And this is a good thing) + +Each Converter accepts messages from exactly one provider and is +the point where we establish trust. The Converter is responsible for +converting the staking messages into local units. It does two transformations. +This first is convert the token based on a price oracle. The second step is to apply a discount, +which captures both the volatility of the remote asset, as well as +a general preference for local/native staking. + +This is described [more in depth under Converter](./Converter.md#staking-flow). + +## Virtual Staking + +Once the Converter has normalized the foreign stake into the native staking units, +it calls into the associated ["Virtual Staking" contract](./VirtualStaking.md) in order +to convert this into real stake without owning actual tokens. This contract must be +authorized to have extra power in a native Cosmos SDK module to do this impact, and has +a total limit of tokens it can stake. It performs 3 high level tasks: + +* Staking "virtual tokens" as requested by the Converter (up to a limit) +* Periodically withdrawing rewards and sending them to the Converter +* Unstaking "virtual tokens" as requested by the Converter. This must be immediate and +avoid the "7 concurrent unbonding" limit on the `x/staking` module to be properly usable. + +## Handling Failures + +**TODO** + +Explain how this system limits systemic failures in a few cases. +(Does this section belong here? Or in a higher level doc?) + +Price issues: + +* Foreign Provider price moons (Existing stake hits the max cap) +* Foreign Provider price crashes (Usually accounted by discount, generally should not get more power staking +$1 of foreign tokens than $1 of local tokens) +* Consumer price crashes (All providers get better conversion, but limited by max cap) + +Byzantine Actors: + +* Foreign Provider goes Byzantine (okay as long as their max cap is less than circa 30% of total stake) +* Consumer goes Byzantine (Can force unbond, withhold rewards, but not slash or permanently lock up tokens) diff --git a/docs/consumer/Converter.md b/docs/consumer/Converter.md new file mode 100644 index 00000000..5f3fdd4e --- /dev/null +++ b/docs/consumer/Converter.md @@ -0,0 +1,122 @@ +# Stake Converter + +The Stake Converter is on the consumer side and is connected to an External Staker on the Provider side. +This handles the normalization of the external tokens and _converts_ them into "Virtual Stake". +There is a 1:1 connection between a Converter and a [Virtual Staking Contract](./VirtualStaking.md) +which handles the actual issuance. + +The converter is connected to the Provider chain via IBC and handles the various packets coming from it. + +## Setup + +When we [deploy the contracts](../ibc/Overview.md#deployment), we connect the Stake Converter on the consumer +chain with an [External Staking](../provider/ExternalStaking.md) contract on the Provider. Once this +connection is established, Consumer governance can authorize this Stake Converter with some ability to mint +on the ["Virtual Staking" contract](./VirtualStaking.md). + +When we deploy the Stake Converter, we must also configure the address of the +["Virtual Staking" contract](./VirtualStaking.md) that it will use to stake tokens. +In addition, we must define a price feed on setup. +(see [Price Normalization / Price Feeds](#price-feeds) below) + +## Staking Flow + +Once the connection is established, the provider can send various "virtual stake" messages to the converter, +which is responsible for processing them and normalizing for the local "virtual staking" module. These +packets are sent via a dedicated channel between the provider chain and the consumer chain to ensure +there are no other security assumptions (3rd party modules) involved in sending this critical staking +info. + +By itself, a Converter cannot impact the local staking system. It must connect to the [Virtual Staking system](./VirtualStaking.md), +which will convert the "virtual stake" into actual stake in the dPoS system, and return the rewards as well. This +document focuses on the flow from IBC packets to the virtual stake. + +### Price normalization + +When we receive a "virtual stake" message for 1 provider token, we need to perform a few steps to normalize it to the +local staking tokens. + +The first step is simply doing a price conversion. This is done via a [Price Feed](#price-feeds), which is +defined on setup and can call into arbitrary logic depending on the chain. (For example, +if we are sent 1000 JUNO, we convert to 1200 OSMO based on some price feed) + +The second step is to apply a discount. This discount reduces the value of the cross-stake to a value below what we would get from the pure +currency conversion above. This has two purposes: the first is to provide a margin of error when the price deviates far from the TWAP, so +the cross-stake is not overvalued above native staking; the second is to encourage local staking over remote staking. Looking at the +asset's historical volatility can provide a good estimate for the first step, as a floor for minimum discount. Beyond that, consumer +chain tokenomics and governance design is free to increase the discount as they feel beneficial. + +In this case, let's assume a discount of 40%. A user on the provider chain cross-stakes 100 PROV. We end up with a weight of + +`100 PROV * 18 CONS/PROV * (1 - 0.4) = 1080 CONS` + +Thus this cross-stake will trigger the converter to request the virtual staking module to stake 1080 CONS. + +The discount is stored in the Converter contract and can only be updated by the admin (on-chain governance). + +### Price Feeds + +In order to perform the conversion of remote stake into local units, the Converter needs a +trustable price feed. Since this logic may be chain dependent, we don't want to define it in the Converter +contract, but rather allow chains to plug in their custom price feed without modifying any of +the complex logic required to properly stake. + +There are many possible price feed implementations, a few of the main ones we consider are: + +**Gov-defined feed.** This is a simple contract that stores a constant price value, which is always +returns when asked for the price. On-chain governance can send a vote to update this price +value when needed. __This is good for mocks, or new chaina with no solid price feed and wanting a stable peg__ + +**Local Oracle** If there is a DEX on the consumer chain with sufficient liquidity and volume +on this asset pair (local staking - remote staking), then we can use that for a price feed. +Assuming it keeps a proper TWAP oracle on the pair, we sample this every day and can get the average +price over the last day, which is quite hard to manipulate for such a long time. +__This is good for an established chain with solid DEX infrastructure, like Osmosis or Juno__ + +**Remote Oracle** More dynamic than the gov-defined feed, but less secure than the local Oracle, +we can do an IBC query on a DEX on another chain to find the price feed. This works like the +Local Oracle, except the DEX being queries lives on eg. Osmosis. Note that it introduces another +security dependency, as if the DEX chain goes Byzantine, it could impact the security of the consumer +chain. __This is a better option if the local staking token has a liquid market, but there is +no established DEX on the chain itself (like Stargaze).__ + +The actual logic giving the price feed is located in an Oracle contract (configured upon init). +We recommend using an (eg daily) TWAP on a DEX with good liquidity - ideally on the consumer chain, but this implementation is left up +to the particular chain it is being deployed on. With this TWAP we convert eg. 1 PROV to 18 CONS. + +### Virtual Staking + +Each Converter is connected 1:1 with a [Virtual Staking Contract](./VirtualStaking.md). This contract manages +the stake and has limited permissions to call into a native SDK module to mint "virtual tokens" and stake them, +as well as immediately unbonding them. This contract ensures the delegations are properly distributed. + +The Converter simply tells the virtual staking contract it wishes to bond/unbond N tokens and that contract +manages all minting of tokens and distribution among multiple validators. + +## Rewards Flow + +Once per epoch, the virtual staking module will trigger rewards. This will send a number of messages to the Converter, +specifying which validators the rewards belong to, along with the native reward tokens themselves. + +The Converter will then [transfer all these tokens via ICS20](../ibc/Overview.md) to the corresponding `External Staking` contract +on the Provider chain, and send a message over the standard IBC channel to inform the `External Staking` contract how to distribute them. +(If we get callbacks on ics20, we send the metadata only after tokens have arrived. Until then (for MVP), we send them concurrently and hope) + +## Unstaking Flow + +The Converter can also unstake some tokens. These will be held in escrow on the Provider and are susceptible to slashing upon proper evidence +submission. Since the virtual stake is, well, "virtual" and slashing has no impact, the delegation numbers can be immediately reduced +on the consumer's native staking module. + +For MVP, we just trigger and unbonding, and when the tokens return to the Virtual Staking Module, they can be burnt (or reused for future delegations). +The native x/staking module limits us to 7 simultaneous unbonding (per Converter), so we need to queue these up and execute them in batches. +This is a standard limitation of liquid staking modules. For more explanation, [see the stride docs](https://docs.stride.zone/docs/unstaking): + +> The Stride blockchain initiates the unbonding process by grouping the records of all of the unbondings on the chain. +> Unbondings are grouped because Cosmos chains do not allow more than 7 unbondings at a time within a 21 day period. +> This is a security measure put in place across the Cosmos ecosystem. This does not impact the average user, but it +> is the reason Stride processes requests every 4 days. + +For V1, we modify the staking module to treat virtual stake specially and can just directly update the stake, without adding to the unbonding queue. +This will allow us to perform more than 7 unbonding simulataneously. + diff --git a/docs/consumer/VirtualStaking.md b/docs/consumer/VirtualStaking.md new file mode 100644 index 00000000..9a0deec5 --- /dev/null +++ b/docs/consumer/VirtualStaking.md @@ -0,0 +1,236 @@ +# Virtual Staking + +Virtual Staking is a permissioned contract on the Consumer side that can interact with the +native staking module in special ways. There are usually multiple Virtual Staking contracts +on one Consumer chain, one for each active Provider, linked 1-to-1 with a Converter. + +## Previous Work + +There is a lot of overlap with this design and Osmosis' [design of SuperFluid Staking](https://github.com/osmosis-labs/osmosis/tree/main/x/superfluid). +Before commenting on the technical feasibility or implementation on the SDK side, it would be good to review this in depth. +This is probably the biggest change made to staking functionality without forking `x/staking` and should be leveraged for the MVP at least. + +## Interface + +The entry point to the Virtual Staking system is a contract that can be called by one Converter, and which +has some special abilities to stake virtual tokens. We cannot let any receiver mint arbitrary tokens, or we lose all security, +so each "Converter / Virtual Staking Contract" pair has permission of a maximum amount of "virtual stake" that it can provide +to the system. Anything over that is ignored, after which point, the average rewards per cross-staker start to diminish as +they split a limited resource. + +The `Converter` should be able to call into the `Virtual Staking` contract with the following: + +```rust +pub enum VirtualStakeExecMsg { + /// This mints "virtual stake" if possible and bonds to this validator. + Bond { + amount: Uint128, + validator: String, + }, + /// This unbonds immediately, not like standard staking Undelegate + Unbond { + amount: Uint128, + validator: String, + }, +} +``` + +The `Converter` should be able to query the following info from the contract: + +```rust +pub enum VirtualStakeQueryMsg { + #[returns(BondStatusResponse)] + BondStatus { }, +} + +pub struct BondStatusResponse { + pub cap: Uint128, + pub delegated: Uint128, +} +``` + +Finally, the virtual staking contract should make the following call into the `Converter`, +which is send along with a number of `info.funds` in the native staking token: + +```rust +pub enum ConverterExecMsg { + /// This is required, one message per validator all info.funds go to those delegators + DistributeRewards { + validator: String, + }, + /// Optional (in v1?) to optimize this, by sending multiple payments at once. + /// info.funds should equal rewards.map(|x| x.reward).sum() + DistributeRewardsMulti { + rewards: Vec, + }, +} + +pub struct RewardInfo { + pub validator: String, + pub reward: Uint128, +} +``` + +## Extensibility + +Virtual Staking is primarily defined by the interface exposed to the Convert contract, so we can use wildly +different implementations of it on different chains. Some possible implementations: + +* A CosmWasm contract with much of the logic, calling into a few custom SDK functions. +* A CosmWasm contract that just calls into a native module for all logic. +* An entry point to a precompile (eg. Composable uses "magic addresses" to call into the system, rather than `CustomMsg`) +* A mock contract that doesn't even stake (for testing) + +All these implementations would require a Virtual Staking contract that fulfills the interface above and is informed by the +specific design discussed here, but it can by creative in the implementation. + +The rest of this document describes a "standard SDK implementation" that we will ship with Mesh Security. +Since we want this to be portable and likely to be integrated into as many chains as possible, we will look +for a design minimizing the changes needed in the Go layer, and focus on keeping most logic on CosmWasm. +We can add other implementations later as needed. + +## Standard Implementation + +Virtual Staking will be a mix of a Cosmos SDK module and a CosmWasm contract. +We need to expose some special functionality through the SDK Module and `CustomMsg` type. +The module will contain a list of which contract has what limit, which can only be updated +by the native governance. The interface is limited and can best be described as Rust types: + +```rust +#[cw_serde] +pub enum CustomMsg { + /// Embed one level here, so we are independent of other custom messages (like TokenFactory, etc) + VirtualStake(VirtualStakeMsg), +} + +#[cw_serde] +/// These are the functionality +pub enum VirtualStakeMsg { + /// This mints "virtual stake" if possible and bonds to this validator. + Bond { + amount: Uint128, + validator: String, + }, + /// This unbonds immediately, not like standard staking Undelegate + Unbond { + amount: Uint128, + validator: String, + }, +} +``` + +It will also need a query to get its max cap limit. Note that it can use standard `StakingQuery` types to query it's existing delegations +as they will appear as normal in the `x/staking` system. + +```rust +#[cw_serde] +pub enum CustomQuery { + /// Embed one level here, so we are independent of other custom messages (like TokenFactory, etc) + VirtualStake(VirtualStakeQuery), +} + +#[cw_serde] +/// These are the functionality +pub enum VirtualStakeQuery { + #[returns(MaxCapResponse)] + MaxCap { }, +} + +pub struct MaxCapResponse { + pub cap: Uint128, +} +``` + +### Contract + +Each Virtual Staking contract is deployed with a Converter contract and is tied to it. +It only accepts messages from that Converter and sends all rewards to that Converter. +The general flows it provides are: + +* Accept Stake message from Converter and execute custom Bond message +* Accept Unstake message from Converter and execute custom Unbond message +* Trigger Reward Withdrawals periodically and send to the Converter + +It should keep track on how much it has delegated to each validator, and the total amount of delegations, +along with the max cap. It should not try to bond anything beyond the max cap, as that will error. +But then silently adjust internal bookkeeping, ignoring everything over max cap. + +The simplest solution is: + +* If total delegated is less than max cap, but new delegation will exceed it, just delegate the difference +* If total delegated is less than max cap and we try to stake more, update accounting, but don't send virtual stake messages +* If we unbond, but total delegated remains greater than max cap, update accounting, but don't send virtual stake messages + +However, there is an issue here, as we are staking on different validators. Imagine I have a cap of 100. 80 is bonded to A. +I request to bond 120 more to B, which turns into 20 bond to B. There is an actual ration of 4:1 A:B bonded, but the remote +stakers have delegated at a ration of 2:3. This gets worse if eg. someone unbonds all A. We end up with 120 requested for B, +but 80 delegated to A and 20 delegated to B. + +To properly handle this, we should use Redelegations to rebalance when the amounts are updated. This may be quite some messages +(if we have 50 validators, each time we stake one more, we need to add a bit to that one and decrease the other 50... a smart +solution may use some batches). Limiting the interface to custom SDK modules, we would first "immediately unbond" +all other validators, then bond that virtual stake to the newly bonded validator. Much care must be take with rounding issues +to avoid going 0.000001 token above the max cap. + +Note: in an alternate implementation this redelegation could be in the native Go module, but we chose to put majority of logic into contracts +for portability. + +Note: this example demonstrates why the immediate unbond power is required to properly implement this, and we cannot just do a pure contract +calling normal staking commands (or we would be limited to shifting validators once per unbond period). + +### Module + +The module maintains a list of addresses (for Virtual Staking contracts), along with a max cap of +virtual tokens for each. It's main purpose is to process `Bond` and `Unbond` messages from +any registered contract up to the max cap. Note that it mints "virtual tokens" that don't affect +max supply queries and can only be used for staking. + +The permissions defined in the Meta-Staking module to cap the influence of the various provider is of cirtical importance +for the security design of Mesh Security. Not all remote chains are treated equally and we need to be selective +of how much security we allow to rest on any given token. + +The Virtual Staking **Module** maintains an access map `Addr => StakePermission` which can only be updated by chain governance (param change). +The Virtual Staking **Module** also maintains the current state of each of the receivers. + +```go +type StakePermission struct { + /// Limit we cap the virtual stake of this converter. + /// Defined as a number of "virtual native tokens" this can mint. + MaxStakingRatio: sdk.Int, +} +``` + +Beyond MVP, we wish to add the following functionality: + +* Provide WithdrawReward callbacks each epoch (eg 1 day) on BeginBlock to all registered contracts +* Provide configuration for optional governance multiplier (eg 1 virtual stake leads to 1 tendermint power, +but may be 0 or 1 or even 0.5 gov voting power) + +### Reward Withdrawls + +We need to trigger each Virtual Staking contract once an epoch. +This can be done via CronCat or other bot (for MVP), and ideally via a BeginBlock hook for v1. + +The Virtual Staking Module also has a BeginBlock hook called once per epoch (param setting, eg 1 day), that will trigger all reward withdrawals. +This epoch has a different start for each converter (based on when they were authorized), so they happen staggered over time. +When the epoch finishes for one Converter, the Module will withdraw rewards from all delegations that converter made, and send those tokens +to the Converter along with the info of which validator these are for. + +The initial implementation will call the Converter eg 50 times, once for each validator. If dev time permits, we can use a more optimized +structure and call it once, with all the info it needs to map which token corresponds to which validator. (See both [variants of +`ConverterExecMsg`](#interface)) + +The Converter in turn will make a number of IBC packets to send the tokens and this metadata back to the External Staking module on the Provider chain. + +## Roadmap + +Define which pieces are implemented when: + +MVP: We stake virtual tokens (like SuperFluid), can unbond rapdily, and these influence governance normally +(validators with delegations get more governance voting power) + +V1: We can turn the governance influence on and off per converter. (Stake impacts Tendermint voting power, +but may or may not impact governance voting powerr). We also get native callbacks for triggering rewards +each epoch. + +V2: Make improvements here as possible (rebonding, fractional governance multiplier) diff --git a/docs/ibc/Overview.md b/docs/ibc/Overview.md new file mode 100644 index 00000000..b98e0f3e --- /dev/null +++ b/docs/ibc/Overview.md @@ -0,0 +1,92 @@ +# IBC Protocol + +Mesh Security is fundamentally an interchain-protocol, +and it is important to define the exact mechanics of +how the different subsystems interact. + +Here were are focusing just on the dotted lines, that +we abstracted away when focusing on [Providers](../provider/Provider.md) +and [Consumers](../consumer/Consumer.md). + +Fundamentally there are these two flows, although slashing +is also done cross chain, but outside of IBC (by external observers). +For the time being, we focus on the two IBC data flows +and discuss cross-chain slashing in another section. + +```mermaid +flowchart RL + %%{init: {'theme': 'forest'}}%% + + A[Provider] -- Staking --> B[Consumer]; + B -- Rewards --> A; + + C{{Observer}} -. Slashing Evidence .-> A; +``` + +## Deployment + +As mentioned in [the consumer section](../consumer/Consumer.md), +we need to establish a trust relationship between the Provider side and the Consumer side. This is done in multiple stages. + +I refer to "Admin" here in most of the setup, which can be any address that can make calls on two chains. +It is responsible for proper configuration, but revokes all it's power before the provider-consumer pairing has any +voting power (permitted to virtually stake). It can be a normal account, a ledger, a cosmos sdk native multisig, +a DAO using ICA to act on two chains. In theory this could be on-chain governance, but that would be unwieldy. + +I recommend using the approach of a single actor deploying (for simplicity), then using governance to authorize +the connection once it is all configured (for security). + +Establish a channel (allow list): + +2. Admin deploys _Converter_ and _Virtual Staking_ on Consumer side, both referencing each other. +3. Admin deploys _External Staker_ on Provider side, registering `(connection, port)` from _Converter_ contract from (1) +4. Admin updates _Converter_ (1) to allow the `(connection, port)` from (2) +5. Anyone establishes a new channel between _Converter_ and _External Staker_. +Both contracts ensure the other side matches the stored `(connection, port)` and refused the channel otherwise. +6. Neither contract accept any more channels. They only accept one channel and only what matches their config. + +Now that we have a channel and know which contract is talking to whom, we need +to authorize them: + +1. Admin removes their admin rights on both contracts (or passes to chain governance) +2. Due diligence performed on configuration of both contracts and the channel. Parties from both chains can verify the contract code and configuration. +3. Consumer chain governance votes to authorize this _Virtual Staking_ contract to have special +privileges on the staking system (see [Virtual Staking](../consumer/VirtualStaking.md)). +4. Authorization on the Provider chain [is not required](https://github.com/CosmWasm/mesh-security/blob/begin-architecture/docs/provider/Vault.md#design-decisions), +but the default cross-stake frontend application should add the _External Staker_ to the recommended list. + +Once this has been completed, everything is set up and the token holders on the provider side +can now safely cross-stake on the consumer side with one click. + +Note that the majority of the setup is permissionless. And it just requires one governance vote on the +consumer side to authorize ability to stake virtual tokens, which is essential for any security guarantees. +No forks, no complicated processes... just one proposal. + +## Protocol design + +We use this custom channel to communicate securely and directly +between the two sides, sending messages about bonding and unbonding +delegations. This is the principle form of communication, +and we dig into the [Cross-Chain Staking Protocol](./Staking.md) +in more depth. + +We send reward tokens over a standard ics20 channel so that +they are fungible with the native staking token currently sent +between the chains (it will have the same denom as what's listed on the DEX). +Besides moving the rewards, we need to inform the _External Staker_ that +it has received rewards so it can distribute them to validators. +We will do that either with a separate message in the control channel (above), +or by making use of the ICS20 memo field to pass this info. +Read more about the [Cross-Chain Rewards Protocol](./Rewards.md) +in more depth. + +Finally, slashing is not in-protocol, so a malicious consumer chain +cannot slash an honest delegator. Rather, an external observer must +submit evidence of a double sign on the consumer chain directly to +the provider chain. With that direct proof, it will take action to slash +the appropriate stake. We explain this design more in "Trust Assumptions" below. +Read more about the mechanics of the [Slashing Evidence Handling](./Slashing.md). + +## Trust Assumptions + +**TODO** list between the consumer and the provider diff --git a/docs/ibc/Rewards.md b/docs/ibc/Rewards.md new file mode 100644 index 00000000..a909b166 --- /dev/null +++ b/docs/ibc/Rewards.md @@ -0,0 +1,3 @@ +# Cross-Chain Rewards Protocol + +**TODO** Define IBC design diff --git a/docs/ibc/Slashing.md b/docs/ibc/Slashing.md new file mode 100644 index 00000000..6a5233d9 --- /dev/null +++ b/docs/ibc/Slashing.md @@ -0,0 +1,7 @@ +# Slashing Evidence Handling + +Note: Slashing will not be part of the MVP rollout, +and first implemented in v1. However, we define +proper slashing mechanics here. + +**TODO** Define Slashing design diff --git a/docs/ibc/Staking.md b/docs/ibc/Staking.md new file mode 100644 index 00000000..e0b9de6c --- /dev/null +++ b/docs/ibc/Staking.md @@ -0,0 +1,3 @@ +# Cross-Chain Staking Protocol + +**TODO** Define IBC design diff --git a/docs/provider/LocalStaking.md b/docs/provider/LocalStaking.md new file mode 100644 index 00000000..ecf8c13a --- /dev/null +++ b/docs/provider/LocalStaking.md @@ -0,0 +1,7 @@ +# Local Staking + +A local staking contract connects to a [Vault](./Vault.md). Unlike other creditors, it actually +accepts the native token along with the lien. It manages staking the vault tokens to the native +protocol and returns them when finished unbonding. + +**TODO** flesh this out diff --git a/docs/provider/Provider.md b/docs/provider/Provider.md new file mode 100644 index 00000000..69104206 --- /dev/null +++ b/docs/provider/Provider.md @@ -0,0 +1,190 @@ +# Provider Overview + +Here is a brief overview of the architecture on the provider side +before we dig into the function of each of the components. + +The standard case is a vault on the provider chain enabling +staking the native token locally, as well as staking "virtual tokens" +on multiple external chains connected via IBC: + +```mermaid +flowchart LR + %%{init: {'theme': 'forest'}}%% + subgraph Osmosis + A{{$OSMO}} -- User Deposit --> B(Vault); + B -- $OSMO --> C(Local Staker); + C -- Stake --> D[Native Staking] + B -- Lein --> E(External Staker Juno); + B -- Lein --> G(External Staker Stars); + end + + E -. IBC .-> F{{Juno Consumer}}; + G -. IBC .-> H{{Stars Consumer}}; + +``` + +## Flows + +The first action the user must undertake is depositing +the native staking token into the vault. This must be liquid +(unbonded and fully vested). This gives them a credit on the vault, +which they can withdraw assuming there are no outstanding leins on that amount. +[Read more about the vault](./Vault.md). + +Each vault contains *exactly one* denom and has *exactly one* local staking +module. This local staker can stake the vault token with the +native staking module. It actually accepts the original token, which makes +it different than external stakers, which accept leins. By depositing in the vault +and staking in the local staker, I will have achieved the same effect +(and get the same rewards) as directly staking... but I can now use my balance +for more. +[Read more about local staking](./LocalStaking.md). + +The external stakers allow us to cross-stake the native vault +token on other chains (or other DAOs) that use a different native +staking token, but have opt-ed in to accepting a portion +of their security from this vault in exchange for a portion +of their rewards. In the basic model, these accept a lein +from the vault and will communicate with a consumer interface +to inform how much stake is locked to which validator and +to receive rewards. +[Read more about external staking](./ExternalStaking.md). + +The connection to the consumer is generally over IBC and the consumer is +responsible for converting these "virtual tokens" into delegations +in the native staking module. Note that the consumer must first opt-in to +accept the provider's tokens and can place multiple restrictions and limits +on to how much power can be granted into any external chain. +[Read more about consumers](../consumers/Consumer.md). + +## Stakers and Governance + +Both [local stakers](./LocalStaking.md) and [external stakers](./ExternalStaking.md) +allow the user to bond to the validator of their choice on the associated chain. +The question arises as to what influence the cross-staked user can have on chain governance. + +For MVP, all these delegations provide full governance power to the validator +that was selected, but the cross-staker may not directly vote +on any of these issues (they inherit the validator's vote). +**For MVP, no override of votes** + +For local staking (in the native token), the end goal is the cross-staker has the +same governance rights as if they had staked directly and can override +the validator's voice if they request. However, this is relatively complex when +one local staking contract hold delegations from many staker to the same validator, +and takes careful design with weighted votes and probably something +like cron cat to trigger this. + +**We aim to have full participation in local votes by v2** + +For external staking, the cross-staker will never be able to override +the vote, as they are not expected to be very active in local governance +on these external protocols. (If they want to participate, they can take the +cross-staking rewards and delegate those tokens directly to get a voice.) + +There will be two supported configurations for external staking. +Either the cross-staked tokens provides governance voting power +to the validator in the same proportion that it provides Tendermint voting power. +Or the cross-staked tokens only provide Tendermint voting power (security) +without granting more governance power to that validator. +There are [use cases](../UseCases.md) for each configuration. + +**By v2, we will be able to configure if cross-staked tokens provide governance power to the validator** + +## DAO DAO Extension + +After discussing this general diagram, we realized there is some value in +a simplified version of this, which may also be a great starting place to +testing out UX without the complications of IBC. DAOs have their own +token, governance, staking, and reward contracts. We can compare them to +low-cap chains embedded in a host chain. Let's look at two ways of using DAOs locally + +### Bootstrapping DAOs + +When a new DAO launches, it often wants to accomplish two things: +1. Ensure a reasonable security for the DAO (regardless of low market cap) +2. Airdrop some tokens to native stakers. + +By using a variant of mesh security, they can do both, acting as a +consumer of security from the native staking tokens to provide a solid +base security amount. And also, providing a share of their rewards +to those $JUNO cross-stakers, effectively airdropping those tokens +(at zero cost to the recipient) for providing some initial security. +It would look like this: + +```mermaid +flowchart LR + %%{init: {'theme': 'forest'}}%% + A{{$JUNO}} -- User Deposit --> B(Vault); + B -- $JUNO --> C(Local Staker); + C -- Stake --> D[Native Staking] + B -- Lein --> E(External Staker Neta); + E -- virtual $NETA --> F(Neta Staking contract); +``` + +Note that this requires the exact same Vault and Local Staker +as the real use case, and uses the same External Staker interface. +The "Neta Staking" contract is already built and by building out +this "External Staker Neta" adapter contract, we can work through +all the design issues in a local environment (where we can easy get +coverage with `cw-multi-test`), as well as start building out a +single-chain demo UI, where we can start discovering and resolving +the UX issues in explaining such a system. + +This may force a bit more abstraction in the system, as +"external staker Neta" doesn't take a validator as an argument, +it just stakes. Without delegation, this also brings up many questions +about governance power and such, which may be easier to prototype +in DAO contracts than modifying the Cosmos SDK staking module. + +**Recommendation** Once the MVP Vault is built, it would be good to assign +one contract dev to work out this External Staker implementation to +some standard DAO DAO staking contract (can be a "hacked" version that +just holds a lot of the DAO token, like we did in HackWasm Medellin). +This will unblock frontend developers and allow us to get much quicker +feedback on UX issues in such a system, while the backend engineers +are working with the complexities of IBC and staking virtual tokens in +the native SDK staking module. + +We don't develop this to production quality, but use it +as a proof-of-concept to get some hands on feedback on how to deal +with various issues here like different unbonding periods and +what to do about governance power. + +### Mega DAOs + +If a DAO has a market cap approaching the TVL staked in the native token, this +becomes a dangerous situation for the DAO as the security provisioned by the chain +is insufficient to protect it from attacks. We could turn this model around and allow +the DAO token to "externally stake" on the local staking contract. + +```mermaid +flowchart LR + %%{init: {'theme': 'forest'}}%% + A{{$WYND}} -- User Deposit --> B(Vault); + B -- $WYND --> C(Local Staker); + C -- Stake --> D(WYND DAO Staking) + B -- Lein --> E(External Staker Juno); + E -- virtual stake --> F(Meta-Staking); + F -- $JUNO --> G[Native Staking]; +``` + +Note this would require a different implementation for vault (to handle cw20), +and likely a different "local staker" interface (you don't select validators, but rather unbonding time). +The "External Staker JUNO" would be similar to the normal [Receiver model](../consumer/Receiver.md) +and we will need a full implementation of the [Consumer side](../consumer/Consumer.md) +implemented on the same chain. + +**Recommendation** We do not build this either as MVP or v1, and view later if it makes +sense at all. However, we should consider this use case in our designs to ensure our interfaces +and invariants make this possible. + +## Optional Extensions + +These won't be included in MVP and require more modifications +to the core Cosmos SDK modules, which makes them more risky and +more difficult to port to other chains. But could be considered +as chain-specific extensions. + +* Enable moving bonded tokens directly into the vault? (Custom SDK change) +* Allow depositing vesting tokens to the vault? (deeper SDK change) diff --git a/docs/provider/Vault.md b/docs/provider/Vault.md new file mode 100644 index 00000000..6087c7a4 --- /dev/null +++ b/docs/provider/Vault.md @@ -0,0 +1,116 @@ +# Vault + +The entry point of Mesh Security is the **Vault**. This is where a potential +staker can provide collateral in the form of native tokens, with which he or she wants +to stake on multiple chains. + +Connected to the _Vault_ contract, is exactly one [local Staking contract](./LocalStaking.md) +which can delegate the actual token to the native staking module. It also can connect to an +arbitrary number of [external staking contracts](./ExternalStaking.md) which can make use +of said collateral as "virtual stake" to use in an external staking system (that doesn't +use the vault token as collateral). + +The key here is that we can safely use the +same collateral for multiple protocols, as the maximum slashing penalty is significantly +below 100%. If double-signing is penalized by a 5% slash (typical in Cosmos SDK chains), +then one could safely use the same collateral to provide security to 20 chains, as even +if every validator that used that collateral double-signed, there would still be enough +stake to slash to cover that security promise. + +As discussed in the higher-level description of the provider design, about extending +this [concept to local DAOs](./Provider.md#dao-dao-extension), +there may be many different implementations of both the _Local Staking_ concept as well +as the _External Staking_ concept. However, we must define +standard interfaces here that can plug into the Vault. + +We define this interface as a _Creditor_ (as it accepts leins). + +## Definitions + +TODO: refine this + +* **Native Token** - The native staking token of this blockchain. More specifically, + the token in which all collateral is measured. +* **Slashable Collateral** - `Leins(user).map(|x| x.amount * x.slashable).sum()` +* **Maximum Lein** - `Leins(user).map(|x| x.amount).max()` +* **Free Collateral** - `Collateral(user) - max(SlashableCollateral(user), MaximumLein(user))` + +## Design Decisions + +The _vault_ contract requires one canonical _Local Staking_ contract to be defined when it is +created, and this contract address cannot be changed. + +The _vault_ contract doesn't require the _External Stakers_ to be pre-registered. Each user can decide +which external staker it trusts with their tokens. (We will provide guidance in the UI to only +show "recommended" externals, but do not enforce on the contract level, if someone wants to build their own UI) + +The _vault_ contract enforces the maximum amount a given Creditor can slash to whatever was +agreed when making the lien. + +The _vault_ contract will only release a lien when requested by the creditor. (No auto-release override). + +The _vault_ contract may force-reduce the size of the lien only in the case of slashing by another creditor. +The creditors must be able to handle this case properly. + +The _vault_ contract ensures the user's collateral is sufficient to service all the liens +made on said collateral. + +The _vault_ contract may have a parameter to limit slashable collateral or maximum lien to less than +100% of the size of the collateral. This makes handling a small slash condition much simpler. + +The _creditor_ is informed of a new lien and may reject it by returning an error +(eg if the slashing percentage is too small, or if the total credit would be too high). + +The _creditor_ may slash the collateral up to the agreed upon amount when it was lent out. + +The _creditor_ should release the lien once the user terminates any agreement with the creditor. + +## Implementation + +**TODO** translate the below into Rust code. After writing this in text, I realize +it is much less clear than the corresponding code. + +### State + +* Collateral: `User -> Amount` +* Leins: `User -> {Creditor, Amount, Slashable}[]` +* Credit `Creditor -> Amount` + +### Invariants + +* `SlashableCollateral(user) <= Collateral(user)` - for all users +* `MaximumLein(user) <= Collateral(user)` - for all users +* `Leins(user).map(|x| x.creditor).isUnique()` - for all users + +### Transitions + +**Provide Collateral** + +Any user may deposit native tokens to the vault contract, +thus increasing their collateral as stored in this contract. + +**Withdraw Collateral** + +Any user may withdraw any _Free Collateral_ credited to their account. +Their collateral is reduced by this amount and these native tokens are +immediately transferred to their account. + +**Provide Lein** + +TODO. Promise collateral as slashable to some creditor. +Args `(creditor, amount, slashable)`. +This is updated locally + +**Release Lein** + +TODO + +**Slash** + +TODO + +* Increase Slashing(user, creditor)? + +## Footnotes + +For MVP, Slashable Collateral and Maximum Lein can be up to 100% of total Collateral.