Skip to content

Commit

Permalink
feat(erc20): OnRecv automatic conversion (#1086)
Browse files Browse the repository at this point in the history
* erc20 OnRecv conversion

* suggestions from pr

* apply suggestions from pr

* Change outward conversion to only accept coin denom

* fix test inconsistency

* suggestions from pr

* suggestions from pr

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>
  • Loading branch information
ramacarlucho and fedekunze committed Nov 21, 2022
1 parent 601cc4d commit 694a8cd
Show file tree
Hide file tree
Showing 21 changed files with 1,976 additions and 210 deletions.
10 changes: 4 additions & 6 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,6 @@ import (
v82 "github.com/evmos/evmos/v10/app/upgrades/v8_2"
v9 "github.com/evmos/evmos/v10/app/upgrades/v9"
v91 "github.com/evmos/evmos/v10/app/upgrades/v9_1"
evmostypes "github.com/evmos/evmos/v10/types"
"github.com/evmos/evmos/v10/x/claims"
claimskeeper "github.com/evmos/evmos/v10/x/claims/keeper"
claimstypes "github.com/evmos/evmos/v10/x/claims/types"
Expand Down Expand Up @@ -165,9 +164,6 @@ func init() {
feemarkettypes.DefaultMinGasMultiplier = MainnetMinGasMultiplier
// modify default min commission to 5%
stakingtypes.DefaultMinCommissionRate = sdk.NewDecWithPrec(5, 2)

// Include the possibility to use an ERC-20 contract address as coin Denom
sdk.SetCoinDenomRegex(evmostypes.EvmosCoinDenomRegex)
}

// Name defines the application binary name
Expand Down Expand Up @@ -479,7 +475,7 @@ func NewEvmos(

app.Erc20Keeper = erc20keeper.NewKeeper(
keys[erc20types.StoreKey], appCodec, app.GetSubspace(erc20types.ModuleName),
app.AccountKeeper, app.BankKeeper, app.EvmKeeper,
app.AccountKeeper, app.BankKeeper, app.EvmKeeper, app.StakingKeeper, app.ClaimsKeeper,
)

app.IncentivesKeeper = incentiveskeeper.NewKeeper(
Expand Down Expand Up @@ -523,7 +519,7 @@ func NewEvmos(
// transferKeeper.SendPacket -> claim.SendPacket -> recovery.SendPacket -> channel.SendPacket

// RecvPacket, message that originates from core IBC and goes down to app, the flow is the otherway
// channel.RecvPacket -> recovery.OnRecvPacket -> claim.OnRecvPacket -> transfer.OnRecvPacket
// channel.RecvPacket -> erc20.OnRecvPacket -> recovery.OnRecvPacket -> claim.OnRecvPacket -> transfer.OnRecvPacket

app.TransferKeeper = transferkeeper.NewKeeper(
appCodec, keys[ibctransfertypes.StoreKey], app.GetSubspace(ibctransfertypes.ModuleName),
Expand Down Expand Up @@ -552,6 +548,7 @@ func NewEvmos(
transferModule := transfer.NewAppModule(app.TransferKeeper)

// transfer stack contains (from bottom to top):
// - ERC-20 Middleware
// - Recovery Middleware
// - Airdrop Claims Middleware
// - IBC Transfer
Expand All @@ -562,6 +559,7 @@ func NewEvmos(
transferStack = transfer.NewIBCModule(app.TransferKeeper)
transferStack = claims.NewIBCMiddleware(*app.ClaimsKeeper, transferStack)
transferStack = recovery.NewIBCMiddleware(*app.RecoveryKeeper, transferStack)
transferStack = erc20.NewIBCMiddleware(app.Erc20Keeper, transferStack)

// Create static IBC router, add transfer route, then set and seal it
ibcRouter := porttypes.NewRouter()
Expand Down
41 changes: 8 additions & 33 deletions ibc/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
transfertypes "github.com/cosmos/ibc-go/v5/modules/apps/transfer/types"
channeltypes "github.com/cosmos/ibc-go/v5/modules/core/04-channel/types"
ibctesting "github.com/cosmos/ibc-go/v5/testing"
teststypes "github.com/evmos/evmos/v10/types/tests"
)

func init() {
Expand Down Expand Up @@ -227,32 +228,6 @@ func TestGetTransferAmount(t *testing.T) {
}
}

var (
uosmoDenomtrace = transfertypes.DenomTrace{
Path: "transfer/channel-0",
BaseDenom: "uosmo",
}
uosmoIbcdenom = uosmoDenomtrace.IBCDenom()

uatomDenomtrace = transfertypes.DenomTrace{
Path: "transfer/channel-1",
BaseDenom: "uatom",
}
uatomIbcdenom = uatomDenomtrace.IBCDenom()

aevmosDenomtrace = transfertypes.DenomTrace{
Path: "transfer/channel-0",
BaseDenom: "aevmos",
}
aevmosIbcdenom = aevmosDenomtrace.IBCDenom()

uatomOsmoDenomtrace = transfertypes.DenomTrace{
Path: "transfer/channel-0/transfer/channel-1",
BaseDenom: "uatom",
}
uatomOsmoIbcdenom = uatomOsmoDenomtrace.IBCDenom()
)

func TestGetReceivedCoin(t *testing.T) {

testCases := []struct {
Expand All @@ -273,7 +248,7 @@ func TestGetReceivedCoin(t *testing.T) {
"channel-0",
"uosmo",
"10",
sdk.Coin{Denom: uosmoIbcdenom, Amount: sdk.NewInt(10)},
sdk.Coin{Denom: teststypes.UosmoIbcdenom, Amount: sdk.NewInt(10)},
},
{
"transfer ibc wrapped coin to destination which is its source",
Expand All @@ -293,7 +268,7 @@ func TestGetReceivedCoin(t *testing.T) {
"channel-2",
"transfer/channel-0/transfer/channel-1/uatom",
"10",
sdk.Coin{Denom: uatomIbcdenom, Amount: sdk.NewInt(10)},
sdk.Coin{Denom: teststypes.UatomIbcdenom, Amount: sdk.NewInt(10)},
},
{
"transfer ibc wrapped coin to destination which is not its source",
Expand All @@ -303,7 +278,7 @@ func TestGetReceivedCoin(t *testing.T) {
"channel-0",
"transfer/channel-1/uatom",
"10",
sdk.Coin{Denom: uatomOsmoIbcdenom, Amount: sdk.NewInt(10)},
sdk.Coin{Denom: teststypes.UatomOsmoIbcdenom, Amount: sdk.NewInt(10)},
},
}

Expand All @@ -330,25 +305,25 @@ func TestGetSentCoin(t *testing.T) {
"get ibc wrapped aevmos coin",
"transfer/channel-0/aevmos",
"10",
sdk.Coin{Denom: aevmosIbcdenom, Amount: sdk.NewInt(10)},
sdk.Coin{Denom: teststypes.AevmosIbcdenom, Amount: sdk.NewInt(10)},
},
{
"get ibc wrapped uosmo coin",
"transfer/channel-0/uosmo",
"10",
sdk.Coin{Denom: uosmoIbcdenom, Amount: sdk.NewInt(10)},
sdk.Coin{Denom: teststypes.UosmoIbcdenom, Amount: sdk.NewInt(10)},
},
{
"get ibc wrapped uatom coin",
"transfer/channel-1/uatom",
"10",
sdk.Coin{Denom: uatomIbcdenom, Amount: sdk.NewInt(10)},
sdk.Coin{Denom: teststypes.UatomIbcdenom, Amount: sdk.NewInt(10)},
},
{
"get 2x ibc wrapped uatom coin",
"transfer/channel-0/transfer/channel-1/uatom",
"10",
sdk.Coin{Denom: uatomOsmoIbcdenom, Amount: sdk.NewInt(10)},
sdk.Coin{Denom: teststypes.UatomOsmoIbcdenom, Amount: sdk.NewInt(10)},
},
}

Expand Down
37 changes: 37 additions & 0 deletions types/tests/test_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package tests

import (
transfertypes "github.com/cosmos/ibc-go/v5/modules/apps/transfer/types"
)

var (
UosmoDenomtrace = transfertypes.DenomTrace{
Path: "transfer/channel-0",
BaseDenom: "uosmo",
}
UosmoIbcdenom = UosmoDenomtrace.IBCDenom()

UatomDenomtrace = transfertypes.DenomTrace{
Path: "transfer/channel-1",
BaseDenom: "uatom",
}
UatomIbcdenom = UatomDenomtrace.IBCDenom()

UevmosDenomtrace = transfertypes.DenomTrace{
Path: "transfer/channel-0",
BaseDenom: "aevmos",
}
UevmosIbcdenom = UevmosDenomtrace.IBCDenom()

UatomOsmoDenomtrace = transfertypes.DenomTrace{
Path: "transfer/channel-0/transfer/channel-1",
BaseDenom: "uatom",
}
UatomOsmoIbcdenom = UatomOsmoDenomtrace.IBCDenom()

AevmosDenomtrace = transfertypes.DenomTrace{
Path: "transfer/channel-0",
BaseDenom: "aevmos",
}
AevmosIbcdenom = AevmosDenomtrace.IBCDenom()
)
6 changes: 0 additions & 6 deletions types/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,3 @@ func GetEvmosAddressFromBech32(address string) (sdk.AccAddress, error) {

return sdk.AccAddress(addressBz), nil
}

// Include the possibility to use an ERC-20 contract address as coin Denom
// Otherwise Coin Denom validation will fail in some cases (e.g.: transfer ERC-20 tokens through IBC)
func EvmosCoinDenomRegex() string {
return `^0x[a-fA-F0-9]{40}$|^[a-zA-Z][a-zA-Z0-9/:._-]{2,127}`
}
13 changes: 1 addition & 12 deletions types/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,6 @@ func TestGetEvmosAddressFromBech32(t *testing.T) {
}

func TestEvmosCoinDenom(t *testing.T) {
sdk.SetCoinDenomRegex(EvmosCoinDenomRegex)
testCases := []struct {
name string
denom string
Expand All @@ -148,7 +147,7 @@ func TestEvmosCoinDenom(t *testing.T) {
},
{
"valid denom - ethereum address (ERC-20 contract)",
"0x52908400098527886e0f7030069857D2E4169EE7",
"erc20/0x52908400098527886e0f7030069857D2E4169EE7",
false,
},
{
Expand All @@ -171,16 +170,6 @@ func TestEvmosCoinDenom(t *testing.T) {
"0x52908400098527886E0F7030069857D2E4169E",
true,
},
{
"invalid denom - hex address but 21 bytes long",
"0x52908400098527886e0f7030069857D2E4169EE738",
true,
},
{
"invalid denom - invalid hex, has a 'g'",
"0x52908400098527886e0f7030069857D2E4169gE7",
true,
},
}

for _, tc := range testCases {
Expand Down
99 changes: 99 additions & 0 deletions x/erc20/ibc_middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package erc20

import (
errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"

transfertypes "github.com/cosmos/ibc-go/v5/modules/apps/transfer/types"
channeltypes "github.com/cosmos/ibc-go/v5/modules/core/04-channel/types"
porttypes "github.com/cosmos/ibc-go/v5/modules/core/05-port/types"
"github.com/cosmos/ibc-go/v5/modules/core/exported"

errortypes "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/evmos/evmos/v10/ibc"
"github.com/evmos/evmos/v10/x/erc20/keeper"
)

var _ porttypes.IBCModule = &IBCMiddleware{}

// IBCMiddleware implements the ICS26 callbacks for the transfer middleware given
// the erc20 keeper and the underlying application.
type IBCMiddleware struct {
*ibc.Module
keeper keeper.Keeper
}

// NewIBCMiddleware creates a new IBCMiddleware given the keeper and underlying application
func NewIBCMiddleware(k keeper.Keeper, app porttypes.IBCModule) IBCMiddleware {
return IBCMiddleware{
Module: ibc.NewModule(app),
keeper: k,
}
}

// OnRecvPacket implements the IBCModule interface.
// It receives the tokens through the default ICS20 OnRecvPacket callback logic
// and then automatically converts the Cosmos Coin to their ERC20 token
// representation.
// If the acknowledgement fails, this callback will default to the ibc-core
// packet callback.
func (im IBCMiddleware) OnRecvPacket(
ctx sdk.Context,
packet channeltypes.Packet,
relayer sdk.AccAddress,
) exported.Acknowledgement {
ack := im.Module.OnRecvPacket(ctx, packet, relayer)

// return if the acknowledgement is an error ACK
if !ack.Success() {
return ack
}

return im.keeper.OnRecvPacket(ctx, packet, ack)
}

// OnAcknowledgementPacket implements the IBCModule interface.
// It refunds the token transferred and then automatically converts the
// Cosmos Coin to their ERC20 token representation.
func (im IBCMiddleware) OnAcknowledgementPacket(
ctx sdk.Context,
packet channeltypes.Packet,
acknowledgement []byte,
relayer sdk.AccAddress,
) error {
var ack channeltypes.Acknowledgement
if err := transfertypes.ModuleCdc.UnmarshalJSON(acknowledgement, &ack); err != nil {
return errorsmod.Wrapf(errortypes.ErrUnknownRequest, "cannot unmarshal ICS-20 transfer packet acknowledgement: %v", err)
}

var data transfertypes.FungibleTokenPacketData
if err := transfertypes.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil {
return errorsmod.Wrapf(errortypes.ErrUnknownRequest, "cannot unmarshal ICS-20 transfer packet data: %s", err.Error())
}

if err := im.Module.OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer); err != nil {
return err
}

return im.keeper.OnAcknowledgementPacket(ctx, packet, data, ack)
}

// OnTimeoutPacket implements the IBCModule interface.
// It refunds the token transferred and then automatically converts the
// Cosmos Coin to their ERC20 token representation.
func (im IBCMiddleware) OnTimeoutPacket(
ctx sdk.Context,
packet channeltypes.Packet,
relayer sdk.AccAddress,
) error {
var data transfertypes.FungibleTokenPacketData
if err := transfertypes.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil {
return errorsmod.Wrapf(errortypes.ErrUnknownRequest, "cannot unmarshal ICS-20 transfer packet data: %s", err.Error())
}

if err := im.Module.OnTimeoutPacket(ctx, packet, relayer); err != nil {
return err
}

return im.keeper.OnTimeoutPacket(ctx, packet, data)
}
9 changes: 6 additions & 3 deletions x/erc20/keeper/evm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ func (suite *KeeperTestSuite) TestBalanceOf() {
mockEVMKeeper = &MockEVMKeeper{}
sp, found := suite.app.ParamsKeeper.GetSubspace(types.ModuleName)
suite.Require().True(found)
suite.app.Erc20Keeper = keeper.NewKeeper(suite.app.GetKey("erc20"), suite.app.AppCodec(), sp, suite.app.AccountKeeper, suite.app.BankKeeper, mockEVMKeeper)
suite.app.Erc20Keeper = keeper.NewKeeper(suite.app.GetKey("erc20"), suite.app.AppCodec(), sp, suite.app.AccountKeeper, suite.app.BankKeeper,
mockEVMKeeper, suite.app.StakingKeeper, suite.app.ClaimsKeeper)

tc.malleate()

Expand Down Expand Up @@ -277,7 +278,8 @@ func (suite *KeeperTestSuite) TestForceFail() {
mockEVMKeeper = &MockEVMKeeper{}
sp, found := suite.app.ParamsKeeper.GetSubspace(types.ModuleName)
suite.Require().True(found)
suite.app.Erc20Keeper = keeper.NewKeeper(suite.app.GetKey("erc20"), suite.app.AppCodec(), sp, suite.app.AccountKeeper, suite.app.BankKeeper, mockEVMKeeper)
suite.app.Erc20Keeper = keeper.NewKeeper(suite.app.GetKey("erc20"), suite.app.AppCodec(), sp, suite.app.AccountKeeper,
suite.app.BankKeeper, mockEVMKeeper, suite.app.StakingKeeper, suite.app.ClaimsKeeper)

tc.malleate()

Expand Down Expand Up @@ -365,7 +367,8 @@ func (suite *KeeperTestSuite) TestQueryERC20ForceFail() {
mockEVMKeeper = &MockEVMKeeper{}
sp, found := suite.app.ParamsKeeper.GetSubspace(types.ModuleName)
suite.Require().True(found)
suite.app.Erc20Keeper = keeper.NewKeeper(suite.app.GetKey("erc20"), suite.app.AppCodec(), sp, suite.app.AccountKeeper, suite.app.BankKeeper, mockEVMKeeper)
suite.app.Erc20Keeper = keeper.NewKeeper(suite.app.GetKey("erc20"), suite.app.AppCodec(), sp, suite.app.AccountKeeper,
suite.app.BankKeeper, mockEVMKeeper, suite.app.StakingKeeper, suite.app.ClaimsKeeper)

tc.malleate()

Expand Down
Loading

0 comments on commit 694a8cd

Please sign in to comment.