diff --git a/docs/architecture/RELEASE_STAKE.md b/docs/architecture/RELEASE_STAKE.md new file mode 100644 index 00000000..0a298f05 --- /dev/null +++ b/docs/architecture/RELEASE_STAKE.md @@ -0,0 +1,166 @@ +# Release Stake + +## Overview + +`release_stake` is a message in the restaking system that allows a **consumer contract** to request the release of tokens from its provider contract. + +### Message Structure +```rust +ReleaseStake { + staker: String, + amount: Uint128, + denom: String, +} +``` + +## Core Process +1. **Initiation:** A consumer contract sends the `release_stake` message to the provider. +2. **Validation:** The provider validates the request by checking the amount and ensuring proper authorization. +3. **State Update:** Stake accounting is updated in the provider contract, reflecting the released tokens. +3.1. *Question:* shouldn't this force the clearing of any unbonding claims and/or finalizing pending reward distributions. +4. **Finalization:** The provider processes any unbonding claims and optionally forwards the `release_stake` message up the chain. + +> **Exception:** If the contract is the ultimate provider (e.g., Token Staking), the process terminates by sending a bank transfer to the original staker. + +*Question:* my understanding is that during the unbonding period, the tokens remain locked. During this period they still may be slashed or changed (delegation scenarios). So then when this period has ended, and any pending rewards are accounted for/distributed, can `release_stake` happen. + +## Contract Behaviors +The behavior of `release_stake` varies depending on the contract type. Contracts are categorized into **Pure Pass-Through Contracts** and **State-Mutating Contracts**. + +### Pure Pass-Through Contracts +These contracts do not alter the stake state and simply forward the message upstream to the provider: + +#### 1. Token Staking +- Receives `release_stake` from a consumer. +- Creates a **Bank Send** transaction to the staker. +- Terminates the flow; does not forward to a provider as it is a leaf node. + +#### 2. Fan In +- Maps output denominations to input denominations and forwards the request to the correct provider. +- Ensures the input denom is correctly remapped. + +### State-Mutating Contracts +These contracts manage stake accounting and modify state: + +#### 1. Token Weighting +- Receives `release_stake` after unbonding. +- Makes sure it’s not recalculating weights before propagating `release_stake`. +- Adjusts **token weight ratios** and stake state. +- Forwards the message to the provider. + +#### 2. Delegations + +The delegations contract manages stake allocation across multiple delegates. When processing `release_stake`, it must handle proportional distribution of released amounts across delegators. + +**Key Challenges:** +- Must handle potentially large numbers of delegators (1000+) +- Needs to maintain proper proportional distributions +- Cannot iterate through all delegators due to gas limits +- Must handle multiple release_stake requests during unbonding period +- Must update delegation ratios after release + +**State Management:** +```rust +#[cw_serde] +pub struct UnbondingClaim { + pub total_unbonding: Uint128, // Amount being unbonded in this claim + pub released_amount: Uint128, // Amount processed/released so far + pub end_time: u64, // When unbonding will end +} + +// Maps (denom, delegate) -> vector of unbonding claims +pub const DELEGATE_UNBONDING: Map<(&str, &Addr), Vec>; +``` + +**Process Flow:** + +1. When consumer calls `release_stake`: +```rust +pub fn release_stake( + deps: DepsMut, + env: Env, + info: MessageInfo, + staker: String, // delegate address + amount: Uint128, + denom: String, +) -> Result { + // Validate sender is consumer contract + // Verify delegate has sufficient stake + // Verify no pending rewards need distribution + // Record historical stake amount for proper reward calculation + // Create new UnbondingClaim + // Update total staked amount + // Emit events +} +``` + +2. When delegator calls `release_unbonded`: +```rust +pub fn release_unbonded( + deps: DepsMut, + env: Env, + info: MessageInfo, +) -> Result { + // For each delegate where delegator has stake: + // For each unprocessed claim: + // If unbonding period complete: + // Update delegator's stake and liens + // Recalculate delegation ratios: + // - Get all current delegations + // - Subtract the released amount + // - Normalize remaining delegations to sum to 100% + // - Update STAKER_DELEGATIONS + // Forward portion upstream via release_stake message + // Mark as processed + // Update released_amount in claim + + // Send accumulated messages +} +``` + +#### 3. Fan Out +- Handles multiple output flows. +- Updates **outflow accounting** to reflect the proportionate release. +- Maintains unbonding claims instead of directly triggering `release_stake`. +- The staker must call `ReleaseUnbonded` to propagate `release_stake` upstream. + +#### 4. Splitter +- Maintains split proportions for multiple consumers. +- Updates split accounting based on the `release_stake` message. + +#### 5. Operators +- Maps the **operator address** to the original staker address. +- Updates stake accounting and ensures the message flows through correctly. +- Maintains unbonding claims until the staker calls `ReleaseUnbonded`. + +## State of Contracts Implementing The Release Stake + + +| Contract | `release_stake` Implementation | Current Status | Needed Changes | +| --------------- | ---------------------------------------------------------------------------------------------------------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Token Staking | `release_stake` implemented | Fully Implemented | Tokens are available for immediate withdrawal via `withdraw_unbonded`. | +| Fan In | `release_stake` updates `TOTAL_STAKE` and forwards upstream | Partially Implemented | Add reward accounting (not mandatory), we need partial/full release policy, add validation before forwarding | +| Fan Out | `release_stake` implemented: updates state and liens, but user must call `ReleaseUnbonded` to propagate upstream | Partially Implemented | Needs integration of reward logic (right now is `todo!()` in `withdraw_rewards`/`distribute_rewards`), ensure that all claims and outflows are settled before user triggers `ReleaseUnbonded` to finally push `release_stake` upstream | +| Delegations | still `todo!()` | Not Implemented | ensure delegators’ claims settled, distribute rewards, maybe also handle partial/full release before forwarding | +| Token Weighting | no explicit `release_stake` present | Not Implemented | implement `release_stake`: ensure no weight recalculations in progress, fix token weight ratios, then forward upstream | +| Operators | `release_stake` implemented | Partially Implemented | make sure that operator removal and slashing are in consideration. Ensure unbonding claims handling until `ReleaseUnbonded` called by staker, then forward upstream | +| Splitter | `release_stake` is still `todo!()` | Not Implemented | Implement logic for multiple splits, handle rewards/unbonding claims, then forward | + +## Known Trigger Points +`release_stake` can be triggered in several scenarios: + +1. **User-Initiated Claims** (ReleaseUnbonded): + - A user calls `ReleaseUnbonded` on a contract after the unbonding period expires. + - This triggers `release_stake` messages to propagate up to the provider. + +2. **Administrative Actions:** + - **DefineFlow (Fan Out):** Changing outflows creates unbonding claims for old outflows. + - **SetOperator (Operators):** Changing operators results in unbonding claims for the old operator. + - **DefineDelegates (Delegations):** Modifying delegation configuration creates unbonding claims that are finalized via `ReleaseUnbonded`. + +## Questions + - when it comes to validating the request by the `provider`, should the `provider` always trust the `consumer's` request (`consumer` is always whitelisted) or the `provider` should do additional validation - eg, check that the request doesn't exceed what's already staked? + - do we allow for partial `release_stake` - eg, a consumer would like to free 1/3 of the staked tokens + - for `token_weighting` contract: what happens when the token-weighting contract receieve a msg to release stake when it's currently calculating new weight? Basically both requests in the same block + - for `fan-out` contract: what if we change the denom and request `release_stake` on the old name? It will fail ofc, but I guess we have to call the old denom name. + - for `operators` contract: that's for the future, but shouldn't we check if an operator is being slashed, before releasing the stake, there might be better options? Another one is to proceed with the removal of operators and their stake \ No newline at end of file