From 3ddf8020d1b39dae51e9df41e4b76bc95b27d18e Mon Sep 17 00:00:00 2001 From: Sammy Liu Date: Mon, 23 Oct 2023 16:39:51 -0400 Subject: [PATCH 1/3] refactor(nexus): add the new methods for storing general messages in Nexus and set them to be processing --- x/nexus/keeper/general_message.go | 104 ++++++++++++++++++++++++++---- x/nexus/keeper/transfer.go | 8 +-- 2 files changed, 94 insertions(+), 18 deletions(-) diff --git a/x/nexus/keeper/general_message.go b/x/nexus/keeper/general_message.go index cb314b252..407d8d63d 100644 --- a/x/nexus/keeper/general_message.go +++ b/x/nexus/keeper/general_message.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "fmt" + "github.com/CosmWasm/wasmd/x/wasm" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -37,6 +38,7 @@ func (k Keeper) GenerateMessageID(ctx sdk.Context) (string, []byte, uint64) { } // SetNewWasmMessage sets the given general message from a wasm contract. +// Deprecated func (k Keeper) SetNewWasmMessage(ctx sdk.Context, msg exported.GeneralMessage) error { if msg.Asset != nil { return fmt.Errorf("asset transfer is not supported") @@ -84,6 +86,7 @@ func (k Keeper) SetNewWasmMessage(ctx sdk.Context, msg exported.GeneralMessage) } // SetNewMessage sets the given general message. If the messages is approved, adds the message ID to approved messages store +// Deprecated func (k Keeper) SetNewMessage(ctx sdk.Context, m exported.GeneralMessage) error { sourceChain, ok := k.GetChain(ctx, m.GetSourceChain()) if !ok { @@ -104,11 +107,11 @@ func (k Keeper) SetNewMessage(ctx sdk.Context, m exported.GeneralMessage) error } if m.Asset != nil { - if err := k.validateTransferAsset(ctx, sourceChain, m.Asset.Denom); err != nil { + if err := k.validateAsset(ctx, sourceChain, m.Asset.Denom); err != nil { return err } - if err := k.validateTransferAsset(ctx, destChain, m.Asset.Denom); err != nil { + if err := k.validateAsset(ctx, destChain, m.Asset.Denom); err != nil { return err } } @@ -142,6 +145,7 @@ func (k Keeper) SetNewMessage(ctx sdk.Context, m exported.GeneralMessage) error */ // SetMessageProcessing sets the general message as processing +// Deprecated func (k Keeper) SetMessageProcessing(ctx sdk.Context, id string) error { m, found := k.GetMessage(ctx, id) if !found { @@ -174,7 +178,7 @@ func (k Keeper) SetMessageExecuted(ctx sdk.Context, id string) error { return fmt.Errorf("general message is not processing") } - k.deleteSentMessageID(ctx, m) + k.deleteProcessingMessageID(ctx, m) m.Status = exported.Executed @@ -194,7 +198,7 @@ func (k Keeper) SetMessageFailed(ctx sdk.Context, id string) error { return fmt.Errorf("general message is not processing") } - k.deleteSentMessageID(ctx, m) + k.deleteProcessingMessageID(ctx, m) m.Status = exported.Failed @@ -203,15 +207,6 @@ func (k Keeper) SetMessageFailed(ctx sdk.Context, id string) error { return k.setMessage(ctx, m) } -// DeleteMessage deletes the general message with associated ID, and also deletes the message ID from the approved messages store -func (k Keeper) DeleteMessage(ctx sdk.Context, id string) { - m, found := k.GetMessage(ctx, id) - if found { - k.deleteSentMessageID(ctx, m) - k.getStore(ctx).DeleteNew(getMessageKey(id)) - } -} - // GetMessage returns the general message by ID func (k Keeper) GetMessage(ctx sdk.Context, id string) (m exported.GeneralMessage, found bool) { return m, k.getStore(ctx).GetNew(getMessageKey(id), &m) @@ -227,10 +222,11 @@ func (k Keeper) setProcessingMessageID(ctx sdk.Context, m exported.GeneralMessag } k.getStore(ctx).SetRawNew(getProcessingMessageKey(m.GetDestinationChain(), m.ID), []byte(m.ID)) + return nil } -func (k Keeper) deleteSentMessageID(ctx sdk.Context, m exported.GeneralMessage) { +func (k Keeper) deleteProcessingMessageID(ctx sdk.Context, m exported.GeneralMessage) { k.getStore(ctx).DeleteNew(getProcessingMessageKey(m.GetDestinationChain(), m.ID)) } @@ -272,3 +268,83 @@ func (k Keeper) GetProcessingMessages(ctx sdk.Context, chain exported.ChainName, return funcs.MustOk(k.GetMessage(ctx, id)) }) } + +func (k Keeper) SetNewMessage_(ctx sdk.Context, msg exported.GeneralMessage) error { + if _, ok := k.GetMessage(ctx, msg.ID); ok { + return fmt.Errorf("general message %s already exists", msg.ID) + } + + if !msg.Is(exported.Approved) { + return fmt.Errorf("new general message has to be approved") + } + + funcs.MustNoErr(ctx.EventManager().EmitTypedEvent(&types.MessageReceived{ + ID: msg.ID, + PayloadHash: msg.PayloadHash, + Sender: msg.Sender, + Recipient: msg.Recipient, + })) + + return k.setMessage(ctx, msg) +} + +func (k Keeper) SetMessageProcessing_(ctx sdk.Context, id string) error { + msg, ok := k.GetMessage(ctx, id) + if !ok { + return fmt.Errorf("general message %s not found", id) + } + + if !msg.Is(exported.Approved) && !msg.Is(exported.Failed) { + return fmt.Errorf("general message has to be approved or failed") + } + + if err := k.validateMessage(ctx, msg); err != nil { + return err + } + + msg.Status = exported.Processing + if err := k.setMessage(ctx, msg); err != nil { + return err + } + + funcs.MustNoErr(k.setProcessingMessageID(ctx, msg)) + funcs.MustNoErr(ctx.EventManager().EmitTypedEvent(&types.MessageProcessing{ID: msg.ID})) + + return nil +} + +func (k Keeper) validateMessage(ctx sdk.Context, msg exported.GeneralMessage) error { + if !msg.Sender.Chain.IsFrom(wasm.ModuleName) { + if err := k.validateAddressAndAsset(ctx, msg.Sender, msg.Asset); err != nil { + return err + } + } + + if !msg.Recipient.Chain.IsFrom(wasm.ModuleName) { + if err := k.validateAddressAndAsset(ctx, msg.Recipient, msg.Asset); err != nil { + return err + } + } + + return nil +} + +func (k Keeper) validateAddressAndAsset(ctx sdk.Context, address exported.CrossChainAddress, asset *sdk.Coin) error { + if _, ok := k.GetChain(ctx, address.Chain.Name); !ok { + return fmt.Errorf("chain %s is not found", address.Chain.Name) + } + + if !k.IsChainActivated(ctx, address.Chain) { + return fmt.Errorf("chain %s is not activated", address.Chain.Name) + } + + if err := k.ValidateAddress(ctx, address); err != nil { + return err + } + + if asset == nil { + return nil + } + + return k.validateAsset(ctx, address.Chain, asset.Denom) +} diff --git a/x/nexus/keeper/transfer.go b/x/nexus/keeper/transfer.go index 65d53f5c9..ba9f74b8b 100644 --- a/x/nexus/keeper/transfer.go +++ b/x/nexus/keeper/transfer.go @@ -100,11 +100,11 @@ func (k Keeper) ComputeTransferFee(ctx sdk.Context, sourceChain exported.Chain, // EnqueueTransfer enqueues an asset transfer to the given recipient address func (k Keeper) EnqueueTransfer(ctx sdk.Context, senderChain exported.Chain, recipient exported.CrossChainAddress, asset sdk.Coin) (exported.TransferID, error) { - if err := k.validateTransferAsset(ctx, senderChain, asset.Denom); err != nil { + if err := k.validateAsset(ctx, senderChain, asset.Denom); err != nil { return 0, err } - if err := k.validateTransferAsset(ctx, recipient.Chain, asset.Denom); err != nil { + if err := k.validateAsset(ctx, recipient.Chain, asset.Denom); err != nil { return 0, err } @@ -178,10 +178,10 @@ func (k Keeper) EnqueueTransfer(ctx sdk.Context, senderChain exported.Chain, rec return transferID, nil } -// validateTransferAsset validates asset if +// validateAsset validates asset if // - chain supports foreign assets, and the asset is registered on the chain // - or asset is the native asset on the chain -func (k Keeper) validateTransferAsset(ctx sdk.Context, chain exported.Chain, asset string) error { +func (k Keeper) validateAsset(ctx sdk.Context, chain exported.Chain, asset string) error { if chain.SupportsForeignAssets && k.IsAssetRegistered(ctx, chain, asset) { return nil } From 9af2430e08b0028c126f33f781cbcbb789b6c214 Mon Sep 17 00:00:00 2001 From: Sammy Liu Date: Wed, 25 Oct 2023 12:48:13 -0400 Subject: [PATCH 2/3] add tests --- x/nexus/keeper/general_message.go | 9 +- x/nexus/keeper/general_message_test.go | 278 ++++++++++++++++++++++++- 2 files changed, 285 insertions(+), 2 deletions(-) diff --git a/x/nexus/keeper/general_message.go b/x/nexus/keeper/general_message.go index 407d8d63d..dfe1748f7 100644 --- a/x/nexus/keeper/general_message.go +++ b/x/nexus/keeper/general_message.go @@ -269,6 +269,7 @@ func (k Keeper) GetProcessingMessages(ctx sdk.Context, chain exported.ChainName, }) } +// SetNewMessage_ sets the given general messsage as approved func (k Keeper) SetNewMessage_(ctx sdk.Context, msg exported.GeneralMessage) error { if _, ok := k.GetMessage(ctx, msg.ID); ok { return fmt.Errorf("general message %s already exists", msg.ID) @@ -288,6 +289,8 @@ func (k Keeper) SetNewMessage_(ctx sdk.Context, msg exported.GeneralMessage) err return k.setMessage(ctx, msg) } +// SetMessageProcessing_ sets the given general message as processing and perform +// validations on the message func (k Keeper) SetMessageProcessing_(ctx sdk.Context, id string) error { msg, ok := k.GetMessage(ctx, id) if !ok { @@ -326,12 +329,16 @@ func (k Keeper) validateMessage(ctx sdk.Context, msg exported.GeneralMessage) er } } + if (msg.Sender.Chain.IsFrom(wasm.ModuleName) || msg.Recipient.Chain.IsFrom(wasm.ModuleName)) && msg.Asset != nil { + return fmt.Errorf("asset transfer is not supported for wasm messages") + } + return nil } func (k Keeper) validateAddressAndAsset(ctx sdk.Context, address exported.CrossChainAddress, asset *sdk.Coin) error { if _, ok := k.GetChain(ctx, address.Chain.Name); !ok { - return fmt.Errorf("chain %s is not found", address.Chain.Name) + return fmt.Errorf("chain %s is not registered", address.Chain.Name) } if !k.IsChainActivated(ctx, address.Chain) { diff --git a/x/nexus/keeper/general_message_test.go b/x/nexus/keeper/general_message_test.go index 60e800fcc..ec7ceab79 100644 --- a/x/nexus/keeper/general_message_test.go +++ b/x/nexus/keeper/general_message_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/CosmWasm/wasmd/x/wasm" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/assert" @@ -25,6 +26,282 @@ import ( . "github.com/axelarnetwork/utils/test" ) +func randMsg(status exported.GeneralMessage_Status, withAsset ...bool) exported.GeneralMessage { + var asset *sdk.Coin + if len(withAsset) > 0 && withAsset[0] { + coin := rand.Coin() + asset = &coin + } + + return exported.GeneralMessage{ + ID: rand.NormalizedStr(10), + Sender: exported.CrossChainAddress{ + Chain: nexustestutils.RandomChain(), + Address: rand.NormalizedStr(42), + }, + Recipient: exported.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 TestSetNewMessage_(t *testing.T) { + var ( + msg exported.GeneralMessage + ctx sdk.Context + keeper nexus.Keeper + ) + + cfg := app.MakeEncodingConfig() + givenKeeper := Given("the keeper", func() { + keeper, ctx = setup(cfg) + }) + + givenKeeper. + When("the message already exists", func() { + msg = randMsg(exported.Approved, true) + keeper.SetNewMessage_(ctx, msg) + }). + Then("should return error", func(t *testing.T) { + assert.ErrorContains(t, keeper.SetNewMessage_(ctx, msg), "already exists") + }). + Run(t) + + givenKeeper. + When("the message has invalid status", func() { + msg = randMsg(exported.Processing) + }). + Then("should return error", func(t *testing.T) { + assert.ErrorContains(t, keeper.SetNewMessage_(ctx, msg), "new general message has to be approved") + }). + Run(t) + + givenKeeper. + When("the message is valid", func() { + msg = randMsg(exported.Approved) + }). + Then("should store the message", func(t *testing.T) { + assert.NoError(t, keeper.SetNewMessage_(ctx, msg)) + + actual, ok := keeper.GetMessage(ctx, msg.ID) + assert.True(t, ok) + assert.Equal(t, msg, actual) + }). + Run(t) +} + +func TestSetMessageProcessing_(t *testing.T) { + var ( + msg exported.GeneralMessage + ctx sdk.Context + keeper nexus.Keeper + ) + + cfg := app.MakeEncodingConfig() + givenKeeper := Given("the keeper", func() { + keeper, ctx = setup(cfg) + }) + + givenKeeper. + When("the message doesn't exist", func() {}). + Then("should return error", func(t *testing.T) { + assert.ErrorContains(t, keeper.SetMessageProcessing_(ctx, rand.NormalizedStr(10)), "not found") + }). + Run(t) + + givenKeeper. + When("the message is being processed", func() { + msg = randMsg(exported.Approved) + msg.Sender = exported.CrossChainAddress{ + Chain: evm.Ethereum, + Address: evmtestutils.RandomAddress().Hex(), + } + msg.Recipient = exported.CrossChainAddress{ + Chain: evm.Ethereum, + Address: evmtestutils.RandomAddress().Hex(), + } + + keeper.SetNewMessage_(ctx, msg) + keeper.SetMessageProcessing_(ctx, msg.ID) + }). + Then("should return error", func(t *testing.T) { + assert.ErrorContains(t, keeper.SetMessageProcessing_(ctx, msg.ID), "general message has to be approved or failed") + }). + Run(t) + + givenKeeper. + When("the message is from wasm", func() { + msg = randMsg(exported.Approved) + msg.Sender = exported.CrossChainAddress{ + Chain: nexustestutils.RandomChain(), + Address: rand.NormalizedStr(42), + } + msg.Sender.Chain.Module = wasm.ModuleName + }). + Branch( + When("the destination chain is not registered", func() { + msg.Recipient.Chain = nexustestutils.RandomChain() + + keeper.SetNewMessage_(ctx, msg) + }). + Then("should return error", func(t *testing.T) { + assert.ErrorContains(t, keeper.SetMessageProcessing_(ctx, msg.ID), "is not registered") + }), + + When("the destination chain is not activated", func() { + msg.Recipient = exported.CrossChainAddress{ + Chain: evm.Ethereum, + Address: evmtestutils.RandomAddress().Hex(), + } + + keeper.DeactivateChain(ctx, msg.Recipient.Chain) + keeper.SetNewMessage_(ctx, msg) + }). + Then("should return error", func(t *testing.T) { + assert.ErrorContains(t, keeper.SetMessageProcessing_(ctx, msg.ID), "is not activated") + }), + + When("the destination address is invalid", func() { + msg.Recipient = exported.CrossChainAddress{ + Chain: evm.Ethereum, + Address: rand.NormalizedStr(42), + } + + keeper.SetNewMessage_(ctx, msg) + }). + Then("should return error", func(t *testing.T) { + assert.ErrorContains(t, keeper.SetMessageProcessing_(ctx, msg.ID), "not an hex address") + }), + + When("the destination chain does't support the asset", func() { + msg.Recipient = exported.CrossChainAddress{ + Chain: evm.Ethereum, + Address: evmtestutils.RandomAddress().Hex(), + } + asset := rand.Coin() + msg.Asset = &asset + + keeper.SetNewMessage_(ctx, msg) + }). + Then("should return error", func(t *testing.T) { + assert.ErrorContains(t, keeper.SetMessageProcessing_(ctx, msg.ID), "does not support foreign asset") + }), + + When("asset is set", func() { + msg.Recipient = exported.CrossChainAddress{ + Chain: evm.Ethereum, + Address: evmtestutils.RandomAddress().Hex(), + } + msg.Asset = &sdk.Coin{Denom: "external-erc-20", Amount: sdk.NewInt(100)} + + keeper.SetNewMessage_(ctx, msg) + }). + Then("should return error", func(t *testing.T) { + assert.ErrorContains(t, keeper.SetMessageProcessing_(ctx, msg.ID), "asset transfer is not supported for wasm messages") + }), + ). + Run(t) + + givenKeeper. + When("the message is to wasm", func() { + msg = randMsg(exported.Approved) + msg.Recipient = exported.CrossChainAddress{ + Chain: nexustestutils.RandomChain(), + Address: rand.NormalizedStr(42), + } + msg.Recipient.Chain.Module = wasm.ModuleName + }). + Branch( + When("the sender chain is not registered", func() { + msg.Sender.Chain = nexustestutils.RandomChain() + + keeper.SetNewMessage_(ctx, msg) + }). + Then("should return error", func(t *testing.T) { + assert.ErrorContains(t, keeper.SetMessageProcessing_(ctx, msg.ID), "is not registered") + }), + + When("the sender chain is not activated", func() { + msg.Sender = exported.CrossChainAddress{ + Chain: evm.Ethereum, + Address: evmtestutils.RandomAddress().Hex(), + } + + keeper.DeactivateChain(ctx, msg.Sender.Chain) + keeper.SetNewMessage_(ctx, msg) + }). + Then("should return error", func(t *testing.T) { + assert.ErrorContains(t, keeper.SetMessageProcessing_(ctx, msg.ID), "is not activated") + }), + + When("the sender address is invalid", func() { + msg.Sender = exported.CrossChainAddress{ + Chain: evm.Ethereum, + Address: rand.NormalizedStr(42), + } + + keeper.SetNewMessage_(ctx, msg) + }). + Then("should return error", func(t *testing.T) { + assert.ErrorContains(t, keeper.SetMessageProcessing_(ctx, msg.ID), "not an hex address") + }), + + When("the sender chain does't support the asset", func() { + msg.Sender = exported.CrossChainAddress{ + Chain: evm.Ethereum, + Address: evmtestutils.RandomAddress().Hex(), + } + asset := rand.Coin() + msg.Asset = &asset + + keeper.SetNewMessage_(ctx, msg) + }). + Then("should return error", func(t *testing.T) { + assert.ErrorContains(t, keeper.SetMessageProcessing_(ctx, msg.ID), "does not support foreign asset") + }), + + When("asset is set", func() { + msg.Sender = exported.CrossChainAddress{ + Chain: evm.Ethereum, + Address: evmtestutils.RandomAddress().Hex(), + } + msg.Asset = &sdk.Coin{Denom: "external-erc-20", Amount: sdk.NewInt(100)} + + keeper.SetNewMessage_(ctx, msg) + }). + Then("should return error", func(t *testing.T) { + assert.ErrorContains(t, keeper.SetMessageProcessing_(ctx, msg.ID), "asset transfer is not supported for wasm messages") + }), + ). + Run(t) + + givenKeeper. + When("the message is valid", func() { + msg = randMsg(exported.Approved) + msg.Sender.Chain.Module = wasm.ModuleName + msg.Recipient = exported.CrossChainAddress{ + Chain: evm.Ethereum, + Address: evmtestutils.RandomAddress().Hex(), + } + + keeper.SetNewMessage_(ctx, msg) + }). + Then("should set the message status to processing", func(t *testing.T) { + assert.NoError(t, keeper.SetMessageProcessing_(ctx, msg.ID)) + + actual, ok := keeper.GetMessage(ctx, msg.ID) + assert.True(t, ok) + assert.Equal(t, exported.Processing, actual.Status) + }). + Run(t) +} + func randWasmMsg(status exported.GeneralMessage_Status) exported.GeneralMessage { return exported.GeneralMessage{ ID: rand.NormalizedStr(10), @@ -521,5 +798,4 @@ func TestGetSentMessages(t *testing.T) { assert.Equal(t, dest2Msgs, toMap(consumeSent(dest2, 100))) assert.Equal(t, dest3Msgs, toMap(consumeSent(dest3, 100))) assert.Equal(t, dest4Msgs, toMap(consumeSent(dest4, 100))) - } From a89f362a1c8481c0afce0b1df7f5f3fe91c3cc87 Mon Sep 17 00:00:00 2001 From: Sammy Liu Date: Wed, 25 Oct 2023 13:04:01 -0400 Subject: [PATCH 3/3] add more comments --- x/nexus/keeper/general_message.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/x/nexus/keeper/general_message.go b/x/nexus/keeper/general_message.go index dfe1748f7..3a7c58e6d 100644 --- a/x/nexus/keeper/general_message.go +++ b/x/nexus/keeper/general_message.go @@ -38,7 +38,7 @@ func (k Keeper) GenerateMessageID(ctx sdk.Context) (string, []byte, uint64) { } // SetNewWasmMessage sets the given general message from a wasm contract. -// Deprecated +// Deprecated: use SetNewMessage_ instead func (k Keeper) SetNewWasmMessage(ctx sdk.Context, msg exported.GeneralMessage) error { if msg.Asset != nil { return fmt.Errorf("asset transfer is not supported") @@ -86,7 +86,7 @@ func (k Keeper) SetNewWasmMessage(ctx sdk.Context, msg exported.GeneralMessage) } // SetNewMessage sets the given general message. If the messages is approved, adds the message ID to approved messages store -// Deprecated +// Deprecated: use SetNewMessage_ instead func (k Keeper) SetNewMessage(ctx sdk.Context, m exported.GeneralMessage) error { sourceChain, ok := k.GetChain(ctx, m.GetSourceChain()) if !ok { @@ -145,7 +145,7 @@ func (k Keeper) SetNewMessage(ctx sdk.Context, m exported.GeneralMessage) error */ // SetMessageProcessing sets the general message as processing -// Deprecated +// Deprecated: use SetMessageProcessing_ instead func (k Keeper) SetMessageProcessing(ctx sdk.Context, id string) error { m, found := k.GetMessage(ctx, id) if !found { @@ -297,7 +297,7 @@ func (k Keeper) SetMessageProcessing_(ctx sdk.Context, id string) error { return fmt.Errorf("general message %s not found", id) } - if !msg.Is(exported.Approved) && !msg.Is(exported.Failed) { + if !(msg.Is(exported.Approved) || msg.Is(exported.Failed)) { return fmt.Errorf("general message has to be approved or failed") } @@ -317,18 +317,23 @@ func (k Keeper) SetMessageProcessing_(ctx sdk.Context, id string) error { } func (k Keeper) validateMessage(ctx sdk.Context, msg exported.GeneralMessage) error { + // only validate sender and asset if it's not from wasm. + // the nexus module doesn't know how to validate wasm chains and addresses. if !msg.Sender.Chain.IsFrom(wasm.ModuleName) { if err := k.validateAddressAndAsset(ctx, msg.Sender, msg.Asset); err != nil { return err } } + // only validate recipient and asset if it's not to wasm. + // the nexus module doesn't know how to validate wasm chains and addresses. if !msg.Recipient.Chain.IsFrom(wasm.ModuleName) { if err := k.validateAddressAndAsset(ctx, msg.Recipient, msg.Asset); err != nil { return err } } + // asset is not supported for wasm messages if (msg.Sender.Chain.IsFrom(wasm.ModuleName) || msg.Recipient.Chain.IsFrom(wasm.ModuleName)) && msg.Asset != nil { return fmt.Errorf("asset transfer is not supported for wasm messages") } @@ -336,6 +341,7 @@ func (k Keeper) validateMessage(ctx sdk.Context, msg exported.GeneralMessage) er return nil } +// validateAddressAndAsset validates 1) chain existence, 2) chain activation, 3) address, 4) asset func (k Keeper) validateAddressAndAsset(ctx sdk.Context, address exported.CrossChainAddress, asset *sdk.Coin) error { if _, ok := k.GetChain(ctx, address.Chain.Name); !ok { return fmt.Errorf("chain %s is not registered", address.Chain.Name)