Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

stakeibc file re-org part 5 (msg_server) #1120

Merged
merged 36 commits into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
835d9d4
moved msg_register_host_zone
sampocs Mar 4, 2024
0facf41
moved msg_add_validator
sampocs Mar 4, 2024
3b1fd3c
moved msg_change_validator_weight
sampocs Mar 4, 2024
393ef52
moved msg_clear_balance
sampocs Mar 4, 2024
3ed418f
moved msg_liquid_stake
sampocs Mar 4, 2024
af243d3
moved msg_redeem_stake
sampocs Mar 4, 2024
b2cf4e2
renamed msg_server_test
sampocs Mar 4, 2024
add9cff
moved add_validator test
sampocs Mar 4, 2024
8d4335d
moved clear balance test
sampocs Mar 4, 2024
0c1da90
moved liquid stake test
sampocs Mar 4, 2024
5770106
moved redeem stake test
sampocs Mar 4, 2024
0d11311
removed empty files
sampocs Mar 4, 2024
e23e6fd
renamed icatest.go
sampocs Mar 4, 2024
ce017f2
moved msg_create_trade_route
sampocs Mar 4, 2024
dc0490c
moved msg_delete_trade_route
sampocs Mar 4, 2024
00c03ed
moved delete validator
sampocs Mar 4, 2024
ff497fe
moved update trade route
sampocs Mar 4, 2024
2b588a1
moved restore ica
sampocs Mar 4, 2024
08eb85f
moved update validator exchange rate
sampocs Mar 4, 2024
75a60dc
moved calibrate delegation
sampocs Mar 4, 2024
6e04f6d
moved update inner bounds
sampocs Mar 4, 2024
b0ca626
moved resume host zone
sampocs Mar 4, 2024
aba262e
moved delete validator test
sampocs Mar 4, 2024
dfa5b37
fixed comment separators
sampocs Mar 4, 2024
e4717f1
moved delete trade route test
sampocs Mar 4, 2024
da0211c
moved create trade route test
sampocs Mar 4, 2024
80988ed
moved update trade route test
sampocs Mar 4, 2024
eec77fb
moved restore interchain account test
sampocs Mar 4, 2024
4082a1f
moved resume host zone test
sampocs Mar 4, 2024
e202846
removed empty files
sampocs Mar 4, 2024
18483e6
moved update inner bounds test
sampocs Mar 4, 2024
fd94741
moved rebalance validators
sampocs Mar 4, 2024
cbe9d75
moved lsm liquid stake helpers
sampocs Mar 4, 2024
ab099c8
moved lsm liquid stake
sampocs Mar 4, 2024
85bb523
moved lsm liquid stake test
sampocs Mar 4, 2024
318c529
deleted empty files
sampocs Mar 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading