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

refactor(nexus): add message router for the axelarnet and evm modules #2020

Merged
merged 5 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,11 @@ func NewAxelarApp(

setKeeper(keepers, initAxelarIBCKeeper(keepers))

messageRouter := nexusTypes.NewMessageRouter().
AddRoute(evmTypes.ModuleName, evmKeeper.NewMessageRoute()).
AddRoute(axelarnetTypes.ModuleName, axelarnetKeeper.NewMessageRoute(getKeeper[axelarnetKeeper.Keeper](keepers), getKeeper[axelarnetKeeper.IBCKeeper](keepers), getKeeper[feegrantkeeper.Keeper](keepers), axelarbankkeeper.NewBankKeeper(getKeeper[bankkeeper.BaseKeeper](keepers)), getKeeper[nexusKeeper.Keeper](keepers), getKeeper[authkeeper.AccountKeeper](keepers)))
getKeeperAsRef[nexusKeeper.Keeper](keepers).SetMessageRouter(messageRouter)

axelarnetModule := axelarnet.NewAppModule(getKeeper[axelarnetKeeper.Keeper](keepers), getKeeper[nexusKeeper.Keeper](keepers), axelarbankkeeper.NewBankKeeper(getKeeper[bankkeeper.BaseKeeper](keepers)), getKeeper[authkeeper.AccountKeeper](keepers), getKeeper[axelarnetKeeper.IBCKeeper](keepers), transferStack, rateLimiter, logger)

// Create static IBC router, add axelarnet module as the IBC transfer route, and seal it
Expand Down
10 changes: 5 additions & 5 deletions app/keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,15 +284,15 @@ func initEvmKeeper(appCodec codec.Codec, keys map[string]*sdk.KVStoreKey, keeper
}

func initNexusKeeper(appCodec codec.Codec, keys map[string]*sdk.KVStoreKey, keepers *keeperCache) *nexusKeeper.Keeper {
// Setting Router will finalize all routes by sealing router
// No more routes can be added
nexusRouter := nexusTypes.NewRouter()
nexusRouter.
// setting validator will finalize all by sealing it
// no more validators can be added
addressValidator := nexusTypes.NewAddressValidator().
AddAddressValidator(evmTypes.ModuleName, evmKeeper.NewAddressValidator()).
AddAddressValidator(axelarnetTypes.ModuleName, axelarnetKeeper.NewAddressValidator(getKeeper[axelarnetKeeper.Keeper](keepers)))

nexusK := nexusKeeper.NewKeeper(appCodec, keys[nexusTypes.StoreKey], keepers.getSubspace(nexusTypes.ModuleName))
nexusK.SetRouter(nexusRouter)
nexusK.SetAddressValidator(addressValidator)

return &nexusK
}

Expand Down
4 changes: 0 additions & 4 deletions x/axelarnet/keeper/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,6 @@ func randomChains() []types.CosmosChain {
return chains
}

func randomNormalizedStr(min, max int) string {
return strings.ReplaceAll(utils.NormalizeString(rand.StrBetween(min, max)), utils.DefaultDelimiter, "-")
}

// randomTransferQueue returns a random (valid) transfer queue state for testing
func randomTransferQueue(cdc codec.Codec, transfers []types.IBCTransfer) utils.QueueState {
qs := utils.QueueState{Items: make(map[string]utils.QueueState_Item)}
Expand Down
91 changes: 91 additions & 0 deletions x/axelarnet/keeper/message_route.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package keeper

import (
"fmt"

storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"

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

// for IBC execution
const gasCost = storetypes.Gas(1000000)

func NewMessageRoute(
keeper Keeper,
ibcK types.IBCKeeper,
feegrantK types.FeegrantKeeper,
bankK types.BankKeeper,
nexusK types.Nexus,
accountK types.AccountKeeper,
) nexus.MessageRoute {
return func(ctx sdk.Context, routingCtx nexus.RoutingContext, msg nexus.GeneralMessage) error {
if routingCtx.Payload == nil {
return fmt.Errorf("payload is required for routing messages to a cosmos chain")
}

bz, err := types.TranslateMessage(msg, routingCtx.Payload)
if err != nil {
return sdkerrors.Wrap(err, "invalid payload")
}

asset, err := escrowAssetToMessageSender(ctx, keeper, feegrantK, bankK, nexusK, accountK, routingCtx, msg)
if err != nil {
return err
}

ctx.GasMeter().ConsumeGas(gasCost, "execute-message")

return ibcK.SendMessage(ctx.Context(), msg.Recipient, asset, string(bz), msg.ID)
}
}

// all general messages are sent from the Axelar general message sender, so receiver can use the packet sender to authenticate the message
// escrowAssetToMessageSender sends the asset to general msg sender account
func escrowAssetToMessageSender(
ctx sdk.Context,
keeper Keeper,
feegrantK types.FeegrantKeeper,
bankK types.BankKeeper,
nexusK types.Nexus,
accountK types.AccountKeeper,
routingCtx nexus.RoutingContext,
msg nexus.GeneralMessage,
) (sdk.Coin, error) {
switch msg.Type() {
case nexus.TypeGeneralMessage:
// pure general message, take dust amount from sender to satisfy ibc transfer requirements
asset := sdk.NewCoin(exported.NativeAsset, sdk.OneInt())
sender := routingCtx.Sender

if !routingCtx.FeeGranter.Empty() {
req := types.RouteMessageRequest{
Sender: routingCtx.Sender,
ID: msg.ID,
Payload: routingCtx.Payload,
Feegranter: routingCtx.FeeGranter,
}
if err := feegrantK.UseGrantedFees(ctx, routingCtx.FeeGranter, routingCtx.Sender, sdk.NewCoins(asset), []sdk.Msg{&req}); err != nil {
return sdk.Coin{}, err
}

sender = routingCtx.FeeGranter
}

return asset, bankK.SendCoins(ctx, sender, types.AxelarGMPAccount, sdk.NewCoins(asset))
case nexus.TypeGeneralMessageWithToken:
// general message with token, get token from corresponding account
asset, sender, err := prepareTransfer(ctx, keeper, nexusK, bankK, accountK, *msg.Asset)
if err != nil {
return sdk.Coin{}, err
}

return asset, bankK.SendCoins(ctx, sender, types.AxelarGMPAccount, sdk.NewCoins(asset))
default:
return sdk.Coin{}, fmt.Errorf("unrecognized message type")
}
}
218 changes: 218 additions & 0 deletions x/axelarnet/keeper/message_route_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
package keeper_test

import (
"context"
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/stretchr/testify/assert"

"github.com/axelarnetwork/axelar-core/testutils/rand"
"github.com/axelarnetwork/axelar-core/x/axelarnet/exported"
"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"
evmtestutils "github.com/axelarnetwork/axelar-core/x/evm/types/testutils"
nexus "github.com/axelarnetwork/axelar-core/x/nexus/exported"
nexustestutils "github.com/axelarnetwork/axelar-core/x/nexus/exported/testutils"
"github.com/axelarnetwork/utils/funcs"
"github.com/axelarnetwork/utils/slices"
. "github.com/axelarnetwork/utils/test"
)

func randPayload() []byte {
bytesType := funcs.Must(abi.NewType("bytes", "bytes", nil))
stringType := funcs.Must(abi.NewType("string", "string", nil))
stringArrayType := funcs.Must(abi.NewType("string[]", "string[]", nil))

argNum := int(rand.I64Between(1, 10))

var args abi.Arguments
for i := 0; i < argNum; i += 1 {
args = append(args, abi.Argument{Type: stringType})
}

schema := abi.Arguments{{Type: stringType}, {Type: stringArrayType}, {Type: stringArrayType}, {Type: bytesType}}
payload := funcs.Must(
schema.Pack(
rand.StrBetween(5, 10),
slices.Expand2(func() string { return rand.Str(5) }, argNum),
slices.Expand2(func() string { return "string" }, argNum),
funcs.Must(args.Pack(slices.Expand2(func() interface{} { return "string" }, argNum)...)),
),
)

return append(funcs.Must(hexutil.Decode(types.CosmWasmV1)), payload...)
}

func randMsg(status nexus.GeneralMessage_Status, payload []byte, token ...*sdk.Coin) nexus.GeneralMessage {
var asset *sdk.Coin
if len(token) > 0 {
asset = token[0]
}

return nexus.GeneralMessage{
ID: rand.NormalizedStr(10),
Sender: nexus.CrossChainAddress{
Chain: nexustestutils.RandomChain(),
Address: rand.NormalizedStr(42),
},
Recipient: nexus.CrossChainAddress{
Chain: nexustestutils.RandomChain(),
Address: rand.NormalizedStr(42),
},
PayloadHash: evmtestutils.RandomHash().Bytes(),
Status: status,
Asset: asset,
SourceTxID: evmtestutils.RandomHash().Bytes(),
SourceTxIndex: uint64(rand.I64Between(0, 100)),
}
}

func TestNewMessageRoute(t *testing.T) {
var (
ctx sdk.Context
routingCtx nexus.RoutingContext
msg nexus.GeneralMessage
route nexus.MessageRoute

k keeper.Keeper
feegrantK *mock.FeegrantKeeperMock
ibcK *mock.IBCKeeperMock
bankK *mock.BankKeeperMock
nexusK *mock.NexusMock
accountK *mock.AccountKeeperMock
)

givenMessageRoute := Given("the message route", func() {
ctx, k, _, feegrantK = setup()

ibcK = &mock.IBCKeeperMock{}
bankK = &mock.BankKeeperMock{}
nexusK = &mock.NexusMock{}
accountK = &mock.AccountKeeperMock{}

route = keeper.NewMessageRoute(k, ibcK, feegrantK, bankK, nexusK, accountK)
})

givenMessageRoute.
When("payload is nil", func() {
routingCtx = nexus.RoutingContext{Payload: nil}
}).
Then("should return error", func(t *testing.T) {
assert.ErrorContains(t, route(ctx, routingCtx, msg), "payload is required")
}).
Run(t)

givenMessageRoute.
When("the message cannot be translated", func() {
routingCtx = nexus.RoutingContext{
Sender: rand.AccAddr(),
FeeGranter: nil,
Payload: rand.Bytes(100),
}
msg = randMsg(nexus.Processing, routingCtx.Payload)
}).
Then("should return error", func(t *testing.T) {
assert.ErrorContains(t, route(ctx, routingCtx, msg), "invalid payload")
}).
Run(t)

whenTheMessageCanBeTranslated := When("the message can be translated", func() {
routingCtx = nexus.RoutingContext{
Sender: rand.AccAddr(),
Payload: randPayload(),
}
})

givenMessageRoute.
When2(whenTheMessageCanBeTranslated).
When("the message has no token transfer", func() {
msg = randMsg(nexus.Processing, routingCtx.Payload)
}).
Branch(
When("the fee granter is not set", func() {
routingCtx.FeeGranter = nil
}).
Then("should deduct the fee from the sender", func(t *testing.T) {
bankK.SendCoinsFunc = func(_ sdk.Context, _, _ sdk.AccAddress, _ sdk.Coins) error { return nil }
ibcK.SendMessageFunc = func(_ context.Context, _ nexus.CrossChainAddress, _ sdk.Coin, _, _ string) error {
return nil
}

assert.NoError(t, route(ctx, routingCtx, msg))

assert.Len(t, bankK.SendCoinsCalls(), 1)
assert.Equal(t, routingCtx.Sender, bankK.SendCoinsCalls()[0].FromAddr)
assert.Equal(t, types.AxelarGMPAccount, bankK.SendCoinsCalls()[0].ToAddr)
assert.Equal(t, sdk.NewCoins(sdk.NewCoin(exported.NativeAsset, sdk.OneInt())), bankK.SendCoinsCalls()[0].Amt)

assert.Len(t, ibcK.SendMessageCalls(), 1)
assert.Equal(t, msg.Recipient, ibcK.SendMessageCalls()[0].Recipient)
assert.Equal(t, sdk.NewCoin(exported.NativeAsset, sdk.OneInt()), ibcK.SendMessageCalls()[0].Asset)
assert.Equal(t, msg.ID, ibcK.SendMessageCalls()[0].ID)
}),

When("the fee granter is set", func() {
routingCtx.FeeGranter = rand.AccAddr()
}).
Then("should deduct the fee from the fee granter", func(t *testing.T) {
feegrantK.UseGrantedFeesFunc = func(_ sdk.Context, granter, _ sdk.AccAddress, _ sdk.Coins, _ []sdk.Msg) error {
return nil
}
bankK.SendCoinsFunc = func(_ sdk.Context, _, _ sdk.AccAddress, _ sdk.Coins) error { return nil }
ibcK.SendMessageFunc = func(_ context.Context, _ nexus.CrossChainAddress, _ sdk.Coin, _, _ string) error {
return nil
}

assert.NoError(t, route(ctx, routingCtx, msg))

assert.Len(t, feegrantK.UseGrantedFeesCalls(), 1)
assert.Equal(t, routingCtx.FeeGranter, feegrantK.UseGrantedFeesCalls()[0].Granter)
assert.Equal(t, routingCtx.Sender, feegrantK.UseGrantedFeesCalls()[0].Grantee)
assert.Equal(t, sdk.NewCoins(sdk.NewCoin(exported.NativeAsset, sdk.OneInt())), feegrantK.UseGrantedFeesCalls()[0].Fee)

assert.Len(t, bankK.SendCoinsCalls(), 1)
assert.Equal(t, routingCtx.FeeGranter, bankK.SendCoinsCalls()[0].FromAddr)
assert.Equal(t, types.AxelarGMPAccount, bankK.SendCoinsCalls()[0].ToAddr)
assert.Equal(t, sdk.NewCoins(sdk.NewCoin(exported.NativeAsset, sdk.OneInt())), bankK.SendCoinsCalls()[0].Amt)

assert.Len(t, ibcK.SendMessageCalls(), 1)
assert.Equal(t, msg.Recipient, ibcK.SendMessageCalls()[0].Recipient)
assert.Equal(t, sdk.NewCoin(exported.NativeAsset, sdk.OneInt()), ibcK.SendMessageCalls()[0].Asset)
assert.Equal(t, msg.ID, ibcK.SendMessageCalls()[0].ID)
}),
).
Run(t)

givenMessageRoute.
When2(whenTheMessageCanBeTranslated).
When("the message has token transfer", func() {
coin := rand.Coin()
msg = randMsg(nexus.Processing, routingCtx.Payload, &coin)
}).
Then("should deduct from the corresponding account", func(t *testing.T) {
nexusK.GetChainByNativeAssetFunc = func(_ sdk.Context, _ string) (nexus.Chain, bool) {
return exported.Axelarnet, true
}
bankK.SendCoinsFunc = func(_ sdk.Context, _, _ sdk.AccAddress, _ sdk.Coins) error { return nil }
ibcK.SendMessageFunc = func(_ context.Context, _ nexus.CrossChainAddress, _ sdk.Coin, _, _ string) error {
return nil
}

assert.NoError(t, route(ctx, routingCtx, msg))

assert.Len(t, bankK.SendCoinsCalls(), 1)
assert.Equal(t, types.GetEscrowAddress(msg.Asset.Denom), bankK.SendCoinsCalls()[0].FromAddr)
assert.Equal(t, types.AxelarGMPAccount, bankK.SendCoinsCalls()[0].ToAddr)
assert.Equal(t, sdk.NewCoins(*msg.Asset), bankK.SendCoinsCalls()[0].Amt)

assert.Len(t, ibcK.SendMessageCalls(), 1)
assert.Equal(t, msg.Recipient, ibcK.SendMessageCalls()[0].Recipient)
assert.Equal(t, *msg.Asset, ibcK.SendMessageCalls()[0].Asset)
assert.Equal(t, msg.ID, ibcK.SendMessageCalls()[0].ID)
}).
Run(t)
}
Loading
Loading