-
Notifications
You must be signed in to change notification settings - Fork 193
/
redemption_rate.go
162 lines (138 loc) · 7.15 KB
/
redemption_rate.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
package keeper
import (
"encoding/json"
"fmt"
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/Stride-Labs/stride/v19/utils"
icaoracletypes "github.com/Stride-Labs/stride/v19/x/icaoracle/types"
recordstypes "github.com/Stride-Labs/stride/v19/x/records/types"
"github.com/Stride-Labs/stride/v19/x/stakeibc/types"
)
// 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
}