Skip to content

Commit

Permalink
stakeibc file re-org part 3 (ibc, ica, icq, and utilities) (#1116)
Browse files Browse the repository at this point in the history
  • Loading branch information
sampocs committed Mar 2, 2024
1 parent 731e28b commit 16a15d4
Show file tree
Hide file tree
Showing 13 changed files with 689 additions and 647 deletions.
80 changes: 0 additions & 80 deletions x/stakeibc/keeper/get_light_client_safely_test.go

This file was deleted.

153 changes: 0 additions & 153 deletions x/stakeibc/keeper/hooks.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
package keeper

import (
"encoding/json"
"fmt"
"time"

sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/gogoproto/proto"

"github.com/Stride-Labs/stride/v18/utils"
epochstypes "github.com/Stride-Labs/stride/v18/x/epochs/types"
icaoracletypes "github.com/Stride-Labs/stride/v18/x/icaoracle/types"
recordstypes "github.com/Stride-Labs/stride/v18/x/records/types"
"github.com/Stride-Labs/stride/v18/x/stakeibc/types"
)

Expand Down Expand Up @@ -148,154 +143,6 @@ func (k Keeper) ClaimAccruedStakingRewards(ctx sdk.Context) {
}
}

// Updates the redemption rate for each host zone
// At a high level, the redemption rate is equal to the amount of native tokens locked divided by the stTokens in existence.
// The equation is broken down further into the following sub-components:
//
// Native Tokens Locked:
// 1. Deposit Account Balance: native tokens deposited from liquid stakes, that are still living on Stride
// 2. Undelegated Balance: native tokens that have been transferred to the host zone, but have not been delegated yet
// 3. Tokenized Delegations: Delegations inherent in LSM Tokens that have not yet been converted to native stake
// 4. Native Delegations: Delegations either from native tokens, or LSM Tokens that have been detokenized
// StToken Amount:
// 1. Total Supply of the stToken
//
// Redemption Rate =
// (Deposit Account Balance + Undelegated Balance + Tokenized Delegation + Native Delegation) / (stToken Supply)
func (k Keeper) UpdateRedemptionRates(ctx sdk.Context, depositRecords []recordstypes.DepositRecord) {
k.Logger(ctx).Info("Updating Redemption Rates...")

// Update the redemption rate for each host zone
for _, hostZone := range k.GetAllActiveHostZone(ctx) {
k.UpdateRedemptionRateForHostZone(ctx, hostZone, depositRecords)
}
}

func (k Keeper) UpdateRedemptionRateForHostZone(ctx sdk.Context, hostZone types.HostZone, depositRecords []recordstypes.DepositRecord) {
// Gather redemption rate components
stSupply := k.bankKeeper.GetSupply(ctx, types.StAssetDenomFromHostZoneDenom(hostZone.HostDenom)).Amount
if stSupply.IsZero() {
k.Logger(ctx).Info(utils.LogWithHostZone(hostZone.ChainId,
"No st%s in circulation - redemption rate is unchanged", hostZone.HostDenom))
return
}

depositAccountBalance := k.GetDepositAccountBalance(hostZone.ChainId, depositRecords)
undelegatedBalance := k.GetUndelegatedBalance(hostZone.ChainId, depositRecords)
tokenizedDelegation := k.GetTotalTokenizedDelegations(ctx, hostZone)
nativeDelegation := sdk.NewDecFromInt(hostZone.TotalDelegations)

k.Logger(ctx).Info(utils.LogWithHostZone(hostZone.ChainId,
"Redemption Rate Components - Deposit Account Balance: %v, Undelegated Balance: %v, "+
"LSM Delegated Balance: %v, Native Delegations: %v, stToken Supply: %v",
depositAccountBalance, undelegatedBalance, tokenizedDelegation,
nativeDelegation, stSupply))

// Calculate the redemption rate
nativeTokensLocked := depositAccountBalance.Add(undelegatedBalance).Add(tokenizedDelegation).Add(nativeDelegation)
redemptionRate := nativeTokensLocked.Quo(sdk.NewDecFromInt(stSupply))

k.Logger(ctx).Info(utils.LogWithHostZone(hostZone.ChainId,
"New Redemption Rate: %v (vs Prev Rate: %v)", redemptionRate, hostZone.RedemptionRate))

// Update the host zone
hostZone.LastRedemptionRate = hostZone.RedemptionRate
hostZone.RedemptionRate = redemptionRate
k.SetHostZone(ctx, hostZone)

// If the redemption rate is outside of safety bounds, exit so the redemption rate is not pushed to the oracle
redemptionRateSafe, _ := k.IsRedemptionRateWithinSafetyBounds(ctx, hostZone)
if !redemptionRateSafe {
return
}

// Otherwise, submit the redemption rate to the oracle
if err := k.PostRedemptionRateToOracles(ctx, hostZone.HostDenom, redemptionRate); err != nil {
k.Logger(ctx).Error(fmt.Sprintf("Unable to send redemption rate to oracle: %s", err.Error()))
return
}
}

// Determine the deposit account balance, representing native tokens that have been deposited
// from liquid stakes, but have not yet been transferred to the host
func (k Keeper) GetDepositAccountBalance(chainId string, depositRecords []recordstypes.DepositRecord) sdk.Dec {
// sum on deposit records with status TRANSFER_QUEUE or TRANSFER_IN_PROGRESS
totalAmount := sdkmath.ZeroInt()
for _, depositRecord := range depositRecords {
transferStatus := (depositRecord.Status == recordstypes.DepositRecord_TRANSFER_QUEUE ||
depositRecord.Status == recordstypes.DepositRecord_TRANSFER_IN_PROGRESS)

if depositRecord.HostZoneId == chainId && transferStatus {
totalAmount = totalAmount.Add(depositRecord.Amount)
}
}

return sdk.NewDecFromInt(totalAmount)
}

// Determine the undelegated balance from the deposit records queued for staking
func (k Keeper) GetUndelegatedBalance(chainId string, depositRecords []recordstypes.DepositRecord) sdk.Dec {
// sum on deposit records with status DELEGATION_QUEUE or DELEGATION_IN_PROGRESS
totalAmount := sdkmath.ZeroInt()
for _, depositRecord := range depositRecords {
delegationStatus := (depositRecord.Status == recordstypes.DepositRecord_DELEGATION_QUEUE ||
depositRecord.Status == recordstypes.DepositRecord_DELEGATION_IN_PROGRESS)

if depositRecord.HostZoneId == chainId && delegationStatus {
totalAmount = totalAmount.Add(depositRecord.Amount)
}
}

return sdk.NewDecFromInt(totalAmount)
}

// Returns the total delegated balance that's stored in LSM tokens
// This is used for the redemption rate calculation
//
// The relevant tokens are identified by the deposit records in status "DEPOSIT_PENDING"
// "DEPOSIT_PENDING" means the liquid staker's tokens have not been sent to Stride yet
// so they should *not* be included in the redemption rate. All other statuses indicate
// the LSM tokens have been deposited and should be included in the final calculation
//
// Each LSM token represents a delegator share so the validator's shares to tokens rate
// must be used to denominate it's value in native tokens
func (k Keeper) GetTotalTokenizedDelegations(ctx sdk.Context, hostZone types.HostZone) sdk.Dec {
total := sdkmath.ZeroInt()
for _, deposit := range k.RecordsKeeper.GetLSMDepositsForHostZone(ctx, hostZone.ChainId) {
if deposit.Status != recordstypes.LSMTokenDeposit_DEPOSIT_PENDING {
validator, _, found := GetValidatorFromAddress(hostZone.Validators, deposit.ValidatorAddress)
if !found {
k.Logger(ctx).Error(fmt.Sprintf("Validator %s found in LSMTokenDeposit but no longer exists", deposit.ValidatorAddress))
continue
}
liquidStakedShares := deposit.Amount
liquidStakedTokens := sdk.NewDecFromInt(liquidStakedShares).Mul(validator.SharesToTokensRate)
total = total.Add(liquidStakedTokens.TruncateInt())
}
}

return sdk.NewDecFromInt(total)
}

// Pushes a redemption rate update to the ICA oracle
func (k Keeper) PostRedemptionRateToOracles(ctx sdk.Context, hostDenom string, redemptionRate sdk.Dec) error {
stDenom := types.StAssetDenomFromHostZoneDenom(hostDenom)
attributes, err := json.Marshal(icaoracletypes.RedemptionRateAttributes{
SttokenDenom: stDenom,
})
if err != nil {
return err
}

// Metric Key is of format: {stToken}_redemption_rate
metricKey := fmt.Sprintf("%s_%s", stDenom, icaoracletypes.MetricType_RedemptionRate)
metricValue := redemptionRate.String()
metricType := icaoracletypes.MetricType_RedemptionRate
k.ICAOracleKeeper.QueueMetricUpdate(ctx, metricKey, metricValue, metricType, string(attributes))

return nil
}

// TODO [cleanup]: Remove after v17 upgrade
func (k Keeper) DisableHubTokenization(ctx sdk.Context) {
k.Logger(ctx).Info("Disabling the ability to tokenize Gaia delegations")
Expand Down
71 changes: 71 additions & 0 deletions x/stakeibc/keeper/ibc.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@ package keeper
import (
"fmt"

errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
icatypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/types"
clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types"
connectiontypes "github.com/cosmos/ibc-go/v7/modules/core/03-connection/types"
ibctmtypes "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint"
"github.com/spf13/cast"

ratelimittypes "github.com/Stride-Labs/stride/v18/x/ratelimit/types"
"github.com/Stride-Labs/stride/v18/x/stakeibc/types"
Expand Down Expand Up @@ -192,3 +197,69 @@ func (k Keeper) StoreTradeRouteIcaAddress(ctx sdk.Context, callbackChainId, call

return nil
}

// TODO [cleanup]: Cleanup error messages, and rename to GetLightClientTime
// Retrieves the light client time for a given connection
func (k Keeper) GetLightClientTimeSafely(ctx sdk.Context, connectionID string) (uint64, error) {
// get light client's latest height
conn, found := k.IBCKeeper.ConnectionKeeper.GetConnection(ctx, connectionID)
if !found {
errMsg := fmt.Sprintf("invalid connection id, %s not found", connectionID)
k.Logger(ctx).Error(errMsg)
return 0, fmt.Errorf(errMsg)
}
// TODO(TEST-112) make sure to update host LCs here!
latestConsensusClientState, found := k.IBCKeeper.ClientKeeper.GetLatestClientConsensusState(ctx, conn.ClientId)
if !found {
errMsg := fmt.Sprintf("client id %s not found for connection %s", conn.ClientId, connectionID)
k.Logger(ctx).Error(errMsg)
return 0, fmt.Errorf(errMsg)
} else {
latestTime := latestConsensusClientState.GetTimestamp()
return latestTime, nil
}
}

// TODO [cleanup]: Cleanup error messages, and rename to GetLightClientHeight
// Retrieves the light client time for a given connection
func (k Keeper) GetLightClientHeightSafely(ctx sdk.Context, connectionID string) (uint64, error) {
// get light client's latest height
conn, found := k.IBCKeeper.ConnectionKeeper.GetConnection(ctx, connectionID)
if !found {
errMsg := fmt.Sprintf("invalid connection id, %s not found", connectionID)
k.Logger(ctx).Error(errMsg)
return 0, fmt.Errorf(errMsg)
}
clientState, found := k.IBCKeeper.ClientKeeper.GetClientState(ctx, conn.ClientId)
if !found {
errMsg := fmt.Sprintf("client id %s not found for connection %s", conn.ClientId, connectionID)
k.Logger(ctx).Error(errMsg)
return 0, fmt.Errorf(errMsg)
} else {
latestHeightHostZone, err := cast.ToUint64E(clientState.GetLatestHeight().GetRevisionHeight())
if err != nil {
errMsg := fmt.Sprintf("error casting latest height to int64: %s", err.Error())
k.Logger(ctx).Error(errMsg)
return 0, fmt.Errorf(errMsg)
}
return latestHeightHostZone, nil
}
}

// Lookup a chain ID from a connection ID by looking up the client state
func (k Keeper) GetChainIdFromConnectionId(ctx sdk.Context, connectionID string) (string, error) {
connection, found := k.IBCKeeper.ConnectionKeeper.GetConnection(ctx, connectionID)
if !found {
return "", errorsmod.Wrapf(connectiontypes.ErrConnectionNotFound, "connection %s not found", connectionID)
}
clientState, found := k.IBCKeeper.ClientKeeper.GetClientState(ctx, connection.ClientId)
if !found {
return "", errorsmod.Wrapf(clienttypes.ErrClientNotFound, "client %s not found", connection.ClientId)
}
client, ok := clientState.(*ibctmtypes.ClientState)
if !ok {
return "", types.ErrClientStateNotTendermint
}

return client.ChainId, nil
}
Loading

0 comments on commit 16a15d4

Please sign in to comment.