-
Notifications
You must be signed in to change notification settings - Fork 202
/
ibc.go
265 lines (238 loc) · 10.7 KB
/
ibc.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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
package keeper
import (
"fmt"
errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
icatypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/types"
clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types"
connectiontypes "github.com/cosmos/ibc-go/v7/modules/core/03-connection/types"
ibctmtypes "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint"
"github.com/spf13/cast"
ratelimittypes "github.com/Stride-Labs/ibc-rate-limiting/ratelimit/types"
"github.com/Stride-Labs/stride/v22/x/stakeibc/types"
)
func (k Keeper) OnChanOpenAck(ctx sdk.Context, portID, channelID string) error {
// Lookup connection ID, counterparty chain ID, and ICA address from the channel ID
controllerConnectionId, _, err := k.IBCKeeper.ChannelKeeper.GetChannelConnection(ctx, portID, channelID)
if err != nil {
return err
}
address, found := k.ICAControllerKeeper.GetInterchainAccountAddress(ctx, controllerConnectionId, portID)
if !found {
k.Logger(ctx).Info(fmt.Sprintf("No ICA address associated with connection %s and port %s", controllerConnectionId, portID))
return nil
}
chainId, err := k.GetChainIdFromConnectionId(ctx, controllerConnectionId)
if err != nil {
return err
}
k.Logger(ctx).Info(fmt.Sprintf("Found matching address for chain: %s, address %s, port %s", chainId, address, portID))
// Check if the chainId matches one of the host zones, and if so,
// store the relevant ICA address on the host zone struct
if err := k.StoreHostZoneIcaAddress(ctx, chainId, portID, address); err != nil {
return err
}
// Check if the chainId matches any ICAs from trade routes, and if so,
// store the relevant ICA addresses in the trade route structs
if err := k.StoreTradeRouteIcaAddress(ctx, chainId, portID, address); err != nil {
return err
}
return nil
}
// Checks if the chainId matches a given host zone, and the address matches a relevant ICA account
// If so, stores the ICA address on the host zone struct
// Also whitelists ICA addresses from rate limiting
func (k Keeper) StoreHostZoneIcaAddress(ctx sdk.Context, chainId, portId, address string) error {
// Check if the chainId matches a host zone
// If the chainId does not match (for instance, a reward zone in a trade route is not a host zone)
// then we can ignore the ICA address checks
hostZone, found := k.GetHostZone(ctx, chainId)
if !found {
k.Logger(ctx).Info(fmt.Sprintf("chainId %s has no associated host zone", chainId))
return nil
}
// expected port IDs for each ICA account type
delegationOwner := types.FormatHostZoneICAOwner(chainId, types.ICAAccountType_DELEGATION)
delegationPortID, err := icatypes.NewControllerPortID(delegationOwner)
if err != nil {
return err
}
withdrawalOwner := types.FormatHostZoneICAOwner(chainId, types.ICAAccountType_WITHDRAWAL)
withdrawalPortID, err := icatypes.NewControllerPortID(withdrawalOwner)
if err != nil {
return err
}
feeOwner := types.FormatHostZoneICAOwner(chainId, types.ICAAccountType_FEE)
feePortID, err := icatypes.NewControllerPortID(feeOwner)
if err != nil {
return err
}
redemptionOwner := types.FormatHostZoneICAOwner(chainId, types.ICAAccountType_REDEMPTION)
redemptionPortID, err := icatypes.NewControllerPortID(redemptionOwner)
if err != nil {
return err
}
communityPoolDepositOwner := types.FormatHostZoneICAOwner(chainId, types.ICAAccountType_COMMUNITY_POOL_DEPOSIT)
communityPoolDepositPortID, err := icatypes.NewControllerPortID(communityPoolDepositOwner)
if err != nil {
return err
}
communityPoolReturnOwner := types.FormatHostZoneICAOwner(chainId, types.ICAAccountType_COMMUNITY_POOL_RETURN)
communityPoolReturnPortID, err := icatypes.NewControllerPortID(communityPoolReturnOwner)
if err != nil {
return err
}
// Set ICA account addresses
switch {
case portId == withdrawalPortID:
hostZone.WithdrawalIcaAddress = address
case portId == feePortID:
hostZone.FeeIcaAddress = address
case portId == delegationPortID:
hostZone.DelegationIcaAddress = address
case portId == redemptionPortID:
hostZone.RedemptionIcaAddress = address
case portId == communityPoolDepositPortID:
hostZone.CommunityPoolDepositIcaAddress = address
case portId == communityPoolReturnPortID:
hostZone.CommunityPoolReturnIcaAddress = address
default:
k.Logger(ctx).Info(fmt.Sprintf("portId %s has an associated host zone, but does not match any ICA accounts", portId))
return nil
}
k.SetHostZone(ctx, hostZone)
// Once the delegation channel is registered, whitelist epochly transfers so they're not rate limited
// Epochly transfers go from the deposit address to the delegation address
if portId == delegationPortID {
k.RatelimitKeeper.SetWhitelistedAddressPair(ctx, ratelimittypes.WhitelistedAddressPair{
Sender: hostZone.DepositAddress,
Receiver: hostZone.DelegationIcaAddress,
})
}
// Once the fee channel is registered, whitelist reward transfers so they're not rate limited
// Reward transfers go from the fee address to the reward collector
if portId == feePortID {
rewardCollectorAddress := k.AccountKeeper.GetModuleAccount(ctx, types.RewardCollectorName).GetAddress()
k.RatelimitKeeper.SetWhitelistedAddressPair(ctx, ratelimittypes.WhitelistedAddressPair{
Sender: hostZone.FeeIcaAddress,
Receiver: rewardCollectorAddress.String(),
})
}
// Once the community pool deposit ICA is registered, whitelist epochly community pool transfers
// from the deposit ICA to the community pool holding accounts
if portId == communityPoolDepositPortID {
k.RatelimitKeeper.SetWhitelistedAddressPair(ctx, ratelimittypes.WhitelistedAddressPair{
Sender: hostZone.CommunityPoolDepositIcaAddress,
Receiver: hostZone.CommunityPoolStakeHoldingAddress,
})
k.RatelimitKeeper.SetWhitelistedAddressPair(ctx, ratelimittypes.WhitelistedAddressPair{
Sender: hostZone.CommunityPoolDepositIcaAddress,
Receiver: hostZone.CommunityPoolRedeemHoldingAddress,
})
}
// Once the community pool return ICA is registered, whitelist epochly community pool transfers
// from the community pool stake holding account to the community pool return ICA
if portId == communityPoolReturnPortID {
k.RatelimitKeeper.SetWhitelistedAddressPair(ctx, ratelimittypes.WhitelistedAddressPair{
Sender: hostZone.CommunityPoolStakeHoldingAddress,
Receiver: hostZone.CommunityPoolReturnIcaAddress,
})
}
return nil
}
// Checks if the port matches an ICA account on the trade route, and if so, stores the
// relevant ICA address on the trade route
func (k Keeper) StoreTradeRouteIcaAddress(ctx sdk.Context, callbackChainId, callbackPortId, address string) error {
// Check if the port Id matches either the trade or unwind ICA on the tradeRoute
// If the chainId and port Id from the callback match the account
// on a trade route, set the ICA address in the relevant places,
// including the from/to addresses on each hop
for _, route := range k.GetAllTradeRoutes(ctx) {
// Build the expected port ID for the reward and trade accounts,
// using the chainId and route ID
rewardAccount := route.RewardAccount
rewardOwner := types.FormatTradeRouteICAOwnerFromRouteId(rewardAccount.ChainId, route.GetRouteId(), rewardAccount.Type)
rewardPortId, err := icatypes.NewControllerPortID(rewardOwner)
if err != nil {
return err
}
tradeAccount := route.TradeAccount
tradeOwner := types.FormatTradeRouteICAOwnerFromRouteId(tradeAccount.ChainId, route.GetRouteId(), tradeAccount.Type)
tradePortId, err := icatypes.NewControllerPortID(tradeOwner)
if err != nil {
return err
}
// Check if route IDs match the callback chainId/portId
if route.RewardAccount.ChainId == callbackChainId && callbackPortId == rewardPortId {
k.Logger(ctx).Info(fmt.Sprintf("ICA Address %s found for Unwind ICA on %s", address, route.Description()))
route.RewardAccount.Address = address
} else if route.TradeAccount.ChainId == callbackChainId && callbackPortId == tradePortId {
k.Logger(ctx).Info(fmt.Sprintf("ICA Address %s found for Trade ICA on %s", address, route.Description()))
route.TradeAccount.Address = address
}
k.SetTradeRoute(ctx, route)
}
return nil
}
// TODO [cleanup]: Cleanup error messages, and rename to GetLightClientTime
// Retrieves the light client time for a given connection
func (k Keeper) GetLightClientTimeSafely(ctx sdk.Context, connectionID string) (uint64, error) {
// get light client's latest height
conn, found := k.IBCKeeper.ConnectionKeeper.GetConnection(ctx, connectionID)
if !found {
errMsg := fmt.Sprintf("invalid connection id, %s not found", connectionID)
k.Logger(ctx).Error(errMsg)
return 0, fmt.Errorf(errMsg)
}
// TODO(TEST-112) make sure to update host LCs here!
latestConsensusClientState, found := k.IBCKeeper.ClientKeeper.GetLatestClientConsensusState(ctx, conn.ClientId)
if !found {
errMsg := fmt.Sprintf("client id %s not found for connection %s", conn.ClientId, connectionID)
k.Logger(ctx).Error(errMsg)
return 0, fmt.Errorf(errMsg)
} else {
latestTime := latestConsensusClientState.GetTimestamp()
return latestTime, nil
}
}
// TODO [cleanup]: Cleanup error messages, and rename to GetLightClientHeight
// Retrieves the light client time for a given connection
func (k Keeper) GetLightClientHeightSafely(ctx sdk.Context, connectionID string) (uint64, error) {
// get light client's latest height
conn, found := k.IBCKeeper.ConnectionKeeper.GetConnection(ctx, connectionID)
if !found {
errMsg := fmt.Sprintf("invalid connection id, %s not found", connectionID)
k.Logger(ctx).Error(errMsg)
return 0, fmt.Errorf(errMsg)
}
clientState, found := k.IBCKeeper.ClientKeeper.GetClientState(ctx, conn.ClientId)
if !found {
errMsg := fmt.Sprintf("client id %s not found for connection %s", conn.ClientId, connectionID)
k.Logger(ctx).Error(errMsg)
return 0, fmt.Errorf(errMsg)
} else {
latestHeightHostZone, err := cast.ToUint64E(clientState.GetLatestHeight().GetRevisionHeight())
if err != nil {
errMsg := fmt.Sprintf("error casting latest height to int64: %s", err.Error())
k.Logger(ctx).Error(errMsg)
return 0, fmt.Errorf(errMsg)
}
return latestHeightHostZone, nil
}
}
// Lookup a chain ID from a connection ID by looking up the client state
func (k Keeper) GetChainIdFromConnectionId(ctx sdk.Context, connectionID string) (string, error) {
connection, found := k.IBCKeeper.ConnectionKeeper.GetConnection(ctx, connectionID)
if !found {
return "", errorsmod.Wrapf(connectiontypes.ErrConnectionNotFound, "connection %s not found", connectionID)
}
clientState, found := k.IBCKeeper.ClientKeeper.GetClientState(ctx, connection.ClientId)
if !found {
return "", errorsmod.Wrapf(clienttypes.ErrClientNotFound, "client %s not found", connection.ClientId)
}
client, ok := clientState.(*ibctmtypes.ClientState)
if !ok {
return "", types.ErrClientStateNotTendermint
}
return client.ChainId, nil
}