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

fix(axelarnet)!: retry failed transfer #2203

Merged
merged 6 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ func NewAxelarApp(
*GetKeeper[axelarnetKeeper.IBCKeeper](keepers),
GetKeeper[nexusKeeper.Keeper](keepers),
axelarbankkeeper.NewBankKeeper(GetKeeper[bankkeeper.BaseKeeper](keepers)),
GetKeeper[authkeeper.AccountKeeper](keepers),
logger,
),
)
Expand Down
29 changes: 22 additions & 7 deletions x/axelarnet/keeper/migrate.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,35 @@
package keeper

import (
"fmt"

sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/axelarnetwork/axelar-core/x/axelarnet/types"
)

// Migrate5to6 returns the handler that performs in-place store migrations from version 5 to 6
func Migrate5to6(k Keeper) func(ctx sdk.Context) error {
// Migrate6to7 returns the handler that performs in-place store migrations from version 6 to 7
func Migrate6to7(k Keeper, bankK types.BankKeeper, accountK types.AccountKeeper, nexusK types.Nexus, ibcK IBCKeeper) func(ctx sdk.Context) error {

return func(ctx sdk.Context) error {
addModuleParamCallContractsProposalMinDeposits(ctx, k)
// Failed IBC transfers are held in Axelarnet module account for later retry.
// This migration escrows tokens back to escrow accounts so that we can use the same code path for retry.
moduleAccount := accountK.GetModuleAddress(types.ModuleName)

balances := bankK.SpendableCoins(ctx, moduleAccount)
for _, coin := range balances {
asset, err := nexusK.NewLockableAsset(ctx, ibcK, bankK, coin)
haiyizxx marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
k.Logger(ctx).Info(fmt.Sprintf("coin %s is not a lockable asset", coin), "error", err)
continue

Check warning on line 24 in x/axelarnet/keeper/migrate.go

View check run for this annotation

Codecov / codecov/patch

x/axelarnet/keeper/migrate.go#L23-L24

Added lines #L23 - L24 were not covered by tests
}

err = asset.LockFrom(ctx, moduleAccount)
haiyizxx marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return err
}

Check warning on line 30 in x/axelarnet/keeper/migrate.go

View check run for this annotation

Codecov / codecov/patch

x/axelarnet/keeper/migrate.go#L29-L30

Added lines #L29 - L30 were not covered by tests
}

return nil
}
}

func addModuleParamCallContractsProposalMinDeposits(ctx sdk.Context, k Keeper) {
k.params.Set(ctx, types.KeyCallContractsProposalMinDeposits, types.DefaultParams().CallContractsProposalMinDeposits)
}
69 changes: 42 additions & 27 deletions x/axelarnet/keeper/migrate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,44 +11,59 @@ import (

"github.com/axelarnetwork/axelar-core/app"
"github.com/axelarnetwork/axelar-core/testutils/fake"
"github.com/axelarnetwork/axelar-core/testutils/rand"
"github.com/axelarnetwork/axelar-core/x/axelarnet/keeper"
"github.com/axelarnetwork/axelar-core/x/axelarnet/types"
"github.com/axelarnetwork/axelar-core/x/axelarnet/types/mock"
nexus "github.com/axelarnetwork/axelar-core/x/nexus/exported"
nexusmock "github.com/axelarnetwork/axelar-core/x/nexus/exported/mock"
nexustypes "github.com/axelarnetwork/axelar-core/x/nexus/types"
. "github.com/axelarnetwork/utils/test"
)

func TestMigrate5to6(t *testing.T) {
func TestMigrate6to7(t *testing.T) {
var (
bank *mock.BankKeeperMock
account *mock.AccountKeeperMock
nexusK *mock.NexusMock
lockableAsset *nexusmock.LockableAssetMock
)

encCfg := app.MakeEncodingConfig()
subspace := params.NewSubspace(encCfg.Codec, encCfg.Amino, sdk.NewKVStoreKey("axelarnetKey"), sdk.NewKVStoreKey("tAxelarnetKey"), "axelarnet")
k := keeper.NewKeeper(encCfg.Codec, sdk.NewKVStoreKey("axelarnet"), subspace, &mock.ChannelKeeperMock{}, &mock.FeegrantKeeperMock{})
ibcK := keeper.NewIBCKeeper(k, &mock.IBCTransferKeeperMock{})
ctx := sdk.NewContext(fake.NewMultiStore(), tmproto.Header{}, false, log.TestingLogger())

Given("subspace is setup with params before migration", func() {
subspace.Set(ctx, types.KeyRouteTimeoutWindow, types.DefaultParams().RouteTimeoutWindow)
subspace.Set(ctx, types.KeyTransferLimit, types.DefaultParams().TransferLimit)
subspace.Set(ctx, types.KeyEndBlockerLimit, types.DefaultParams().EndBlockerLimit)
Given("keeper setup before migration", func() {
bank = &mock.BankKeeperMock{}
account = &mock.AccountKeeperMock{
GetModuleAddressFunc: func(_ string) sdk.AccAddress {
return rand.AccAddr()
},
}
lockableAsset = &nexusmock.LockableAssetMock{
LockFromFunc: func(ctx sdk.Context, fromAddr sdk.AccAddress) error {
return nil
},
GetAssetFunc: func() sdk.Coin {
return rand.Coin()
},
}
nexusK = &mock.NexusMock{
NewLockableAssetFunc: func(ctx sdk.Context, ibc nexustypes.IBCKeeper, bank nexustypes.BankKeeper, coin sdk.Coin) (nexus.LockableAsset, error) {
return lockableAsset, nil
},
}
}).
When("", func() {}).
Then("the migration should add the new param with the default value", func(t *testing.T) {
actual := types.CallContractProposalMinDeposits{}

assert.PanicsWithError(t, "UnmarshalJSON cannot decode empty bytes", func() {
subspace.Get(ctx, types.KeyCallContractsProposalMinDeposits, &actual)
})
assert.PanicsWithError(t, "UnmarshalJSON cannot decode empty bytes", func() {
k.GetParams(ctx)
})

keeper.Migrate5to6(k)(ctx)

assert.NotPanics(t, func() {
subspace.Get(ctx, types.KeyCallContractsProposalMinDeposits, &actual)
})
assert.NotPanics(t, func() {
k.GetParams(ctx)
})

assert.Equal(t, types.DefaultParams().CallContractsProposalMinDeposits, actual)
When("Axelarnet module account has balance for failed cross chain transfers", func() {
bank.SpendableCoinsFunc = func(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins {
return sdk.NewCoins(rand.Coin(), rand.Coin(), rand.Coin())
}
}).
Then("the migration should lock back to escrow account", func(t *testing.T) {
err := keeper.Migrate6to7(k, bank, account, nexusK, ibcK)(ctx)
assert.NoError(t, err)
assert.Len(t, lockableAsset.LockFromCalls(), 3)
}).
Run(t)
}
14 changes: 13 additions & 1 deletion x/axelarnet/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,19 @@
return nil, fmt.Errorf("chain %s is not activated", chain.Name)
}

err := s.ibcK.SendIBCTransfer(ctx, t)
lockableAsset, err := s.nexus.NewLockableAsset(ctx, s.ibcK, s.bank, t.Token)
if err != nil {
return nil, err
}

Check warning on line 476 in x/axelarnet/keeper/msg_server.go

View check run for this annotation

Codecov / codecov/patch

x/axelarnet/keeper/msg_server.go#L475-L476

Added lines #L475 - L476 were not covered by tests

err = lockableAsset.UnlockTo(ctx, types.AxelarIBCAccount)
if err != nil {
return nil, err
}

Check warning on line 481 in x/axelarnet/keeper/msg_server.go

View check run for this annotation

Codecov / codecov/patch

x/axelarnet/keeper/msg_server.go#L480-L481

Added lines #L480 - L481 were not covered by tests
// Notice: all IBC transfer are routed from AxelarIBCAccount after v1.1
t.Sender = types.AxelarIBCAccount
haiyizxx marked this conversation as resolved.
Show resolved Hide resolved

err = s.ibcK.SendIBCTransfer(ctx, t)
if err != nil {
return nil, err
}
Expand Down
10 changes: 10 additions & 0 deletions x/axelarnet/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,17 @@ func TestRetryIBCTransfer(t *testing.T) {
EnqueueForTransferFunc: func(sdk.Context, nexus.CrossChainAddress, sdk.Coin) (nexus.TransferID, error) {
return nexus.TransferID(rand.I64Between(1, 9999)), nil
},
NewLockableAssetFunc: func(ctx sdk.Context, ibc nexustypes.IBCKeeper, bank nexustypes.BankKeeper, coin sdk.Coin) (nexus.LockableAsset, error) {
lockableAsset := &nexusmock.LockableAssetMock{
UnlockToFunc: func(ctx sdk.Context, fromAddr sdk.AccAddress) error {
return nil
},
}

return lockableAsset, nil
},
}

channelK.GetNextSequenceSendFunc = func(sdk.Context, string, string) (uint64, bool) {
return uint64(rand.I64Between(1, 99999)), true
}
Expand Down
18 changes: 10 additions & 8 deletions x/axelarnet/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,22 +96,24 @@
// AppModule implements module.AppModule
type AppModule struct {
AppModuleBasic
logger log.Logger
ibcK keeper.IBCKeeper
keeper keeper.Keeper
nexus types.Nexus
bank types.BankKeeper
logger log.Logger
ibcK keeper.IBCKeeper
keeper keeper.Keeper
nexus types.Nexus
bank types.BankKeeper
account types.AccountKeeper
}

// NewAppModule creates a new AppModule object
func NewAppModule(ibcK keeper.IBCKeeper, nexus types.Nexus, bank types.BankKeeper, logger log.Logger) AppModule {
func NewAppModule(ibcK keeper.IBCKeeper, nexus types.Nexus, bank types.BankKeeper, account types.AccountKeeper, logger log.Logger) AppModule {

Check warning on line 108 in x/axelarnet/module.go

View check run for this annotation

Codecov / codecov/patch

x/axelarnet/module.go#L108

Added line #L108 was not covered by tests
return AppModule{
AppModuleBasic: AppModuleBasic{},
logger: logger,
ibcK: ibcK,
keeper: ibcK.Keeper,
nexus: nexus,
bank: bank,
account: account,

Check warning on line 116 in x/axelarnet/module.go

View check run for this annotation

Codecov / codecov/patch

x/axelarnet/module.go#L116

Added line #L116 was not covered by tests
}
}

Expand Down Expand Up @@ -160,7 +162,7 @@

types.RegisterQueryServiceServer(cfg.QueryServer(), keeper.NewGRPCQuerier(am.keeper, am.nexus))

err := cfg.RegisterMigration(types.ModuleName, 5, keeper.Migrate5to6(am.keeper))
err := cfg.RegisterMigration(types.ModuleName, 6, keeper.Migrate6to7(am.keeper, am.bank, am.account, am.nexus, am.ibcK))

Check warning on line 165 in x/axelarnet/module.go

View check run for this annotation

Codecov / codecov/patch

x/axelarnet/module.go#L165

Added line #L165 was not covered by tests
if err != nil {
panic(err)
}
Expand All @@ -179,7 +181,7 @@
}

// ConsensusVersion implements AppModule/ConsensusVersion.
func (AppModule) ConsensusVersion() uint64 { return 6 }
func (AppModule) ConsensusVersion() uint64 { return 7 }

Check warning on line 184 in x/axelarnet/module.go

View check run for this annotation

Codecov / codecov/patch

x/axelarnet/module.go#L184

Added line #L184 was not covered by tests

// AxelarnetIBCModule is an IBCModule that adds rate limiting and gmp processing to the ibc middleware
type AxelarnetIBCModule struct {
Expand Down
1 change: 1 addition & 0 deletions x/axelarnet/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ type BankKeeper interface {
SpendableBalance(ctx sdk.Context, address sdk.AccAddress, denom string) sdk.Coin
SendCoinsFromModuleToModule(ctx sdk.Context, senderModule, recipientModule string, amt sdk.Coins) error
GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins
SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins
}

// IBCTransferKeeper provides functionality to manage IBC transfers
Expand Down
50 changes: 50 additions & 0 deletions x/axelarnet/types/mock/expected_keepers.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading