Skip to content

Commit

Permalink
unit tests for msg server
Browse files Browse the repository at this point in the history
  • Loading branch information
kingpinXD committed Feb 8, 2024
1 parent a35fe2d commit 6c6c7b7
Show file tree
Hide file tree
Showing 8 changed files with 501 additions and 179 deletions.
2 changes: 1 addition & 1 deletion proto/crosschain/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ message MsgAbortStuckCCTXResponse {}
message MsgRefundAbortedCCTX {
string creator = 1;
string cctx_index = 2;
string receiver_btc_refund = 3;
string refund_address = 3; // if not provided, the refund will be sent to the sender/txOrgin
}

message MsgRefundAbortedCCTXResponse {}
41 changes: 31 additions & 10 deletions x/crosschain/keeper/msg_server_refund_aborted_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package keeper
import (
errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/zeta-chain/zetacore/common"
"github.com/zeta-chain/zetacore/x/crosschain/types"
observertypes "github.com/zeta-chain/zetacore/x/observer/types"
Expand All @@ -17,11 +18,17 @@ func (k msgServer) RefundAbortedCCTX(goCtx context.Context, msg *types.MsgRefund
if msg.Creator != k.zetaObserverKeeper.GetParams(ctx).GetAdminPolicyAccount(observertypes.Policy_Type_group2) {
return nil, observertypes.ErrNotAuthorized
}

// check if the cctx exists
cctx, found := k.GetCrossChainTx(ctx, msg.CctxIndex)
if !found {
return nil, types.ErrCannotFindCctx
}
// make sure separate refund address is provided for bitcoin chain as we cannot refund to tx origin or sender in this case
if common.IsBitcoinChain(cctx.InboundTxParams.SenderChainId) && msg.RefundAddress == "" {
return nil, errorsmod.Wrap(types.ErrInvalidAddress, "invalid refund address")
}

// check if the cctx is aborted
if cctx.CctxStatus.Status != types.CctxStatus_Aborted {
return nil, errorsmod.Wrap(types.ErrInvalidStatus, "CCTX is not aborted")
Expand All @@ -31,21 +38,35 @@ func (k msgServer) RefundAbortedCCTX(goCtx context.Context, msg *types.MsgRefund
return nil, errorsmod.Wrap(types.ErrUnableProcessRefund, "CCTX is already refunded")
}

// Set the proper refund address.
// For BTC sender chain the refund address is the one provided in the message in the RefundAddress field.
// For EVM chain with coin type ERC20 the refund address is the sender , but can be overridden by the RefundAddress field in the message.
// For EVM chain with coin type Zeta the refund address is the tx origin, but can be overridden by the RefundAddress field in the message.
// For EVM chain with coin type Gas the refund address is the tx origin, but can be overridden by the RefundAddress field in the message.

refundAddress := ethcommon.HexToAddress(cctx.InboundTxParams.TxOrigin)
if cctx.InboundTxParams.CoinType == common.CoinType_ERC20 {
refundAddress = ethcommon.HexToAddress(cctx.InboundTxParams.Sender)
}
if msg.RefundAddress != "" {
refundAddress = ethcommon.HexToAddress(msg.RefundAddress)
}
// Make sure the refund address is valid
if refundAddress == (ethcommon.Address{}) {
return nil, errorsmod.Wrap(types.ErrInvalidAddress, "invalid refund address")
}

// refund the amount
if common.IsEVMChain(cctx.InboundTxParams.SenderChainId) {
err := k.RefundAbortedAmountOnZetaChainForEvmChain(ctx, cctx)
if err != nil {
return nil, errorsmod.Wrap(types.ErrUnableProcessRefund, err.Error())
}
} else if common.IsBitcoinChain(cctx.InboundTxParams.SenderChainId) {
err := k.RefundAbortedAmountOnZetaChainForBitcoinChain(ctx, cctx, msg.ReceiverBtcRefund)
if err != nil {
return nil, errorsmod.Wrap(types.ErrUnableProcessRefund, err.Error())
}
err := k.RefundAbortedAmountOnZetaChain(ctx, cctx, refundAddress)
if err != nil {
return nil, errorsmod.Wrap(types.ErrUnableProcessRefund, err.Error())
}

// set the cctx as refunded
cctx.IsRefunded = true
k.SetCrossChainTx(ctx, cctx)

// Include the refunded amount in ZetaAccount, so we can now remove it from the ZetaAbortedAmount counter.
if cctx.GetCurrentOutTxParam().CoinType == common.CoinType_Zeta {
k.RemoveZetaAbortedAmount(ctx, cctx.GetCurrentOutTxParam().Amount)
}
Expand Down
271 changes: 271 additions & 0 deletions x/crosschain/keeper/msg_server_refund_aborted_tx_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
package keeper_test

import (
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
"github.com/zeta-chain/zetacore/cmd/zetacored/config"
"github.com/zeta-chain/zetacore/common"
keepertest "github.com/zeta-chain/zetacore/testutil/keeper"
"github.com/zeta-chain/zetacore/testutil/sample"
"github.com/zeta-chain/zetacore/x/crosschain/keeper"
crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types"
fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types"
)

func TestMsgServer_RefundAbortedCCTX(t *testing.T) {
t.Run("Successfully refund tx for coin-type Gas", func(t *testing.T) {
k, ctx, sdkk, zk := keepertest.CrosschainKeeper(t)
admin := sample.AccAddress()
chainID := getValidEthChainID(t)
setAdminPolicies(ctx, zk, admin)
msgServer := keeper.NewMsgServerImpl(*k)
k.GetAuthKeeper().GetModuleAccount(ctx, fungibletypes.ModuleName)
cctx := sample.CrossChainTx(t, "sample-index")
cctx.CctxStatus.Status = crosschaintypes.CctxStatus_Aborted
cctx.IsRefunded = false
cctx.InboundTxParams.TxOrigin = cctx.InboundTxParams.Sender
cctx.InboundTxParams.SenderChainId = chainID
cctx.InboundTxParams.CoinType = common.CoinType_Gas
k.SetCrossChainTx(ctx, *cctx)
deploySystemContracts(t, ctx, zk.FungibleKeeper, sdkk.EvmKeeper)
zrc20 := setupGasCoin(t, ctx, zk.FungibleKeeper, sdkk.EvmKeeper, cctx.InboundTxParams.SenderChainId, "foobar", "foobar")
_, err := msgServer.RefundAbortedCCTX(ctx, &crosschaintypes.MsgRefundAbortedCCTX{
Creator: admin,
CctxIndex: cctx.Index,
RefundAddress: "",
})
require.NoError(t, err)
refundAddress := ethcommon.HexToAddress(cctx.InboundTxParams.TxOrigin)
balance, err := zk.FungibleKeeper.BalanceOfZRC4(ctx, zrc20, refundAddress)
require.NoError(t, err)
require.Equal(t, cctx.InboundTxParams.Amount.Uint64(), balance.Uint64())
c, found := k.GetCrossChainTx(ctx, cctx.Index)
require.True(t, found)
require.True(t, c.IsRefunded)
})
t.Run("Successfully refund tx for coin-type Zeta", func(t *testing.T) {
k, ctx, sdkk, zk := keepertest.CrosschainKeeper(t)
admin := sample.AccAddress()
chainID := getValidEthChainID(t)
setAdminPolicies(ctx, zk, admin)
msgServer := keeper.NewMsgServerImpl(*k)
k.GetAuthKeeper().GetModuleAccount(ctx, fungibletypes.ModuleName)
cctx := sample.CrossChainTx(t, "sample-index")
cctx.CctxStatus.Status = crosschaintypes.CctxStatus_Aborted
cctx.IsRefunded = false
cctx.InboundTxParams.TxOrigin = cctx.InboundTxParams.Sender
cctx.InboundTxParams.SenderChainId = chainID
cctx.InboundTxParams.CoinType = common.CoinType_Zeta
k.SetCrossChainTx(ctx, *cctx)
deploySystemContracts(t, ctx, zk.FungibleKeeper, sdkk.EvmKeeper)
_, err := msgServer.RefundAbortedCCTX(ctx, &crosschaintypes.MsgRefundAbortedCCTX{
Creator: admin,
CctxIndex: cctx.Index,
RefundAddress: "",
})
require.NoError(t, err)
refundAddress := ethcommon.HexToAddress(cctx.InboundTxParams.TxOrigin)
refundAddressCosmos := sdk.AccAddress(refundAddress.Bytes())
balance := sdkk.BankKeeper.GetBalance(ctx, refundAddressCosmos, config.BaseDenom)
require.Equal(t, cctx.InboundTxParams.Amount.Uint64(), balance.Amount.Uint64())
c, found := k.GetCrossChainTx(ctx, cctx.Index)
require.True(t, found)
require.True(t, c.IsRefunded)
})
t.Run("Successfully refund to refund address if provided", func(t *testing.T) {
k, ctx, sdkk, zk := keepertest.CrosschainKeeper(t)
admin := sample.AccAddress()
chainID := getValidEthChainID(t)
setAdminPolicies(ctx, zk, admin)
msgServer := keeper.NewMsgServerImpl(*k)
k.GetAuthKeeper().GetModuleAccount(ctx, fungibletypes.ModuleName)
cctx := sample.CrossChainTx(t, "sample-index")
cctx.CctxStatus.Status = crosschaintypes.CctxStatus_Aborted
cctx.IsRefunded = false
cctx.InboundTxParams.TxOrigin = cctx.InboundTxParams.Sender
cctx.InboundTxParams.SenderChainId = chainID
cctx.InboundTxParams.CoinType = common.CoinType_Zeta
k.SetCrossChainTx(ctx, *cctx)
deploySystemContracts(t, ctx, zk.FungibleKeeper, sdkk.EvmKeeper)
refundAddress := sample.EthAddress()
_, err := msgServer.RefundAbortedCCTX(ctx, &crosschaintypes.MsgRefundAbortedCCTX{
Creator: admin,
CctxIndex: cctx.Index,
RefundAddress: refundAddress.String(),
})
require.NoError(t, err)
refundAddressCosmos := sdk.AccAddress(refundAddress.Bytes())
balance := sdkk.BankKeeper.GetBalance(ctx, refundAddressCosmos, config.BaseDenom)
require.Equal(t, cctx.InboundTxParams.Amount.Uint64(), balance.Amount.Uint64())
c, found := k.GetCrossChainTx(ctx, cctx.Index)
require.True(t, found)
require.True(t, c.IsRefunded)
})
t.Run("Failed refund if address provided is invalid", func(t *testing.T) {
k, ctx, sdkk, zk := keepertest.CrosschainKeeper(t)
admin := sample.AccAddress()
chainID := getValidEthChainID(t)
setAdminPolicies(ctx, zk, admin)
msgServer := keeper.NewMsgServerImpl(*k)
k.GetAuthKeeper().GetModuleAccount(ctx, fungibletypes.ModuleName)
cctx := sample.CrossChainTx(t, "sample-index")
cctx.CctxStatus.Status = crosschaintypes.CctxStatus_Aborted
cctx.IsRefunded = false
cctx.InboundTxParams.TxOrigin = cctx.InboundTxParams.Sender
cctx.InboundTxParams.SenderChainId = chainID
cctx.InboundTxParams.CoinType = common.CoinType_Zeta
k.SetCrossChainTx(ctx, *cctx)
deploySystemContracts(t, ctx, zk.FungibleKeeper, sdkk.EvmKeeper)
_, err := msgServer.RefundAbortedCCTX(ctx, &crosschaintypes.MsgRefundAbortedCCTX{
Creator: admin,
CctxIndex: cctx.Index,
RefundAddress: "invalid-address",
})
require.ErrorContains(t, err, "invalid refund address")
})
t.Run("Successfully refund tx for coin-type ERC20", func(t *testing.T) {
k, ctx, sdkk, zk := keepertest.CrosschainKeeper(t)
admin := sample.AccAddress()
chainID := getValidEthChainID(t)
asset := sample.EthAddress().String()
setAdminPolicies(ctx, zk, admin)
msgServer := keeper.NewMsgServerImpl(*k)
k.GetAuthKeeper().GetModuleAccount(ctx, fungibletypes.ModuleName)
cctx := sample.CrossChainTx(t, "sample-index")
cctx.CctxStatus.Status = crosschaintypes.CctxStatus_Aborted
cctx.IsRefunded = false
cctx.InboundTxParams.SenderChainId = chainID
cctx.InboundTxParams.CoinType = common.CoinType_ERC20
cctx.InboundTxParams.Asset = asset
k.SetCrossChainTx(ctx, *cctx)
// deploy zrc20
deploySystemContracts(t, ctx, zk.FungibleKeeper, sdkk.EvmKeeper)
zrc20Addr := deployZRC20(
t,
ctx,
zk.FungibleKeeper,
sdkk.EvmKeeper,
chainID,
"bar",
asset,
"bar",
)
_, err := msgServer.RefundAbortedCCTX(ctx, &crosschaintypes.MsgRefundAbortedCCTX{
Creator: admin,
CctxIndex: cctx.Index,
RefundAddress: "",
})
require.NoError(t, err)
refundAddress := ethcommon.HexToAddress(cctx.InboundTxParams.Sender)
balance, err := zk.FungibleKeeper.BalanceOfZRC4(ctx, zrc20Addr, refundAddress)
require.NoError(t, err)
require.Equal(t, cctx.InboundTxParams.Amount.Uint64(), balance.Uint64())
c, found := k.GetCrossChainTx(ctx, cctx.Index)
require.True(t, found)
require.True(t, c.IsRefunded)
})
t.Run("Successfully refund tx for coin-type Gas with BTC sender", func(t *testing.T) {
k, ctx, sdkk, zk := keepertest.CrosschainKeeper(t)
admin := sample.AccAddress()
chainID := getValidBtcChainID()
setAdminPolicies(ctx, zk, admin)
msgServer := keeper.NewMsgServerImpl(*k)
k.GetAuthKeeper().GetModuleAccount(ctx, fungibletypes.ModuleName)
cctx := sample.CrossChainTx(t, "sample-index")
cctx.CctxStatus.Status = crosschaintypes.CctxStatus_Aborted
cctx.IsRefunded = false
cctx.InboundTxParams.TxOrigin = cctx.InboundTxParams.Sender
cctx.InboundTxParams.SenderChainId = chainID
cctx.InboundTxParams.CoinType = common.CoinType_Gas
k.SetCrossChainTx(ctx, *cctx)
deploySystemContracts(t, ctx, zk.FungibleKeeper, sdkk.EvmKeeper)
zrc20 := setupGasCoin(t, ctx, zk.FungibleKeeper, sdkk.EvmKeeper, cctx.InboundTxParams.SenderChainId, "foobar", "foobar")
_, err := msgServer.RefundAbortedCCTX(ctx, &crosschaintypes.MsgRefundAbortedCCTX{
Creator: admin,
CctxIndex: cctx.Index,
RefundAddress: cctx.InboundTxParams.TxOrigin,
})
require.NoError(t, err)
refundAddress := ethcommon.HexToAddress(cctx.InboundTxParams.TxOrigin)
balance, err := zk.FungibleKeeper.BalanceOfZRC4(ctx, zrc20, refundAddress)
require.NoError(t, err)
require.Equal(t, cctx.InboundTxParams.Amount.Uint64(), balance.Uint64())
c, found := k.GetCrossChainTx(ctx, cctx.Index)
require.True(t, found)
require.True(t, c.IsRefunded)
})
t.Run("Fail refund if status is not aborted", func(t *testing.T) {
k, ctx, sdkk, zk := keepertest.CrosschainKeeper(t)
admin := sample.AccAddress()
chainID := getValidEthChainID(t)
setAdminPolicies(ctx, zk, admin)
msgServer := keeper.NewMsgServerImpl(*k)
k.GetAuthKeeper().GetModuleAccount(ctx, fungibletypes.ModuleName)
cctx := sample.CrossChainTx(t, "sample-index")
cctx.CctxStatus.Status = crosschaintypes.CctxStatus_PendingOutbound
cctx.IsRefunded = false
cctx.InboundTxParams.TxOrigin = cctx.InboundTxParams.Sender
cctx.InboundTxParams.SenderChainId = chainID
cctx.InboundTxParams.CoinType = common.CoinType_Gas
k.SetCrossChainTx(ctx, *cctx)
deploySystemContracts(t, ctx, zk.FungibleKeeper, sdkk.EvmKeeper)

_, err := msgServer.RefundAbortedCCTX(ctx, &crosschaintypes.MsgRefundAbortedCCTX{
Creator: admin,
CctxIndex: cctx.Index,
RefundAddress: "",
})
require.ErrorContains(t, err, "CCTX is not aborted")
c, found := k.GetCrossChainTx(ctx, cctx.Index)
require.True(t, found)
require.False(t, c.IsRefunded)
})
t.Run("Fail refund if status cctx not found", func(t *testing.T) {
k, ctx, sdkk, zk := keepertest.CrosschainKeeper(t)
admin := sample.AccAddress()
chainID := getValidEthChainID(t)
setAdminPolicies(ctx, zk, admin)
msgServer := keeper.NewMsgServerImpl(*k)
k.GetAuthKeeper().GetModuleAccount(ctx, fungibletypes.ModuleName)
cctx := sample.CrossChainTx(t, "sample-index")
cctx.CctxStatus.Status = crosschaintypes.CctxStatus_PendingOutbound
cctx.IsRefunded = false
cctx.InboundTxParams.TxOrigin = cctx.InboundTxParams.Sender
cctx.InboundTxParams.SenderChainId = chainID
cctx.InboundTxParams.CoinType = common.CoinType_Gas
deploySystemContracts(t, ctx, zk.FungibleKeeper, sdkk.EvmKeeper)

_, err := msgServer.RefundAbortedCCTX(ctx, &crosschaintypes.MsgRefundAbortedCCTX{
Creator: admin,
CctxIndex: cctx.Index,
RefundAddress: "",
})
require.ErrorContains(t, err, "cannot find cctx")
})
t.Run("Fail refund if refund address not provided for BTC chain", func(t *testing.T) {
k, ctx, sdkk, zk := keepertest.CrosschainKeeper(t)
admin := sample.AccAddress()
chainID := getValidBtcChainID()
setAdminPolicies(ctx, zk, admin)
msgServer := keeper.NewMsgServerImpl(*k)
k.GetAuthKeeper().GetModuleAccount(ctx, fungibletypes.ModuleName)
cctx := sample.CrossChainTx(t, "sample-index")
cctx.CctxStatus.Status = crosschaintypes.CctxStatus_Aborted
cctx.IsRefunded = false
cctx.InboundTxParams.TxOrigin = cctx.InboundTxParams.Sender
cctx.InboundTxParams.SenderChainId = chainID
cctx.InboundTxParams.CoinType = common.CoinType_Gas
k.SetCrossChainTx(ctx, *cctx)
deploySystemContracts(t, ctx, zk.FungibleKeeper, sdkk.EvmKeeper)
_ = setupGasCoin(t, ctx, zk.FungibleKeeper, sdkk.EvmKeeper, cctx.InboundTxParams.SenderChainId, "foobar", "foobar")
_, err := msgServer.RefundAbortedCCTX(ctx, &crosschaintypes.MsgRefundAbortedCCTX{
Creator: admin,
CctxIndex: cctx.Index,
RefundAddress: "",
})
require.ErrorContains(t, err, "invalid refund address")
})
}
6 changes: 4 additions & 2 deletions x/crosschain/keeper/msg_server_vote_inbound_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/zeta-chain/zetacore/common"
"github.com/zeta-chain/zetacore/x/crosschain/types"
observerKeeper "github.com/zeta-chain/zetacore/x/observer/keeper"
Expand Down Expand Up @@ -192,8 +193,9 @@ func (k msgServer) VoteOnObservedInboundTx(goCtx context.Context, msg *types.Msg

// gas payment for erc20 type might fail because no liquidity pool is defined to swap the zrc20 token into the gas token
// in this gas we should refund the sender on ZetaChain
if cctx.InboundTxParams.CoinType == common.CoinType_ERC20 {
if err := k.RefundAbortedAmountOnZetaChainForEvmChain(ctx, cctx); err != nil {
refundAddress := ethcommon.HexToAddress(cctx.InboundTxParams.Sender)
if cctx.InboundTxParams.CoinType == common.CoinType_ERC20 && refundAddress != (ethcommon.Address{}) {
if err := k.RefundAbortedAmountOnZetaChain(ctx, cctx, refundAddress); err != nil {
// log the error
k.Logger(ctx).Error("failed to refund amount of aborted cctx on ZetaChain",
"error", err,
Expand Down
Loading

0 comments on commit 6c6c7b7

Please sign in to comment.