-
Notifications
You must be signed in to change notification settings - Fork 202
/
liquidstake.go
153 lines (130 loc) · 5.56 KB
/
liquidstake.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
package keeper
import (
"errors"
"fmt"
"time"
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/v21/x/autopilot/types"
stakeibckeeper "github.com/Stride-Labs/stride/v21/x/stakeibc/keeper"
stakeibctypes "github.com/Stride-Labs/stride/v21/x/stakeibc/types"
)
const (
// If the forward transfer fails, the tokens are sent to the fallback address
// which is a less than ideal UX
// As a result, we decided to use a long timeout here such, even in the case
// of high activity, a timeout should be very unlikely to occur
// Empirically we found that times of high market stress took roughly
// 2 hours for transfers to complete
LiquidStakeForwardTransferTimeout = (time.Hour * 3)
)
// Attempts to do an autopilot liquid stake (and optional forward)
// The liquid stake is only allowed if the inbound packet came along a trusted channel
func (k Keeper) TryLiquidStaking(
ctx sdk.Context,
packet channeltypes.Packet,
transferMetadata transfertypes.FungibleTokenPacketData,
autopilotMetadata types.StakeibcPacketMetadata,
) error {
params := k.GetParams(ctx)
if !params.StakeibcActive {
return errorsmod.Wrapf(types.ErrPacketForwardingInactive, "autopilot stakeibc routing is inactive")
}
// Verify the amount is valid
amount, ok := sdk.NewIntFromString(transferMetadata.Amount)
if !ok {
return errors.New("not a parsable amount field")
}
// In this case, we can't process a liquid staking transaction, because we're dealing with native tokens (e.g. STRD, stATOM)
if transfertypes.ReceiverChainIsSource(packet.GetSourcePort(), packet.GetSourceChannel(), transferMetadata.Denom) {
return fmt.Errorf("native token is not supported for liquid staking (%s)", transferMetadata.Denom)
}
// Note: the denom in the packet is the base denom e.g. uatom - not ibc/xxx
// We need to use the port and channel to build the IBC denom
prefixedDenom := transfertypes.GetPrefixedDenom(packet.GetDestPort(), packet.GetDestChannel(), transferMetadata.Denom)
ibcDenom := transfertypes.ParseDenomTrace(prefixedDenom).IBCDenom()
hostZone, err := k.stakeibcKeeper.GetHostZoneFromHostDenom(ctx, transferMetadata.Denom)
if err != nil {
return err
}
// Verify the IBC denom of the packet matches the host zone, to confirm the packet
// was sent over a trusted channel
if hostZone.IbcDenom != ibcDenom {
return fmt.Errorf("ibc denom %s is not equal to host zone ibc denom %s", ibcDenom, hostZone.IbcDenom)
}
return k.RunLiquidStake(ctx, amount, transferMetadata, autopilotMetadata)
}
// Submits a LiquidStake message from the transfer receiver
// If a forwarding recipient is specified, the stTokens are ibc transferred
func (k Keeper) RunLiquidStake(
ctx sdk.Context,
amount sdkmath.Int,
transferMetadata transfertypes.FungibleTokenPacketData,
autopilotMetadata types.StakeibcPacketMetadata,
) error {
msg := &stakeibctypes.MsgLiquidStake{
Creator: transferMetadata.Receiver,
Amount: amount,
HostDenom: transferMetadata.Denom,
}
if err := msg.ValidateBasic(); err != nil {
return err
}
msgServer := stakeibckeeper.NewMsgServerImpl(k.stakeibcKeeper)
msgResponse, err := msgServer.LiquidStake(
sdk.WrapSDKContext(ctx),
msg,
)
if err != nil {
return errorsmod.Wrapf(err, "failed to liquid stake")
}
// If the IBCReceiver is empty, there is no forwarding step
if autopilotMetadata.IbcReceiver == "" {
return nil
}
// Otherwise, if there is forwarding info, submit the IBC transfer
return k.IBCTransferStToken(ctx, msgResponse.StToken, transferMetadata, autopilotMetadata)
}
// Submits an IBC transfer of the stToken to a non-stride zone (either back to the host zone or to a different zone)
// The sender of the transfer is the hashed receiver of the original autopilot inbound transfer
func (k Keeper) IBCTransferStToken(
ctx sdk.Context,
stToken sdk.Coin,
transferMetadata transfertypes.FungibleTokenPacketData,
autopilotMetadata types.StakeibcPacketMetadata,
) error {
hostZone, err := k.stakeibcKeeper.GetHostZoneFromHostDenom(ctx, transferMetadata.Denom)
if err != nil {
return err
}
// If there's no channelID specified in the packet, default to the channel on the host zone
channelId := autopilotMetadata.TransferChannel
if channelId == "" {
channelId = hostZone.TransferChannelId
}
// Use a long timeout for the transfer
timeoutTimestamp := uint64(ctx.BlockTime().UnixNano() + LiquidStakeForwardTransferTimeout.Nanoseconds())
// Submit the transfer from the hashed address
transferMsg := &transfertypes.MsgTransfer{
SourcePort: transfertypes.PortID,
SourceChannel: channelId,
Token: stToken,
Sender: transferMetadata.Receiver,
Receiver: autopilotMetadata.IbcReceiver,
TimeoutTimestamp: timeoutTimestamp,
Memo: "autopilot-liquid-stake-and-forward",
}
transferResponse, err := k.transferKeeper.Transfer(sdk.WrapSDKContext(ctx), transferMsg)
if err != nil {
return errorsmod.Wrapf(err, "failed to submit transfer during autopilot liquid stake and forward")
}
// Store the original receiver as the fallback address in case the transfer fails
// autopilotMetadata.StrideAddress is never the hashed address, because the autopilotMetadata struct
// is parsed upstream of hashing the receiver
// So StrideAddress is used as the fallback (which is always the original receiver)
k.SetTransferFallbackAddress(ctx, channelId, transferResponse.Sequence, autopilotMetadata.StrideAddress)
return err
}