-
Notifications
You must be signed in to change notification settings - Fork 208
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
465 additions
and
433 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package keeper | ||
|
||
import ( | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
|
||
icqtypes "github.com/Stride-Labs/stride/x/interchainquery/types" | ||
) | ||
|
||
const ( | ||
ICQCallbackID_WithdrawalBalance = "withdrawalbalance" | ||
ICQCallbackID_Delegation = "delegation" | ||
ICQCallbackID_Validator = "validator" | ||
) | ||
|
||
// ICQCallbacks wrapper struct for stakeibc keeper | ||
type ICQCallback func(Keeper, sdk.Context, []byte, icqtypes.Query) error | ||
|
||
type ICQCallbacks struct { | ||
k Keeper | ||
callbacks map[string]ICQCallback | ||
} | ||
|
||
var _ icqtypes.QueryCallbacks = ICQCallbacks{} | ||
|
||
func (k Keeper) ICQCallbackHandler() ICQCallbacks { | ||
return ICQCallbacks{k, make(map[string]ICQCallback)} | ||
} | ||
|
||
func (c ICQCallbacks) CallICQCallback(ctx sdk.Context, id string, args []byte, query icqtypes.Query) error { | ||
return c.callbacks[id](c.k, ctx, args, query) | ||
} | ||
|
||
func (c ICQCallbacks) HasICQCallback(id string) bool { | ||
_, found := c.callbacks[id] | ||
return found | ||
} | ||
|
||
func (c ICQCallbacks) AddICQCallback(id string, fn interface{}) icqtypes.QueryCallbacks { | ||
c.callbacks[id] = fn.(ICQCallback) | ||
return c | ||
} | ||
|
||
func (c ICQCallbacks) RegisterICQCallbacks() icqtypes.QueryCallbacks { | ||
return c. | ||
AddICQCallback(ICQCallbackID_WithdrawalBalance, ICQCallback(WithdrawalBalanceCallback)). | ||
AddICQCallback(ICQCallbackID_Delegation, ICQCallback(DelegatorSharesCallback)). | ||
AddICQCallback(ICQCallbackID_Validator, ICQCallback(ValidatorExchangeRateCallback)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
package keeper | ||
|
||
import ( | ||
"fmt" | ||
|
||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" | ||
"github.com/spf13/cast" | ||
|
||
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" | ||
|
||
epochtypes "github.com/Stride-Labs/stride/x/epochs/types" | ||
icqtypes "github.com/Stride-Labs/stride/x/interchainquery/types" | ||
"github.com/Stride-Labs/stride/x/stakeibc/types" | ||
) | ||
|
||
// DelegatorSharesCallback is a callback handler for UpdateValidatorSharesExchRate queries. | ||
// | ||
// In an attempt to get the ICA's delegation amount on a given validator, we have to query: | ||
// 1. the validator's internal exchange rate | ||
// 2. the Delegation ICA's delegated shares | ||
// And apply the following equation: | ||
// num_tokens = exchange_rate * num_shares | ||
// | ||
// This is the callback from query #2 | ||
// | ||
// Note: for now, to get proofs in your ICQs, you need to query the entire store on the host zone! e.g. "store/bank/key" | ||
func DelegatorSharesCallback(k Keeper, ctx sdk.Context, args []byte, query icqtypes.Query) error { | ||
hostZone, found := k.GetHostZone(ctx, query.GetChainId()) | ||
if !found { | ||
errMsg := fmt.Sprintf("no registered zone for queried chain ID (%s)", query.GetChainId()) | ||
k.Logger(ctx).Error(errMsg) | ||
return sdkerrors.Wrapf(types.ErrHostZoneNotFound, errMsg) | ||
} | ||
|
||
// Unmarshal the query response which returns a delegation object for the delegator/validator pair | ||
queriedDelgation := stakingtypes.Delegation{} | ||
err := k.cdc.Unmarshal(args, &queriedDelgation) | ||
if err != nil { | ||
errMsg := fmt.Sprintf("unable to unmarshal queried delegation info for zone %s, err: %s", hostZone.ChainId, err.Error()) | ||
k.Logger(ctx).Error(errMsg) | ||
return sdkerrors.Wrapf(types.ErrMarshalFailure, errMsg) | ||
} | ||
k.Logger(ctx).Info(fmt.Sprintf("DelegationCallback: HostZone: %s, Delegator: %s, Validator: %s, Shares: %v", | ||
hostZone.ChainId, queriedDelgation.DelegatorAddress, queriedDelgation.ValidatorAddress, queriedDelgation.Shares)) | ||
|
||
// ensure ICQ can be issued now! else fail the callback | ||
isWithinWindow, err := k.IsWithinBufferWindow(ctx) | ||
if err != nil { | ||
errMsg := fmt.Sprintf("unable to determine if ICQ callback is inside buffer window, err: %s", err.Error()) | ||
k.Logger(ctx).Error(errMsg) | ||
return sdkerrors.Wrapf(types.ErrOutsideIcqWindow, errMsg) | ||
} else if !isWithinWindow { | ||
k.Logger(ctx).Error("delegator shares callback is outside ICQ window") | ||
return nil | ||
} | ||
|
||
// Grab the validator object form the hostZone using the address returned from the query | ||
validator, valIndex, found := GetValidatorFromAddress(hostZone.Validators, queriedDelgation.ValidatorAddress) | ||
if !found { | ||
errMsg := fmt.Sprintf("no registered validator for address (%s)", queriedDelgation.ValidatorAddress) | ||
k.Logger(ctx).Error(errMsg) | ||
return sdkerrors.Wrapf(types.ErrValidatorNotFound, errMsg) | ||
} | ||
|
||
// get the validator's internal exchange rate, aborting if it hasn't been updated this epoch | ||
strideEpochTracker, found := k.GetEpochTracker(ctx, epochtypes.STRIDE_EPOCH) | ||
if !found { | ||
k.Logger(ctx).Error("failed to find stride epoch") | ||
return sdkerrors.Wrapf(sdkerrors.ErrNotFound, "no epoch number for epoch (%s)", epochtypes.STRIDE_EPOCH) | ||
} | ||
if validator.InternalExchangeRate.EpochNumber != strideEpochTracker.GetEpochNumber() { | ||
errMsg := fmt.Sprintf("DelegationCallback: validator (%s) internal exchange rate has not been updated this epoch (epoch #%d)", | ||
validator.Address, strideEpochTracker.GetEpochNumber()) | ||
k.Logger(ctx).Error(errMsg) | ||
return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, errMsg) | ||
} | ||
|
||
// TODO: make sure conversion math precision matches the sdk's staking module's version (we did it slightly differently) | ||
// note: truncateInt per https://github.com/cosmos/cosmos-sdk/blob/cb31043d35bad90c4daa923bb109f38fd092feda/x/staking/types/validator.go#L431 | ||
validatorTokens := queriedDelgation.Shares.Mul(validator.InternalExchangeRate.InternalTokensToSharesRate).TruncateInt() | ||
k.Logger(ctx).Info(fmt.Sprintf("DelegationCallback: HostZone: %s, Validator: %s, Previous NumTokens: %d, Current NumTokens: %v", | ||
hostZone.ChainId, validator.Address, validator.DelegationAmt, validatorTokens)) | ||
|
||
// Confirm the validator has actually been slashed | ||
if validatorTokens.Uint64() == validator.DelegationAmt { | ||
k.Logger(ctx).Info(fmt.Sprintf("DelegationCallback: Validator (%s) was not slashed", validator.Address)) | ||
return nil | ||
} else if validatorTokens.Uint64() > validator.DelegationAmt { | ||
errMsg := fmt.Sprintf("DelegationCallback: Validator (%s) tokens returned from query is greater than the DelegationAmt", validator.Address) | ||
k.Logger(ctx).Error(errMsg) | ||
return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, errMsg) | ||
} | ||
|
||
// TODO(TESTS-171) add some safety checks here (e.g. we could query the slashing module to confirm the decr in tokens was due to slash) | ||
// update our records of the total stakedbal and of the validator's delegation amt | ||
// NOTE: we assume any decrease in delegation amt that's not tracked via records is a slash | ||
|
||
// Get slash percentage | ||
delegationAmount, err := cast.ToInt64E(validator.DelegationAmt) | ||
if err != nil { | ||
errMsg := fmt.Sprintf("unable to convert validator delegation amount to int64, err: %s", err.Error()) | ||
k.Logger(ctx).Error(errMsg) | ||
return sdkerrors.Wrapf(types.ErrIntCast, errMsg) | ||
} | ||
slashAmountUInt := validator.DelegationAmt - validatorTokens.Uint64() | ||
slashAmount, err := cast.ToInt64E(slashAmountUInt) | ||
if err != nil { | ||
errMsg := fmt.Sprintf("unable to convert validator slash amount to int64, err: %s", err.Error()) | ||
k.Logger(ctx).Error(errMsg) | ||
return sdkerrors.Wrapf(types.ErrIntCast, errMsg) | ||
} | ||
weight, err := cast.ToInt64E(validator.Weight) | ||
if err != nil { | ||
errMsg := fmt.Sprintf("unable to convert validator weight to int64, err: %s", err.Error()) | ||
k.Logger(ctx).Error(errMsg) | ||
return sdkerrors.Wrapf(types.ErrIntCast, errMsg) | ||
} | ||
|
||
slashPct := sdk.NewDec(slashAmount).Quo(sdk.NewDec(delegationAmount)) | ||
k.Logger(ctx).Info(fmt.Sprintf("ICQ'd Delegation Amoount Mismatch, HostZone: %s, Validator: %s, Delegator: %s, Records Tokens: %d, Tokens from ICQ %v, Slash Amount: %d, Slash Pct: %v!", | ||
hostZone.ChainId, validator.Address, queriedDelgation.DelegatorAddress, validator.DelegationAmt, validatorTokens, slashAmount, slashPct)) | ||
|
||
// Abort if the slash was greater than 10% | ||
tenPercent := sdk.NewDec(10).Quo(sdk.NewDec(100)) | ||
if slashPct.GT(tenPercent) { | ||
errMsg := fmt.Sprintf("DelegationCallback: Validator (%s) slashed but ABORTING update, slash is greater than 0.10 (%d)", validator.Address, slashPct) | ||
k.Logger(ctx).Error(errMsg) | ||
return sdkerrors.Wrapf(types.ErrSlashGtTenPct, errMsg) | ||
} | ||
|
||
// Update the host zone and validator to reflect the weight and delegation change | ||
weightAdjustment := sdk.NewDec(validatorTokens.Int64()).Quo(sdk.NewDec(delegationAmount)) | ||
validator.Weight = sdk.NewDec(weight).Mul(weightAdjustment).TruncateInt().Uint64() | ||
validator.DelegationAmt -= slashAmountUInt | ||
|
||
hostZone.StakedBal -= slashAmountUInt | ||
hostZone.Validators[valIndex] = &validator | ||
k.SetHostZone(ctx, hostZone) | ||
|
||
k.Logger(ctx).Info(fmt.Sprintf("Validator (%s) slashed! Delegation updated to: %v", validator.Address, validator.DelegationAmt)) | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
package keeper | ||
|
||
import ( | ||
"fmt" | ||
|
||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" | ||
|
||
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" | ||
|
||
epochtypes "github.com/Stride-Labs/stride/x/epochs/types" | ||
icqtypes "github.com/Stride-Labs/stride/x/interchainquery/types" | ||
"github.com/Stride-Labs/stride/x/stakeibc/types" | ||
) | ||
|
||
// ValidatorCallback is a callback handler for validator queries. | ||
// | ||
// In an attempt to get the ICA's delegation amount on a given validator, we have to query: | ||
// 1. the validator's internal exchange rate | ||
// 2. the Delegation ICA's delegated shares | ||
// And apply the following equation: | ||
// num_tokens = exchange_rate * num_shares | ||
// | ||
// This is the callback from query #1 | ||
func ValidatorExchangeRateCallback(k Keeper, ctx sdk.Context, args []byte, query icqtypes.Query) error { | ||
hostZone, found := k.GetHostZone(ctx, query.GetChainId()) | ||
if !found { | ||
errMsg := fmt.Sprintf("no registered zone for queried chain ID (%s)", query.GetChainId()) | ||
k.Logger(ctx).Error(errMsg) | ||
return sdkerrors.Wrapf(types.ErrHostZoneNotFound, errMsg) | ||
} | ||
queriedValidator := stakingtypes.Validator{} | ||
err := k.cdc.Unmarshal(args, &queriedValidator) | ||
if err != nil { | ||
errMsg := fmt.Sprintf("unable to unmarshal queriedValidator info for zone %s, err: %s", hostZone.ChainId, err.Error()) | ||
k.Logger(ctx).Error(errMsg) | ||
return sdkerrors.Wrapf(types.ErrMarshalFailure, errMsg) | ||
} | ||
k.Logger(ctx).Info(fmt.Sprintf("ValidatorCallback: HostZone %s, Queried Validator %v, Jailed: %v, Tokens: %v, Shares: %v", | ||
hostZone.ChainId, queriedValidator.OperatorAddress, queriedValidator.Jailed, queriedValidator.Tokens, queriedValidator.DelegatorShares)) | ||
|
||
// ensure ICQ can be issued now! else fail the callback | ||
withinBufferWindow, err := k.IsWithinBufferWindow(ctx) | ||
if err != nil { | ||
errMsg := fmt.Sprintf("unable to determine if ICQ callback is inside buffer window, err: %s", err.Error()) | ||
k.Logger(ctx).Error(errMsg) | ||
return sdkerrors.Wrapf(types.ErrOutsideIcqWindow, errMsg) | ||
} else if !withinBufferWindow { | ||
k.Logger(ctx).Error("validator exchange rate callback is outside ICQ window") | ||
return nil | ||
} | ||
|
||
// get the validator from the host zone | ||
validator, valIndex, found := GetValidatorFromAddress(hostZone.Validators, queriedValidator.OperatorAddress) | ||
if !found { | ||
errMsg := fmt.Sprintf("no registered validator for address (%s)", queriedValidator.OperatorAddress) | ||
k.Logger(ctx).Error(errMsg) | ||
return sdkerrors.Wrapf(types.ErrValidatorNotFound, errMsg) | ||
} | ||
// get the stride epoch number | ||
strideEpochTracker, found := k.GetEpochTracker(ctx, epochtypes.STRIDE_EPOCH) | ||
if !found { | ||
k.Logger(ctx).Error("failed to find stride epoch") | ||
return sdkerrors.Wrapf(sdkerrors.ErrNotFound, "no epoch number for epoch (%s)", epochtypes.STRIDE_EPOCH) | ||
} | ||
|
||
// If the validator's delegation shares is 0, we'll get a division by zero error when trying to get the exchange rate | ||
// because `validator.TokensFromShares` uses delegation shares in the denominator | ||
if queriedValidator.DelegatorShares.IsZero() { | ||
errMsg := fmt.Sprintf("can't calculate validator internal exchange rate because delegation amount is 0 (validator: %s)", validator.Address) | ||
k.Logger(ctx).Error(errMsg) | ||
return sdkerrors.Wrapf(types.ErrDivisionByZero, errMsg) | ||
} | ||
|
||
// We want the validator's internal exchange rate which is held internally behind `validator.TokensFromShares` | ||
// Since, | ||
// exchange_rate = num_tokens / num_shares | ||
// We can use `validator.TokensFromShares`, plug in 1.0 for the number of shares, | ||
// and the returned number of tokens will be equal to the internal exchange rate | ||
validator.InternalExchangeRate = &types.ValidatorExchangeRate{ | ||
InternalTokensToSharesRate: queriedValidator.TokensFromShares(sdk.NewDec(1.0)), | ||
EpochNumber: strideEpochTracker.GetEpochNumber(), | ||
} | ||
hostZone.Validators[valIndex] = &validator | ||
k.SetHostZone(ctx, hostZone) | ||
|
||
k.Logger(ctx).Info(fmt.Sprintf("ValidatorCallback: HostZone %s, Validator %v, tokensFromShares %v", | ||
hostZone.ChainId, validator.Address, validator.InternalExchangeRate.InternalTokensToSharesRate)) | ||
|
||
// armed with the exch rate, we can now query the (val,del) delegation | ||
err = k.QueryDelegationsIcq(ctx, hostZone, queriedValidator.OperatorAddress) | ||
if err != nil { | ||
errMsg := fmt.Sprintf("ValidatorCallback: failed to query delegation, zone %s, err: %s", hostZone.ChainId, err.Error()) | ||
k.Logger(ctx).Error(errMsg) | ||
return sdkerrors.Wrapf(types.ErrICQFailed, errMsg) | ||
} | ||
return nil | ||
} |
Oops, something went wrong.