Skip to content

Commit

Permalink
stakeibc file re-org part 5 (msg_server) (#1120)
Browse files Browse the repository at this point in the history
  • Loading branch information
sampocs authored Mar 4, 2024
1 parent c991249 commit eed8266
Show file tree
Hide file tree
Showing 35 changed files with 3,869 additions and 4,146 deletions.
80 changes: 70 additions & 10 deletions x/stakeibc/keeper/interchainaccounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,19 @@ import (
"fmt"

errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
"github.com/cosmos/gogoproto/proto"
icacontrollerkeeper "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/controller/keeper"
icacontrollertypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/controller/types"
icatypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/types"
connectiontypes "github.com/cosmos/ibc-go/v7/modules/core/03-connection/types"

"github.com/Stride-Labs/stride/v18/utils"
epochstypes "github.com/Stride-Labs/stride/v18/x/epochs/types"
icacallbackstypes "github.com/Stride-Labs/stride/v18/x/icacallbacks/types"

"github.com/Stride-Labs/stride/v18/x/stakeibc/types"

distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types"

epochstypes "github.com/Stride-Labs/stride/v18/x/epochs/types"

sdk "github.com/cosmos/cosmos-sdk/types"
icacontrollerkeeper "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/controller/keeper"
icacontrollertypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/controller/types"
icatypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/types"
)

const (
Expand Down Expand Up @@ -262,3 +259,66 @@ func (k Keeper) SubmitICATxWithoutCallback(

return nil
}

// Registers a new TradeRoute ICAAccount, given the type
// Stores down the connection and chainId now, and the address upon callback
func (k Keeper) RegisterTradeRouteICAAccount(
ctx sdk.Context,
tradeRouteId string,
connectionId string,
icaAccountType types.ICAAccountType,
) (account types.ICAAccount, err error) {
// Get the chain ID and counterparty connection-id from the connection ID on Stride
chainId, err := k.GetChainIdFromConnectionId(ctx, connectionId)
if err != nil {
return account, err
}
connection, found := k.IBCKeeper.ConnectionKeeper.GetConnection(ctx, connectionId)
if !found {
return account, errorsmod.Wrap(connectiontypes.ErrConnectionNotFound, connectionId)
}
counterpartyConnectionId := connection.Counterparty.ConnectionId

// Build the appVersion, owner, and portId needed for registration
appVersion := string(icatypes.ModuleCdc.MustMarshalJSON(&icatypes.Metadata{
Version: icatypes.Version,
ControllerConnectionId: connectionId,
HostConnectionId: counterpartyConnectionId,
Encoding: icatypes.EncodingProtobuf,
TxType: icatypes.TxTypeSDKMultiMsg,
}))
owner := types.FormatTradeRouteICAOwnerFromRouteId(chainId, tradeRouteId, icaAccountType)
portID, err := icatypes.NewControllerPortID(owner)
if err != nil {
return account, err
}

// Create the associate ICAAccount object
account = types.ICAAccount{
ChainId: chainId,
Type: icaAccountType,
ConnectionId: connectionId,
}

// Check if an ICA account has already been created
// (in the event that this trade route was removed and then added back)
// If so, there's no need to register a new ICA
_, channelFound := k.ICAControllerKeeper.GetOpenActiveChannel(ctx, connectionId, portID)
icaAddress, icaFound := k.ICAControllerKeeper.GetInterchainAccountAddress(ctx, connectionId, portID)
if channelFound && icaFound {
account = types.ICAAccount{
ChainId: chainId,
Type: icaAccountType,
ConnectionId: connectionId,
Address: icaAddress,
}
return account, nil
}

// Otherwise, if there's no account already, register a new one
if err := k.ICAControllerKeeper.RegisterInterchainAccount(ctx, connectionId, owner, appVersion); err != nil {
return account, err
}

return account, nil
}
134 changes: 134 additions & 0 deletions x/stakeibc/keeper/lsm.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (
transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types"
channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types"

icqtypes "github.com/Stride-Labs/stride/v18/x/interchainquery/types"

recordstypes "github.com/Stride-Labs/stride/v18/x/records/types"
"github.com/Stride-Labs/stride/v18/x/stakeibc/types"
)
Expand Down Expand Up @@ -238,6 +240,138 @@ func (k Keeper) ShouldCheckIfValidatorWasSlashed(
return oldInterval.LT(newInterval)
}

// StartLSMLiquidStake runs the transactional logic that occurs before the optional query
// This includes validation on the LSM Token and the stToken amount calculation
func (k Keeper) StartLSMLiquidStake(ctx sdk.Context, msg types.MsgLSMLiquidStake) (types.LSMLiquidStake, error) {
// Validate the provided message parameters - including the denom and staker balance
lsmLiquidStake, err := k.ValidateLSMLiquidStake(ctx, msg)
if err != nil {
return types.LSMLiquidStake{}, err
}
hostZone := lsmLiquidStake.HostZone

if hostZone.Halted {
return types.LSMLiquidStake{}, errorsmod.Wrapf(types.ErrHaltedHostZone, "host zone %s is halted", hostZone.ChainId)
}

// Check if we already have tokens with this denom in records
_, found := k.RecordsKeeper.GetLSMTokenDeposit(ctx, hostZone.ChainId, lsmLiquidStake.Deposit.Denom)
if found {
return types.LSMLiquidStake{}, errorsmod.Wrapf(sdkerrors.ErrInvalidRequest,
"there is already a previous record with this denom being processed: %s", lsmLiquidStake.Deposit.Denom)
}

// Determine the amount of stTokens to mint using the redemption rate and the validator's sharesToTokens rate
// StTokens = LSMTokenShares * Validator SharesToTokens Rate / Redemption Rate
// Note: in the event of a slash query, these tokens will be minted only if the
// validator's sharesToTokens rate did not change
stCoin := k.CalculateLSMStToken(msg.Amount, lsmLiquidStake)
if stCoin.Amount.IsZero() {
return types.LSMLiquidStake{}, errorsmod.Wrapf(types.ErrInsufficientLiquidStake,
"Liquid stake of %s%s would return 0 stTokens", msg.Amount.String(), hostZone.HostDenom)
}

// Add the stToken to this deposit record
lsmLiquidStake.Deposit.StToken = stCoin
k.RecordsKeeper.SetLSMTokenDeposit(ctx, *lsmLiquidStake.Deposit)

return lsmLiquidStake, nil
}

// SubmitValidatorSlashQuery submits an interchain query for the validator's sharesToTokens rate
// This is done periodically at checkpoints denominated in native tokens
// (e.g. every 100k ATOM that's LSM liquid staked with validator X)
func (k Keeper) SubmitValidatorSlashQuery(ctx sdk.Context, lsmLiquidStake types.LSMLiquidStake) error {
chainId := lsmLiquidStake.HostZone.ChainId
validatorAddress := lsmLiquidStake.Validator.Address
timeoutDuration := LSMSlashQueryTimeout
timeoutPolicy := icqtypes.TimeoutPolicy_EXECUTE_QUERY_CALLBACK

// Build and serialize the callback data required to complete the LSM Liquid stake upon query callback
callbackData := types.ValidatorSharesToTokensQueryCallback{
LsmLiquidStake: &lsmLiquidStake,
}
callbackDataBz, err := proto.Marshal(&callbackData)
if err != nil {
return errorsmod.Wrapf(err, "unable to serialize LSMLiquidStake struct for validator sharesToTokens rate query callback")
}

return k.SubmitValidatorSharesToTokensRateICQ(ctx, chainId, validatorAddress, callbackDataBz, timeoutDuration, timeoutPolicy)
}

// FinishLSMLiquidStake finishes the liquid staking flow by escrowing the LSM token,
// sending a user their stToken, and then IBC transfering the LSM Token to the host zone
//
// If the slash query interrupted the transaction, this function is called
// asynchronously after the query callback
//
// If no slash query was needed, this is called synchronously after StartLSMLiquidStake
// If this is run asynchronously, we need to re-validate the transaction info (e.g. staker's balance)
func (k Keeper) FinishLSMLiquidStake(ctx sdk.Context, lsmLiquidStake types.LSMLiquidStake, async bool) error {
hostZone := lsmLiquidStake.HostZone
lsmTokenDeposit := *lsmLiquidStake.Deposit

// If the transaction was interrupted by the slash query,
// validate the LSM Liquid stake message parameters again
// The most significant check here is that the user still has sufficient balance for this LSM liquid stake
if async {
lsmLiquidStakeMsg := types.MsgLSMLiquidStake{
Creator: lsmTokenDeposit.StakerAddress,
LsmTokenIbcDenom: lsmTokenDeposit.IbcDenom,
Amount: lsmTokenDeposit.Amount,
}
if _, err := k.ValidateLSMLiquidStake(ctx, lsmLiquidStakeMsg); err != nil {
return err
}
}

// Get the staker's address and the host zone's deposit account address (which will custody the tokens)
liquidStakerAddress := sdk.MustAccAddressFromBech32(lsmTokenDeposit.StakerAddress)
hostZoneDepositAddress, err := sdk.AccAddressFromBech32(hostZone.DepositAddress)
if err != nil {
return errorsmod.Wrapf(err, "host zone address is invalid")
}

// Transfer the LSM token to the deposit account
lsmIBCToken := sdk.NewCoin(lsmTokenDeposit.IbcDenom, lsmTokenDeposit.Amount)
if err := k.bankKeeper.SendCoins(ctx, liquidStakerAddress, hostZoneDepositAddress, sdk.NewCoins(lsmIBCToken)); err != nil {
return errorsmod.Wrap(err, "failed to send tokens from Account to Module")
}

// Mint stToken and send to the user
stToken := sdk.NewCoins(lsmTokenDeposit.StToken)
if err := k.bankKeeper.MintCoins(ctx, types.ModuleName, stToken); err != nil {
return errorsmod.Wrapf(err, "Failed to mint stTokens")
}
if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, liquidStakerAddress, stToken); err != nil {
return errorsmod.Wrapf(err, "Failed to send %s from module to account", lsmTokenDeposit.StToken.String())
}

// Get delegation account address as the destination for the LSM Token
if hostZone.DelegationIcaAddress == "" {
return errorsmod.Wrapf(types.ErrICAAccountNotFound, "no delegation address found for %s", hostZone.ChainId)
}

// Update the deposit status
k.RecordsKeeper.UpdateLSMTokenDepositStatus(ctx, lsmTokenDeposit, recordstypes.LSMTokenDeposit_TRANSFER_QUEUE)

// Update the slash query progress on the validator
if err := k.IncrementValidatorSlashQueryProgress(
ctx,
hostZone.ChainId,
lsmTokenDeposit.ValidatorAddress,
lsmTokenDeposit.Amount,
); err != nil {
return err
}

// Emit an LSM liquid stake event
EmitSuccessfulLSMLiquidStakeEvent(ctx, *hostZone, lsmTokenDeposit)

k.hooks.AfterLiquidStake(ctx, liquidStakerAddress)
return nil
}

// Loops through all active host zones, grabs queued LSMTokenDeposits for that host
// that are in status TRANSFER_QUEUE, and submits the IBC Transfer to the host
func (k Keeper) TransferAllLSMDeposits(ctx sdk.Context) {
Expand Down
Loading

0 comments on commit eed8266

Please sign in to comment.