forked from evmos/evmos
-
Notifications
You must be signed in to change notification settings - Fork 3
/
ibc_callbacks.go
202 lines (168 loc) · 6.72 KB
/
ibc_callbacks.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
package keeper
import (
errorsmod "cosmossdk.io/errors"
"github.com/EscanBE/evermint/v12/utils"
"github.com/armon/go-metrics"
"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"
errortypes "github.com/cosmos/cosmos-sdk/types/errors"
transfertypes "github.com/cosmos/ibc-go/v6/modules/apps/transfer/types"
channeltypes "github.com/cosmos/ibc-go/v6/modules/core/04-channel/types"
"github.com/cosmos/ibc-go/v6/modules/core/exported"
"github.com/ethereum/go-ethereum/common"
"github.com/EscanBE/evermint/v12/ibc"
"github.com/EscanBE/evermint/v12/x/erc20/types"
)
// OnRecvPacket performs the ICS20 middleware receive callback for automatically
// converting an IBC Coin to their ERC20 representation.
// For the conversion to succeed, the IBC denomination must have previously been
// registered via governance. Note that the native staking denomination
// is excluded from the conversion.
//
// CONTRACT: This middleware MUST be executed transfer after the ICS20 OnRecvPacket
// Return acknowledgement and continue with the next layer of the IBC middleware
// stack if:
// - ERC20s are disabled
// - Denomination is native staking token
// - The base denomination is not registered as ERC20
func (k Keeper) OnRecvPacket(
ctx sdk.Context,
packet channeltypes.Packet,
ack exported.Acknowledgement,
) exported.Acknowledgement {
var data transfertypes.FungibleTokenPacketData
if err := transfertypes.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil {
// NOTE: shouldn't happen as the packet has already
// been decoded on ICS20 transfer logic
err = errorsmod.Wrapf(errortypes.ErrInvalidType, "cannot unmarshal ICS-20 transfer packet data")
return channeltypes.NewErrorAcknowledgement(err)
}
// use a zero gas config to avoid extra costs for the relayers
ctx = utils.UseZeroGasConfig(ctx)
if !k.IsERC20Enabled(ctx) {
return ack
}
sender, recipient, _, _, err := ibc.GetTransferSenderRecipient(packet)
if err != nil {
return channeltypes.NewErrorAcknowledgement(err)
}
claimsParams := k.claimsKeeper.GetParams(ctx)
// if sender == recipient, and is not from an EVM Channel recovery was executed
if sender.Equals(recipient) && !claimsParams.IsEVMChannel(packet.DestinationChannel) {
// Continue to the next IBC middleware by returning the original ACK.
return ack
}
senderAcc := k.accountKeeper.GetAccount(ctx, sender)
// return acknoledgement without conversion if sender is a module account
if types.IsModuleAccount(senderAcc) {
return ack
}
// parse the transferred denom
coin := ibc.GetReceivedCoin(
packet.SourcePort, packet.SourceChannel,
packet.DestinationPort, packet.DestinationChannel,
data.Denom, data.Amount,
)
// check if the coin is a native staking token
bondDenom := k.stakingKeeper.BondDenom(ctx)
if coin.Denom == bondDenom {
// no-op, received coin is the staking denomination
return ack
}
pairID := k.GetTokenPairID(ctx, coin.Denom)
if len(pairID) == 0 {
// short-circuit: if the denom is not registered, conversion will fail
// so we can continue with the rest of the stack
return ack
}
pair, _ := k.GetTokenPair(ctx, pairID)
if !pair.Enabled {
// no-op: continue with the rest of the stack without conversion
return ack
}
// Instead of converting just the received coins, convert the whole user balance
// which includes the received coins.
balance := k.bankKeeper.GetBalance(ctx, recipient, coin.Denom)
// Build MsgConvertCoin, from recipient to recipient since IBC transfer already occurred
msg := types.NewMsgConvertCoin(balance, common.BytesToAddress(recipient.Bytes()), recipient)
// NOTE: we don't use ValidateBasic the msg since we've already validated
// the ICS20 packet data
// Use MsgConvertCoin to convert the Cosmos Coin to an ERC20
if _, err = k.ConvertCoin(sdk.WrapSDKContext(ctx), msg); err != nil {
return channeltypes.NewErrorAcknowledgement(err)
}
defer func() {
telemetry.IncrCounterWithLabels(
[]string{types.ModuleName, "ibc", "on_recv", "total"},
1,
[]metrics.Label{
telemetry.NewLabel("denom", coin.Denom),
telemetry.NewLabel("source_channel", packet.SourceChannel),
telemetry.NewLabel("source_port", packet.SourcePort),
},
)
}()
return ack
}
// OnAcknowledgementPacket responds to the the success or failure of a packet
// acknowledgement written on the receiving chain. If the acknowledgement was a
// success then nothing occurs. If the acknowledgement failed, then the sender
// is refunded and then the IBC Coins are converted to ERC20.
func (k Keeper) OnAcknowledgementPacket(
ctx sdk.Context, _ channeltypes.Packet,
data transfertypes.FungibleTokenPacketData,
ack channeltypes.Acknowledgement,
) error {
switch ack.Response.(type) {
case *channeltypes.Acknowledgement_Error:
// convert the token from Cosmos Coin to its ERC20 representation
return k.ConvertCoinToERC20FromPacket(ctx, data)
default:
// the acknowledgement succeeded on the receiving chain so nothing needs to
// be executed and no error needs to be returned
return nil
}
}
// OnTimeoutPacket converts the IBC coin to ERC20 after refunding the sender
// since the original packet sent was never received and has been timed out.
func (k Keeper) OnTimeoutPacket(ctx sdk.Context, _ channeltypes.Packet, data transfertypes.FungibleTokenPacketData) error {
return k.ConvertCoinToERC20FromPacket(ctx, data)
}
// ConvertCoinToERC20FromPacket converts the IBC coin to ERC20 after refunding the sender
func (k Keeper) ConvertCoinToERC20FromPacket(ctx sdk.Context, data transfertypes.FungibleTokenPacketData) error {
sender, err := sdk.AccAddressFromBech32(data.Sender)
if err != nil {
return err
}
// use a zero gas config to avoid extra costs for the relayers
ctx = utils.UseZeroGasConfig(ctx)
// assume that all module accounts on this chain need to have their tokens in the
// IBC representation as opposed to ERC20
senderAcc := k.accountKeeper.GetAccount(ctx, sender)
if types.IsModuleAccount(senderAcc) {
return nil
}
coin := ibc.GetSentCoin(data.Denom, data.Amount)
// check if the coin is a native staking token
bondDenom := k.stakingKeeper.BondDenom(ctx)
if coin.Denom == bondDenom {
// no-op, received coin is the staking denomination
return nil
}
params := k.GetParams(ctx)
if !params.EnableErc20 || !k.IsDenomRegistered(ctx, coin.Denom) {
// no-op, ERC20s are disabled or the denom is not registered
return nil
}
msg := types.NewMsgConvertCoin(coin, common.BytesToAddress(sender), sender)
// NOTE: we don't use ValidateBasic the msg since we've already validated the
// fields from the packet data
// convert Coin to ERC20
if _, err = k.ConvertCoin(sdk.WrapSDKContext(ctx), msg); err != nil {
return err
}
defer func() {
telemetry.IncrCounter(1, types.ModuleName, "ibc", "error", "total")
}()
return nil
}