Skip to content

Commit

Permalink
refactor(nexus): add message router for the axelarnet and evm modules (
Browse files Browse the repository at this point in the history
…#2020)

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

* rename and fix tests

* add tests

* set message router

* add comments and rename
  • Loading branch information
fish-sammy authored Nov 8, 2023
1 parent c973b3c commit 7deec05
Show file tree
Hide file tree
Showing 23 changed files with 906 additions and 462 deletions.
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

0 comments on commit 7deec05

Please sign in to comment.