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

add RedeemStake to autopilot #1012

Merged
merged 16 commits into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
4 changes: 3 additions & 1 deletion app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,9 @@ func NewStrideApp(
keys[autopilottypes.StoreKey],
app.GetSubspace(autopilottypes.ModuleName),
app.StakeibcKeeper,
app.ClaimKeeper)
app.ClaimKeeper,
app.TransferKeeper,
)
autopilotModule := autopilot.NewAppModule(appCodec, app.AutopilotKeeper)

app.VestingKeeper = evmosvestingkeeper.NewKeeper(
Expand Down
5 changes: 4 additions & 1 deletion dockernet/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ IBC_DYDX_CHANNEL_1_DENOM='ibc/78B7A771A2ECBF5D10DC6AB35568A7AC4161DB21B3A848DA47
IBC_DYDX_CHANNEL_2_DENOM='ibc/748465E0D883217048DB25F4C3825D03F682A06FE292E21072BF678E249DAC18'
IBC_DYDX_CHANNEL_3_DENOM='ibc/6301148031C0AC9A392C2DDB1B2D1F11B3B9D0A3ECF20C6B5122685D9E4CC631'

IBC_GAIA_STDENOM='ibc/054A44EC8D9B68B9A6F0D5708375E00A5569A28F21E0064FF12CADC3FEF1D04F'
IBC_HOST_STDENOM='ibc/E3AF56419340E719710C088D3855F65C4717E1A0C3B405F0C1D16F2A54E89421'

# COIN TYPES
# Coin types can be found at https://github.com/satoshilabs/slips/blob/master/slip-0044.md
COSMOS_COIN_TYPE=118
Expand Down Expand Up @@ -521,7 +524,7 @@ NUMBERS_ONLY() {
}

GETBAL() {
head -n 1 | grep -o -E '[0-9]+' || "0"
head -n 1 | grep -o -E '[0-9]+' || echo "0"
}

GETSTAKE() {
Expand Down
80 changes: 75 additions & 5 deletions dockernet/tests/integration_tests.bats
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ setup_file() {
STRIDE_TRANSFER_CHANNEL="channel-${TRANSFER_CHANNEL_NUMBER}"
HOST_TRANSFER_CHANNEL="channel-0"

# IBC sttoken is only needed for autopilot tests which run on GAIA and HOST
IBC_STTOKEN="N/A"
if [[ "$CHAIN_NAME" == "GAIA" || "$CHAIN_NAME" == "HOST" ]]; then
IBC_STTOKEN=$(GET_VAR_VALUE IBC_${CHAIN_NAME}_STDENOM)
fi

TRANSFER_AMOUNT=50000000
STAKE_AMOUNT=10000000
REDEEM_AMOUNT=10000
Expand Down Expand Up @@ -86,7 +92,7 @@ setup_file() {
##############################################################################################


@test "[INTEGRATION-BASIC-$CHAIN_NAME] ibc transfer updates all balances" {
@test "[INTEGRATION-BASIC-$CHAIN_NAME] ibc transfer" {
# get initial balances
sval_strd_balance_start=$($STRIDE_MAIN_CMD q bank balances $(STRIDE_ADDRESS) --denom $STRIDE_DENOM | GETBAL)
hval_strd_balance_start=$($HOST_MAIN_CMD q bank balances $HOST_VAL_ADDRESS --denom $IBC_STRD_DENOM | GETBAL)
Expand Down Expand Up @@ -153,7 +159,7 @@ setup_file() {
}

# check that tokens on the host are staked
@test "[INTEGRATION-BASIC-$CHAIN_NAME] tokens on $CHAIN_NAME were staked" {
@test "[INTEGRATION-BASIC-$CHAIN_NAME] delegation on $CHAIN_NAME" {
# wait for another epoch to pass so that tokens are staked
WAIT_FOR_STRING $STRIDE_LOGS "\[DELEGATION\] success on $HOST_CHAIN_ID"
WAIT_FOR_BLOCK $STRIDE_LOGS 4
Expand Down Expand Up @@ -247,7 +253,7 @@ setup_file() {
assert_equal "$sttoken_balance_diff" "$STAKE_AMOUNT"
}

@test "[INTEGRATION-BASIC-$CHAIN_NAME] packet forwarding automatically liquid stakes" {
@test "[INTEGRATION-BASIC-$CHAIN_NAME] autopilot liquid stake" {
memo='{ "autopilot": { "receiver": "'"$(STRIDE_ADDRESS)"'", "stakeibc": { "action": "LiquidStake" } } }'

# get initial balances
Expand Down Expand Up @@ -275,8 +281,72 @@ setup_file() {
assert_equal "$sttoken_balance_diff" "$PACKET_FORWARD_STAKE_AMOUNT"
}

@test "[INTEGRATION-BASIC-$CHAIN_NAME] autopilot liquid stake and transfer" {
riley-stride marked this conversation as resolved.
Show resolved Hide resolved
if [[ "$CHAIN_NAME" != "GAIA" && "$CHAIN_NAME" != "HOST" ]]; then
skip "Packet forward liquid stake test is only run on GAIA and HOST"
fi

memo='{ "autopilot": { "receiver": "'"$(STRIDE_ADDRESS)"'", "stakeibc": { "action": "LiquidStake", "ibc_receiver": "'$HOST_VAL_ADDRESS'" } } }'

# get initial balances
stibctoken_balance_start=$($HOST_MAIN_CMD q bank balances $HOST_VAL_ADDRESS --denom $IBC_STTOKEN 2>/dev/null | GETBAL)

# Send the IBC transfer with the JSON memo
transfer_msg_prefix="$HOST_MAIN_CMD tx ibc-transfer transfer transfer $HOST_TRANSFER_CHANNEL"
if [[ "$CHAIN_NAME" == "GAIA" ]]; then
# For GAIA (ibc-v3), pass the memo into the receiver field
$transfer_msg_prefix "$memo" ${PACKET_FORWARD_STAKE_AMOUNT}${HOST_DENOM} --from $HOST_VAL -y
elif [[ "$CHAIN_NAME" == "HOST" ]]; then
# For HOST (ibc-v5), pass an address for a receiver and the memo in the --memo field
$transfer_msg_prefix $(STRIDE_ADDRESS) ${PACKET_FORWARD_STAKE_AMOUNT}${HOST_DENOM} --memo "$memo" --from $HOST_VAL -y
fi

# Wait for the transfer to complete
WAIT_FOR_BALANCE_CHANGE $CHAIN_NAME $HOST_VAL_ADDRESS $IBC_STTOKEN

# make sure stATOM balance increased
stibctoken_balance_end=$($HOST_MAIN_CMD q bank balances $HOST_VAL_ADDRESS --denom $IBC_STTOKEN 2>/dev/null | GETBAL)
stibctoken_balance_diff=$(($stibctoken_balance_end-$stibctoken_balance_start))
assert_equal "$stibctoken_balance_diff" "$PACKET_FORWARD_STAKE_AMOUNT"
}

@test "[INTEGRATION-BASIC-$CHAIN_NAME] autopilot redeem stake" {
# get initial balances
stibctoken_balance_start=$($HOST_MAIN_CMD q bank balances $HOST_VAL_ADDRESS --denom $IBC_STTOKEN | GETBAL)

redeem_amount=200
memo='{ "autopilot": { "receiver": "'"$(STRIDE_ADDRESS)"'", "stakeibc": { "action": "RedeemStake", "ibc_receiver": "'$HOST_RECEIVER_ADDRESS'" } } }'

# do IBC transfer
transfer_msg_prefix="$HOST_MAIN_CMD tx ibc-transfer transfer transfer $HOST_TRANSFER_CHANNEL"
if [[ "$CHAIN_NAME" == "GAIA" ]]; then
# For GAIA (ibc-v3), pass the memo into the receiver field
$transfer_msg_prefix "$memo" ${redeem_amount}${IBC_STTOKEN} --from $HOST_VAL -y
elif [[ "$CHAIN_NAME" == "HOST" ]]; then
# For HOST (ibc-v5), pass an address for a receiver and the memo in the --memo field
$transfer_msg_prefix $(STRIDE_ADDRESS)${redeem_amount}${IBC_STTOKEN} --memo "$memo" --from $HOST_VAL -y
else
# For all other hosts, skip this test
skip "Packet forward liquid stake test is only run on GAIA and HOST"
fi

WAIT_FOR_BALANCE_CHANGE $CHAIN_NAME $HOST_VAL_ADDRESS $IBC_STTOKEN

# make sure stATOM balance decreased
stibctoken_balance_end=$($HOST_MAIN_CMD q bank balances $HOST_VAL_ADDRESS --denom $IBC_STTOKEN | GETBAL)
stibctoken_balance_diff=$(($stibctoken_balance_start-$stibctoken_balance_end))
assert_equal "$stibctoken_balance_diff" "$redeem_amount"

WAIT_FOR_BLOCK $STRIDE_LOGS 5

# check that the tokens were transferred to the redemption account
riley-stride marked this conversation as resolved.
Show resolved Hide resolved
AMOUNT=$($STRIDE_MAIN_CMD q records list-user-redemption-record | grep -Fiw 'amount' | head -n 1 | grep -o -E '[0-9]+')
amount_positive=$(($AMOUNT > 0))
assert_equal "$amount_positive" "1"
}

# check that redemptions and claims work
@test "[INTEGRATION-BASIC-$CHAIN_NAME] redemption works" {
@test "[INTEGRATION-BASIC-$CHAIN_NAME] redemption and undelegation on $CHAIN_NAME" {
# get initial balance of redemption ICA
redemption_ica_balance_start=$($HOST_MAIN_CMD q bank balances $(GET_ICA_ADDR $HOST_CHAIN_ID redemption) --denom $HOST_DENOM | GETBAL)

Expand All @@ -293,7 +363,7 @@ setup_file() {
assert_equal "$diff_positive" "1"
}

@test "[INTEGRATION-BASIC-$CHAIN_NAME] claimed tokens are properly distributed" {
@test "[INTEGRATION-BASIC-$CHAIN_NAME] claim redeemed tokens" {
# get balance before claim
start_balance=$($HOST_MAIN_CMD q bank balances $HOST_RECEIVER_ADDRESS --denom $HOST_DENOM | GETBAL)

Expand Down
2 changes: 1 addition & 1 deletion x/autopilot/keeper/airdrop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ func (s *KeeperTestSuite) TestAirdropOnRecvPacket() {
destinationPortID: transfertypes.PortID,
packetData: transfertypes.FungibleTokenPacketData{
Receiver: strideAddress,
Memo: strings.Repeat("X", 300),
Memo: strings.Repeat("X", 513),
},
transferShouldSucceed: false,
airdropShouldUpdate: false,
Expand Down
4 changes: 4 additions & 0 deletions x/autopilot/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
ibctransferkeeper "github.com/cosmos/ibc-go/v7/modules/apps/transfer/keeper"

"github.com/Stride-Labs/stride/v16/x/autopilot/types"
claimkeeper "github.com/Stride-Labs/stride/v16/x/claim/keeper"
Expand All @@ -22,6 +23,7 @@ type (
paramstore paramtypes.Subspace
stakeibcKeeper stakeibckeeper.Keeper
claimKeeper claimkeeper.Keeper
transferKeeper ibctransferkeeper.Keeper
}
)

Expand All @@ -31,6 +33,7 @@ func NewKeeper(
ps paramtypes.Subspace,
stakeibcKeeper stakeibckeeper.Keeper,
claimKeeper claimkeeper.Keeper,
transferKeeper ibctransferkeeper.Keeper,
) *Keeper {
// set KeyTable if it has not already been set
if !ps.HasKeyTable() {
Expand All @@ -43,6 +46,7 @@ func NewKeeper(
paramstore: ps,
stakeibcKeeper: stakeibcKeeper,
claimKeeper: claimKeeper,
transferKeeper: transferKeeper,
}
}

Expand Down
7 changes: 7 additions & 0 deletions x/autopilot/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ import (
"github.com/Stride-Labs/stride/v16/x/autopilot/types"
)

const (
HostChainId = "chain-0"
HostBechPrefix = "cosmos"
HostAddress = "cosmos16plylpsgxechajltx9yeseqexzdzut9g8vla4k"
HostDenom = "uatom"
)

type KeeperTestSuite struct {
apptesting.AppTestHelper
QueryClient types.QueryClient
Expand Down
47 changes: 41 additions & 6 deletions x/autopilot/keeper/liquidstake.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func (k Keeper) TryLiquidStaking(
// Note: newData.denom is base denom e.g. uatom - not ibc/xxx
var token = sdk.NewCoin(newData.Denom, amount)

prefixedDenom := transfertypes.GetDenomPrefix(packet.GetDestPort(), packet.GetDestChannel()) + newData.Denom
prefixedDenom := transfertypes.GetPrefixedDenom(packet.GetDestPort(), packet.GetDestChannel(), newData.Denom)
ibcDenom := transfertypes.ParseDenomTrace(prefixedDenom).IBCDenom()

hostZone, err := k.stakeibcKeeper.GetHostZoneFromHostDenom(ctx, token.Denom)
Expand All @@ -57,10 +57,10 @@ func (k Keeper) TryLiquidStaking(
return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid stride_address (%s) in autopilot memo", strideAddress)
}

return k.RunLiquidStake(ctx, strideAddress, token)
return k.RunLiquidStake(ctx, strideAddress, token, packetMetadata)
}

func (k Keeper) RunLiquidStake(ctx sdk.Context, addr sdk.AccAddress, token sdk.Coin) error {
func (k Keeper) RunLiquidStake(ctx sdk.Context, addr sdk.AccAddress, token sdk.Coin, packetMetadata types.StakeibcPacketMetadata) error {
msg := &stakeibctypes.MsgLiquidStake{
Creator: addr.String(),
Amount: token.Amount,
Expand All @@ -72,12 +72,47 @@ func (k Keeper) RunLiquidStake(ctx sdk.Context, addr sdk.AccAddress, token sdk.C
}

msgServer := stakeibckeeper.NewMsgServerImpl(k.stakeibcKeeper)
_, err := msgServer.LiquidStake(
result, err := msgServer.LiquidStake(
sdk.WrapSDKContext(ctx),
msg,
)
if err != nil {
return errorsmod.Wrapf(sdkerrors.ErrInsufficientFunds, err.Error())
return errorsmod.Wrapf(err, err.Error())
}
return nil

if packetMetadata.IbcReceiver == "" {
return nil
}

hostZone, err := k.stakeibcKeeper.GetHostZoneFromHostDenom(ctx, token.Denom)
if err != nil {
return err
}

return k.IBCTransferStAsset(ctx, result.StToken, addr.String(), hostZone, packetMetadata)
}

func (k Keeper) IBCTransferStAsset(ctx sdk.Context, stAsset sdk.Coin, sender string, hostZone *stakeibctypes.HostZone, packetMetadata types.StakeibcPacketMetadata) error {
ibcTransferTimeoutNanos := k.stakeibcKeeper.GetParam(ctx, stakeibctypes.KeyIBCTransferTimeoutNanos)
timeoutTimestamp := uint64(ctx.BlockTime().UnixNano()) + ibcTransferTimeoutNanos
channelId := packetMetadata.TransferChannel
if channelId == "" {
channelId = hostZone.TransferChannelId
}
transferMsg := &transfertypes.MsgTransfer{
SourcePort: transfertypes.PortID,
SourceChannel: channelId,
Token: stAsset,
// TODO: does this reintroduce the bug in PFM where senders can be spoofed?
// If so, should we instead call PFM directly to forward the packet?
// Or should we obfuscate the sender, making it a random address?
Sender: sender,
Receiver: packetMetadata.IbcReceiver,
TimeoutTimestamp: timeoutTimestamp,
// TimeoutHeight: clienttypes.Height{},
// Memo: "stTokenIBCTransfer",
}

_, err := k.transferKeeper.Transfer(sdk.WrapSDKContext(ctx), transferMsg)
return err
}
80 changes: 80 additions & 0 deletions x/autopilot/keeper/redeem_stake.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package keeper

import (
"fmt"

errorsmod "cosmossdk.io/errors"
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types"
channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types"

"github.com/Stride-Labs/stride/v16/x/autopilot/types"
stakeibckeeper "github.com/Stride-Labs/stride/v16/x/stakeibc/keeper"
stakeibctypes "github.com/Stride-Labs/stride/v16/x/stakeibc/types"
)

func (k Keeper) TryRedeemStake(
ctx sdk.Context,
packet channeltypes.Packet,
transferPacketData transfertypes.FungibleTokenPacketData,
autopilotMetadata types.StakeibcPacketMetadata,
) error {
params := k.GetParams(ctx)
if !params.StakeibcActive {
return fmt.Errorf("packet forwarding param is not active")
}

// At this point in the stack, the denom's in the packet data appear as they existed on the sender zone,
// but as a denom trace instead of a hash
// Meaning, for native stTokens, the port and channel on the host zone are part of the denom
// (e.g. transfer/{channel-on-hub}/stuatom)
// Only stride native stTokens can be redeemed, so we confirm that the denom's prefix matches
// the packet's "source" channel (i.e. the channel on the host zone)
if !transfertypes.ReceiverChainIsSource(packet.GetSourcePort(), packet.GetSourceChannel(), transferPacketData.Denom) {
return fmt.Errorf("the ibc token %s is not supported for redeem stake", transferPacketData.Denom)
}

voucherPrefix := transfertypes.GetDenomPrefix(packet.GetSourcePort(), packet.GetSourceChannel())
stAssetDenom := transferPacketData.Denom[len(voucherPrefix):]
if !stakeibctypes.IsStAssetDenom(stAssetDenom) {
return fmt.Errorf("not a liquid staking token")
}

hostZoneDenom := stakeibctypes.HostZoneDenomFromStAssetDenom(stAssetDenom)

amount, ok := sdk.NewIntFromString(transferPacketData.Amount)
if !ok || amount.IsNegative() {
return fmt.Errorf("not a parsable amount field")
}

strideAddress := transferPacketData.Receiver
redemptionReceiver := autopilotMetadata.IbcReceiver

return k.RunRedeemStake(ctx, strideAddress, redemptionReceiver, hostZoneDenom, amount)
}

func (k Keeper) RunRedeemStake(ctx sdk.Context, strideAddress string, redemptionReceiver string, hostZoneDenom string, amount sdkmath.Int) error {
hostZone, err := k.stakeibcKeeper.GetHostZoneFromHostDenom(ctx, hostZoneDenom)
if err != nil {
return err
}

msg := &stakeibctypes.MsgRedeemStake{
Creator: strideAddress,
Amount: amount,
HostZone: hostZone.ChainId,
Receiver: redemptionReceiver,
}

if err := msg.ValidateBasic(); err != nil {
return err
}

msgServer := stakeibckeeper.NewMsgServerImpl(k.stakeibcKeeper)
if _, err = msgServer.RedeemStake(sdk.WrapSDKContext(ctx), msg); err != nil {
return errorsmod.Wrapf(err, "redeem stake failed")
}

return nil
}
Loading