From 4abefdb2450d564f30676d9a72c48df95ce390d1 Mon Sep 17 00:00:00 2001 From: skosito Date: Tue, 4 Jun 2024 17:24:51 +0200 Subject: [PATCH 01/38] move code to validate outbound methods --- x/crosschain/keeper/cctx_gateway_zevm.go | 62 +------------ x/crosschain/keeper/cctx_orchestrator.go | 93 +++++++++++++++++++ .../keeper/msg_server_vote_outbound_tx.go | 2 +- 3 files changed, 96 insertions(+), 61 deletions(-) create mode 100644 x/crosschain/keeper/cctx_orchestrator.go diff --git a/x/crosschain/keeper/cctx_gateway_zevm.go b/x/crosschain/keeper/cctx_gateway_zevm.go index 89e7c5a152..e37b45cb81 100644 --- a/x/crosschain/keeper/cctx_gateway_zevm.go +++ b/x/crosschain/keeper/cctx_gateway_zevm.go @@ -1,8 +1,6 @@ package keeper import ( - "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/zeta-chain/zetacore/x/crosschain/types" @@ -38,63 +36,7 @@ Instead we use a temporary context to make changes and then commit the context o New CCTX status after preprocessing is returned. */ func (c CCTXGatewayZEVM) InitiateOutbound(ctx sdk.Context, cctx *types.CrossChainTx) (newCCTXStatus types.CctxStatus) { - tmpCtx, commit := ctx.CacheContext() - isContractReverted, err := c.crosschainKeeper.HandleEVMDeposit(tmpCtx, cctx) - - // TODO (https://github.com/zeta-chain/node/issues/2278): further processing will be in validateOutbound(...), for now keeping it here - if err != nil && !isContractReverted { - // exceptional case; internal error; should abort CCTX - cctx.SetAbort(err.Error()) - return types.CctxStatus_Aborted - } else if err != nil && isContractReverted { - // contract call reverted; should refund via a revert tx - revertMessage := err.Error() - senderChain := c.crosschainKeeper.zetaObserverKeeper.GetSupportedChainFromChainID(ctx, cctx.InboundParams.SenderChainId) - if senderChain == nil { - cctx.SetAbort(fmt.Sprintf("invalid sender chain id %d", cctx.InboundParams.SenderChainId)) - return types.CctxStatus_Aborted - } - gasLimit, err := c.crosschainKeeper.GetRevertGasLimit(ctx, *cctx) - if err != nil { - cctx.SetAbort(fmt.Sprintf("revert gas limit error: %s", err.Error())) - return types.CctxStatus_Aborted - } - if gasLimit == 0 { - // use same gas limit of outbound as a fallback -- should not be required - gasLimit = cctx.GetCurrentOutboundParam().GasLimit - } + isContractReverted, err := c.crosschainKeeper.HandleEVMDeposit(ctx, cctx) - err = cctx.AddRevertOutbound(gasLimit) - if err != nil { - cctx.SetAbort(fmt.Sprintf("revert outbound error: %s", err.Error())) - return types.CctxStatus_Aborted - } - // we create a new cached context, and we don't commit the previous one with EVM deposit - tmpCtxRevert, commitRevert := ctx.CacheContext() - err = func() error { - err := c.crosschainKeeper.PayGasAndUpdateCctx( - tmpCtxRevert, - senderChain.ChainId, - cctx, - cctx.InboundParams.Amount, - false, - ) - if err != nil { - return err - } - // Update nonce using senderchain id as this is a revert tx and would go back to the original sender - return c.crosschainKeeper.UpdateNonce(tmpCtxRevert, senderChain.ChainId, cctx) - }() - if err != nil { - cctx.SetAbort(fmt.Sprintf("deposit revert message: %s err : %s", revertMessage, err.Error())) - return types.CctxStatus_Aborted - } - commitRevert() - cctx.SetPendingRevert(revertMessage) - return types.CctxStatus_PendingRevert - } - // successful HandleEVMDeposit; - commit() - cctx.SetOutBoundMined("Remote omnichain contract call completed") - return types.CctxStatus_OutboundMined + return c.crosschainKeeper.ValidateOutboundZEVM(ctx, cctx, err, isContractReverted) } diff --git a/x/crosschain/keeper/cctx_orchestrator.go b/x/crosschain/keeper/cctx_orchestrator.go new file mode 100644 index 0000000000..9144cf6521 --- /dev/null +++ b/x/crosschain/keeper/cctx_orchestrator.go @@ -0,0 +1,93 @@ +package keeper + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/zeta-chain/zetacore/x/crosschain/types" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" +) + +func (k Keeper) ValidateOutboundZEVM(ctx sdk.Context, cctx *types.CrossChainTx, zevmError error, isContractReverted bool) (newCCTXStatus types.CctxStatus) { + _, commit := ctx.CacheContext() + if zevmError != nil && !isContractReverted { + // exceptional case; internal error; should abort CCTX + cctx.SetAbort(zevmError.Error()) + return types.CctxStatus_Aborted + } else if zevmError != nil && isContractReverted { + // contract call reverted; should refund via a revert tx + revertMessage := zevmError.Error() + senderChain := k.zetaObserverKeeper.GetSupportedChainFromChainID(ctx, cctx.InboundParams.SenderChainId) + if senderChain == nil { + cctx.SetAbort(fmt.Sprintf("invalid sender chain id %d", cctx.InboundParams.SenderChainId)) + return types.CctxStatus_Aborted + } + gasLimit, err := k.GetRevertGasLimit(ctx, *cctx) + if err != nil { + cctx.SetAbort(fmt.Sprintf("revert gas limit error: %s", err.Error())) + return types.CctxStatus_Aborted + } + if gasLimit == 0 { + // use same gas limit of outbound as a fallback -- should not be required + gasLimit = cctx.GetCurrentOutboundParam().GasLimit + } + + err = cctx.AddRevertOutbound(gasLimit) + if err != nil { + cctx.SetAbort(fmt.Sprintf("revert outbound error: %s", err.Error())) + return types.CctxStatus_Aborted + } + // we create a new cached context, and we don't commit the previous one with EVM deposit + tmpCtxRevert, commitRevert := ctx.CacheContext() + err = func() error { + err := k.PayGasAndUpdateCctx( + tmpCtxRevert, + senderChain.ChainId, + cctx, + cctx.InboundParams.Amount, + false, + ) + if err != nil { + return err + } + // Update nonce using senderchain id as this is a revert tx and would go back to the original sender + return k.UpdateNonce(tmpCtxRevert, senderChain.ChainId, cctx) + }() + if err != nil { + cctx.SetAbort(fmt.Sprintf("deposit revert message: %s err : %s", revertMessage, err.Error())) + return types.CctxStatus_Aborted + } + commitRevert() + cctx.SetPendingRevert(revertMessage) + return types.CctxStatus_PendingRevert + } + // successful HandleEVMDeposit; + commit() + cctx.SetOutBoundMined("Remote omnichain contract call completed") + return types.CctxStatus_OutboundMined +} + +func (k Keeper) ValidateOutboundObservers(ctx sdk.Context, cctx *types.CrossChainTx, ballotStatus observertypes.BallotStatus, valueReceived string) error { + tmpCtx, commit := ctx.CacheContext() + err := func() error { + switch ballotStatus { + case observertypes.BallotStatus_BallotFinalized_SuccessObservation: + k.ProcessSuccessfulOutbound(tmpCtx, cctx, valueReceived) + case observertypes.BallotStatus_BallotFinalized_FailureObservation: + err := k.ProcessFailedOutbound(tmpCtx, cctx, valueReceived) + if err != nil { + return err + } + } + return nil + }() + if err != nil { + return err + } + err = cctx.Validate() + if err != nil { + return err + } + commit() + return nil +} diff --git a/x/crosschain/keeper/msg_server_vote_outbound_tx.go b/x/crosschain/keeper/msg_server_vote_outbound_tx.go index 4c5d3b0d13..3445d32cfb 100644 --- a/x/crosschain/keeper/msg_server_vote_outbound_tx.go +++ b/x/crosschain/keeper/msg_server_vote_outbound_tx.go @@ -95,7 +95,7 @@ func (k msgServer) VoteOutbound( // Fund the gas stability pool with the remaining funds k.FundStabilityPool(ctx, &cctx) - err = k.ProcessOutbound(ctx, &cctx, ballot.BallotStatus, msg.ValueReceived.String()) + err = k.ValidateOutboundObservers(ctx, &cctx, ballot.BallotStatus, msg.ValueReceived.String()) if err != nil { k.SaveFailedOutbound(ctx, &cctx, err.Error(), ballotIndex) return &types.MsgVoteOutboundResponse{}, nil From 28aa31e50953d3bded79a6ecd54f7023c33b40b1 Mon Sep 17 00:00:00 2001 From: skosito Date: Tue, 4 Jun 2024 19:30:59 +0200 Subject: [PATCH 02/38] refactor --- x/crosschain/keeper/cctx_orchestrator.go | 93 +++++++++++++----------- 1 file changed, 51 insertions(+), 42 deletions(-) diff --git a/x/crosschain/keeper/cctx_orchestrator.go b/x/crosschain/keeper/cctx_orchestrator.go index 9144cf6521..b57349c5a5 100644 --- a/x/crosschain/keeper/cctx_orchestrator.go +++ b/x/crosschain/keeper/cctx_orchestrator.go @@ -9,64 +9,73 @@ import ( ) func (k Keeper) ValidateOutboundZEVM(ctx sdk.Context, cctx *types.CrossChainTx, zevmError error, isContractReverted bool) (newCCTXStatus types.CctxStatus) { - _, commit := ctx.CacheContext() if zevmError != nil && !isContractReverted { // exceptional case; internal error; should abort CCTX cctx.SetAbort(zevmError.Error()) return types.CctxStatus_Aborted - } else if zevmError != nil && isContractReverted { + } + + if zevmError != nil && isContractReverted { // contract call reverted; should refund via a revert tx - revertMessage := zevmError.Error() - senderChain := k.zetaObserverKeeper.GetSupportedChainFromChainID(ctx, cctx.InboundParams.SenderChainId) - if senderChain == nil { - cctx.SetAbort(fmt.Sprintf("invalid sender chain id %d", cctx.InboundParams.SenderChainId)) - return types.CctxStatus_Aborted - } - gasLimit, err := k.GetRevertGasLimit(ctx, *cctx) + err := k.tryRevertOutbound(ctx, cctx, zevmError) if err != nil { - cctx.SetAbort(fmt.Sprintf("revert gas limit error: %s", err.Error())) + cctx.SetAbort(err.Error()) return types.CctxStatus_Aborted } - if gasLimit == 0 { - // use same gas limit of outbound as a fallback -- should not be required - gasLimit = cctx.GetCurrentOutboundParam().GasLimit - } - err = cctx.AddRevertOutbound(gasLimit) - if err != nil { - cctx.SetAbort(fmt.Sprintf("revert outbound error: %s", err.Error())) - return types.CctxStatus_Aborted - } - // we create a new cached context, and we don't commit the previous one with EVM deposit - tmpCtxRevert, commitRevert := ctx.CacheContext() - err = func() error { - err := k.PayGasAndUpdateCctx( - tmpCtxRevert, - senderChain.ChainId, - cctx, - cctx.InboundParams.Amount, - false, - ) - if err != nil { - return err - } - // Update nonce using senderchain id as this is a revert tx and would go back to the original sender - return k.UpdateNonce(tmpCtxRevert, senderChain.ChainId, cctx) - }() - if err != nil { - cctx.SetAbort(fmt.Sprintf("deposit revert message: %s err : %s", revertMessage, err.Error())) - return types.CctxStatus_Aborted - } - commitRevert() - cctx.SetPendingRevert(revertMessage) return types.CctxStatus_PendingRevert } - // successful HandleEVMDeposit; + _, commit := ctx.CacheContext() commit() cctx.SetOutBoundMined("Remote omnichain contract call completed") return types.CctxStatus_OutboundMined } +func (k Keeper) tryRevertOutbound(ctx sdk.Context, cctx *types.CrossChainTx, zevmError error) error { + senderChain := k.zetaObserverKeeper.GetSupportedChainFromChainID(ctx, cctx.InboundParams.SenderChainId) + if senderChain == nil { + return fmt.Errorf("invalid sender chain id %d", cctx.InboundParams.SenderChainId) + } + // use same gas limit of outbound as a fallback -- should not be required + gasLimit, err := k.GetRevertGasLimit(ctx, *cctx) + if err != nil { + return fmt.Errorf("revert gas limit error: %s", err.Error()) + } + if gasLimit == 0 { + gasLimit = cctx.GetCurrentOutboundParam().GasLimit + } + + revertMessage := zevmError.Error() + err = cctx.AddRevertOutbound(gasLimit) + if err != nil { + return fmt.Errorf("revert outbound error: %s", err.Error()) + } + + // we create a new cached context, and we don't commit the previous one with EVM deposit + tmpCtxRevert, commitRevert := ctx.CacheContext() + err = func() error { + err := k.PayGasAndUpdateCctx( + tmpCtxRevert, + senderChain.ChainId, + cctx, + cctx.InboundParams.Amount, + false, + ) + if err != nil { + return err + } + + // update nonce using senderchain id as this is a revert tx and would go back to the original sender + return k.UpdateNonce(tmpCtxRevert, senderChain.ChainId, cctx) + }() + if err != nil { + return fmt.Errorf("deposit revert message: %s err : %s", revertMessage, err.Error()) + } + commitRevert() + cctx.SetPendingRevert(revertMessage) + return nil +} + func (k Keeper) ValidateOutboundObservers(ctx sdk.Context, cctx *types.CrossChainTx, ballotStatus observertypes.BallotStatus, valueReceived string) error { tmpCtx, commit := ctx.CacheContext() err := func() error { From e6609e557d17c646ad6bbc6fde9cb63c6b819c25 Mon Sep 17 00:00:00 2001 From: skosito Date: Wed, 5 Jun 2024 12:49:59 +0200 Subject: [PATCH 03/38] renaming and moving code around --- x/crosschain/keeper/cctx_orchestrator.go | 208 ++++++++++++++- x/crosschain/keeper/process_outbound.go | 244 ------------------ ...ound_test.go => validate_outbound_test.go} | 91 ++++--- 3 files changed, 255 insertions(+), 288 deletions(-) delete mode 100644 x/crosschain/keeper/process_outbound.go rename x/crosschain/keeper/{process_outbound_test.go => validate_outbound_test.go} (79%) diff --git a/x/crosschain/keeper/cctx_orchestrator.go b/x/crosschain/keeper/cctx_orchestrator.go index b57349c5a5..c4ef1fedd6 100644 --- a/x/crosschain/keeper/cctx_orchestrator.go +++ b/x/crosschain/keeper/cctx_orchestrator.go @@ -1,10 +1,18 @@ package keeper import ( + "encoding/base64" "fmt" + cosmoserrors "cosmossdk.io/errors" + tmbytes "github.com/cometbft/cometbft/libs/bytes" + tmtypes "github.com/cometbft/cometbft/types" sdk "github.com/cosmos/cosmos-sdk/types" + ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/zeta-chain/zetacore/pkg/chains" + "github.com/zeta-chain/zetacore/pkg/coin" "github.com/zeta-chain/zetacore/x/crosschain/types" + fungiblekeeper "github.com/zeta-chain/zetacore/x/fungible/keeper" observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) @@ -76,14 +84,16 @@ func (k Keeper) tryRevertOutbound(ctx sdk.Context, cctx *types.CrossChainTx, zev return nil } +// ValidateOutboundObservers processes the finalization of an outbound transaction based on the ballot status +// The state is committed only if the individual steps are successful func (k Keeper) ValidateOutboundObservers(ctx sdk.Context, cctx *types.CrossChainTx, ballotStatus observertypes.BallotStatus, valueReceived string) error { tmpCtx, commit := ctx.CacheContext() err := func() error { switch ballotStatus { case observertypes.BallotStatus_BallotFinalized_SuccessObservation: - k.ProcessSuccessfulOutbound(tmpCtx, cctx, valueReceived) + k.processSuccessfulOutbound(tmpCtx, cctx, valueReceived) case observertypes.BallotStatus_BallotFinalized_FailureObservation: - err := k.ProcessFailedOutbound(tmpCtx, cctx, valueReceived) + err := k.processFailedOutbound(tmpCtx, cctx, valueReceived) if err != nil { return err } @@ -100,3 +110,197 @@ func (k Keeper) ValidateOutboundObservers(ctx sdk.Context, cctx *types.CrossChai commit() return nil } + +// ProcessFailedOutbound processes a failed outbound transaction. It does the following things in one function: +// +// 1. For Admin Tx or a withdrawal from Zeta chain, it aborts the CCTX +// +// 2. For other CCTX +// - If the CCTX is in PendingOutbound, it creates a revert tx and sets the finalization status of the current outbound tx to executed +// - If the CCTX is in PendingRevert, it sets the Status to Aborted +// +// 3. Emit an event for the failed outbound transaction +// +// 4. Set the finalization status of the current outbound tx to executed. If a revert tx is is created, the finalization status is not set, it would get set when the revert is processed via a subsequent transaction +// +// This function sets CCTX status , in cases where the outbound tx is successful, but tx itself fails +// This is done because SaveSuccessfulOutbound does not set the cctx status +// For cases where the outbound tx is unsuccessful, the cctx status is automatically set to Aborted in the ProcessFailedOutbound function, so we can just return and error to trigger that +func (k Keeper) processFailedOutbound(ctx sdk.Context, cctx *types.CrossChainTx, valueReceived string) error { + oldStatus := cctx.CctxStatus.Status + // The following logic is used to handler the mentioned conditions separately. The reason being + // All admin tx is created using a policy message , there is no associated inbound tx , therefore we do not need any revert logic + // For transactions which originated from ZEVM , we can process the outbound in the same block as there is no TSS signing required for the revert + // For all other transactions we need to create a revert tx and set the status to pending revert + + if cctx.InboundParams.CoinType == coin.CoinType_Cmd { + // if the cctx is of coin type cmd or the sender chain is zeta chain, then we do not revert, the cctx is aborted + cctx.GetCurrentOutboundParam().TxFinalizationStatus = types.TxFinalizationStatus_Executed + cctx.SetAbort("Outbound failed") + } else if chains.IsZetaChain(cctx.InboundParams.SenderChainId) { + switch cctx.InboundParams.CoinType { + // Try revert if the coin-type is ZETA + case coin.CoinType_Zeta: + { + err := k.processFailedOutboundForZEVM(ctx, cctx) + if err != nil { + return cosmoserrors.Wrap(err, "ProcessFailedOutboundForZEVMTx") + } + } + // For all other coin-types, we do not revert, the cctx is aborted + default: + { + cctx.GetCurrentOutboundParam().TxFinalizationStatus = types.TxFinalizationStatus_Executed + cctx.SetAbort("Outbound failed") + } + } + } else { + err := k.processFailedOutboundForExternalChainTx(ctx, cctx, oldStatus) + if err != nil { + return cosmoserrors.Wrap(err, "ProcessFailedOutBoundForExternalChainTx") + } + } + newStatus := cctx.CctxStatus.Status.String() + EmitOutboundFailure(ctx, valueReceived, oldStatus.String(), newStatus, cctx.Index) + return nil +} + +// processFailedOutboundForExternalChainTx processes the failed outbound transaction for external chain tx +func (k Keeper) processFailedOutboundForExternalChainTx( + ctx sdk.Context, + cctx *types.CrossChainTx, + oldStatus types.CctxStatus, +) error { + switch oldStatus { + case types.CctxStatus_PendingOutbound: + + gasLimit, err := k.GetRevertGasLimit(ctx, *cctx) + if err != nil { + return cosmoserrors.Wrap(err, "GetRevertGasLimit") + } + if gasLimit == 0 { + // use same gas limit of outbound as a fallback -- should not happen + gasLimit = cctx.OutboundParams[0].GasLimit + } + + // create new OutboundParams for the revert + err = cctx.AddRevertOutbound(gasLimit) + if err != nil { + return cosmoserrors.Wrap(err, "AddRevertOutbound") + } + + err = k.PayGasAndUpdateCctx( + ctx, + cctx.InboundParams.SenderChainId, + cctx, + cctx.OutboundParams[0].Amount, + false, + ) + if err != nil { + return err + } + err = k.UpdateNonce(ctx, cctx.InboundParams.SenderChainId, cctx) + if err != nil { + return err + } + // Not setting the finalization status here, the required changes have been made while creating the revert tx + cctx.SetPendingRevert("Outbound failed, start revert") + case types.CctxStatus_PendingRevert: + cctx.GetCurrentOutboundParam().TxFinalizationStatus = types.TxFinalizationStatus_Executed + cctx.SetAbort("Outbound failed: revert failed; abort TX") + } + return nil +} + +// processSuccessfulOutbound processes a successful outbound transaction. It does the following things in one function: +// +// 1. Change the status of the CCTX from +// - PendingRevert to Reverted +// - PendingOutbound to OutboundMined +// +// 2. Set the finalization status of the current outbound tx to executed +// +// 3. Emit an event for the successful outbound transaction +// +// This function sets CCTX status , in cases where the outbound tx is successful, but tx itself fails +// This is done because SaveSuccessfulOutbound does not set the cctx status +// For cases where the outbound tx is unsuccessful, the cctx status is automatically set to Aborted in the ProcessFailedOutbound function, so we can just return and error to trigger that +func (k Keeper) processSuccessfulOutbound(ctx sdk.Context, cctx *types.CrossChainTx, valueReceived string) { + oldStatus := cctx.CctxStatus.Status + switch oldStatus { + case types.CctxStatus_PendingRevert: + cctx.SetReverted("Outbound succeeded, revert executed") + case types.CctxStatus_PendingOutbound: + cctx.SetOutBoundMined("Outbound succeeded, mined") + default: + return + } + cctx.GetCurrentOutboundParam().TxFinalizationStatus = types.TxFinalizationStatus_Executed + newStatus := cctx.CctxStatus.Status.String() + EmitOutboundSuccess(ctx, valueReceived, oldStatus.String(), newStatus, cctx.Index) +} + +// processFailedOutboundForZEVM processes the failed outbound transaction for ZEVM +func (k Keeper) processFailedOutboundForZEVM(ctx sdk.Context, cctx *types.CrossChainTx) error { + indexBytes, err := cctx.GetCCTXIndexBytes() + if err != nil { + // Return err to save the failed outbound ad set to aborted + return fmt.Errorf("failed reverting GetCCTXIndexBytes: %s", err.Error()) + } + // Finalize the older outbound tx + cctx.GetCurrentOutboundParam().TxFinalizationStatus = types.TxFinalizationStatus_Executed + + // create new OutboundParams for the revert. We use the fixed gas limit for revert when calling zEVM + err = cctx.AddRevertOutbound(fungiblekeeper.ZEVMGasLimitDepositAndCall.Uint64()) + if err != nil { + // Return err to save the failed outbound ad set to aborted + return fmt.Errorf("failed AddRevertOutbound: %s", err.Error()) + } + + // Trying to revert the transaction this would get set to a finalized status in the same block as this does not need a TSS singing + cctx.SetPendingRevert("Outbound failed, trying revert") + data, err := base64.StdEncoding.DecodeString(cctx.RelayedMessage) + if err != nil { + return fmt.Errorf("failed decoding relayed message: %s", err.Error()) + } + + // Fetch the original sender and receiver from the CCTX , since this is a revert the sender with be the receiver in the new tx + originalSender := ethcommon.HexToAddress(cctx.InboundParams.Sender) + // This transaction will always have two outbounds, the following logic is just an added precaution. + // The contract call or token deposit would go the original sender. + originalReceiver := ethcommon.HexToAddress(cctx.GetCurrentOutboundParam().Receiver) + orginalReceiverChainID := cctx.GetCurrentOutboundParam().ReceiverChainId + if len(cctx.OutboundParams) == 2 { + // If there are 2 outbound tx, then the original receiver is the receiver in the first outbound tx + originalReceiver = ethcommon.HexToAddress(cctx.OutboundParams[0].Receiver) + orginalReceiverChainID = cctx.OutboundParams[0].ReceiverChainId + } + + // Call evm to revert the transaction + // If revert fails, we set it to abort directly there is no way to refund here as the revert failed + _, err = k.fungibleKeeper.ZETARevertAndCallContract( + ctx, + originalSender, + originalReceiver, + cctx.InboundParams.SenderChainId, + orginalReceiverChainID, + cctx.GetCurrentOutboundParam().Amount.BigInt(), + data, + indexBytes, + ) + if err != nil { + return fmt.Errorf("failed ZETARevertAndCallContract: %s", err.Error()) + } + + cctx.SetReverted("Outbound failed, revert executed") + if len(ctx.TxBytes()) > 0 { + // add event for tendermint transaction hash format + hash := tmbytes.HexBytes(tmtypes.Tx(ctx.TxBytes()).Hash()) + ethTxHash := ethcommon.BytesToHash(hash) + cctx.GetCurrentOutboundParam().Hash = ethTxHash.String() + // #nosec G701 always positive + cctx.GetCurrentOutboundParam().ObservedExternalHeight = uint64(ctx.BlockHeight()) + } + cctx.GetCurrentOutboundParam().TxFinalizationStatus = types.TxFinalizationStatus_Executed + return nil +} diff --git a/x/crosschain/keeper/process_outbound.go b/x/crosschain/keeper/process_outbound.go deleted file mode 100644 index 51c0f43afe..0000000000 --- a/x/crosschain/keeper/process_outbound.go +++ /dev/null @@ -1,244 +0,0 @@ -package keeper - -import ( - "encoding/base64" - "fmt" - - cosmoserrors "cosmossdk.io/errors" - tmbytes "github.com/cometbft/cometbft/libs/bytes" - tmtypes "github.com/cometbft/cometbft/types" - sdk "github.com/cosmos/cosmos-sdk/types" - ethcommon "github.com/ethereum/go-ethereum/common" - - "github.com/zeta-chain/zetacore/pkg/chains" - "github.com/zeta-chain/zetacore/pkg/coin" - "github.com/zeta-chain/zetacore/x/crosschain/types" - fungiblekeeper "github.com/zeta-chain/zetacore/x/fungible/keeper" - observertypes "github.com/zeta-chain/zetacore/x/observer/types" -) - -// ProcessOutbound processes the finalization of an outbound transaction based on the ballot status -// The state is committed only if the individual steps are successful -func (k Keeper) ProcessOutbound( - ctx sdk.Context, - cctx *types.CrossChainTx, - ballotStatus observertypes.BallotStatus, - valueReceived string, -) error { - tmpCtx, commit := ctx.CacheContext() - err := func() error { - switch ballotStatus { - case observertypes.BallotStatus_BallotFinalized_SuccessObservation: - k.ProcessSuccessfulOutbound(tmpCtx, cctx, valueReceived) - case observertypes.BallotStatus_BallotFinalized_FailureObservation: - err := k.ProcessFailedOutbound(tmpCtx, cctx, valueReceived) - if err != nil { - return err - } - } - return nil - }() - if err != nil { - return err - } - err = cctx.Validate() - if err != nil { - return err - } - commit() - return nil -} - -// ProcessSuccessfulOutbound processes a successful outbound transaction. It does the following things in one function: -// -// 1. Change the status of the CCTX from -// - PendingRevert to Reverted -// - PendingOutbound to OutboundMined -// -// 2. Set the finalization status of the current outbound tx to executed -// -// 3. Emit an event for the successful outbound transaction -// -// This function sets CCTX status , in cases where the outbound tx is successful, but tx itself fails -// This is done because SaveSuccessfulOutbound does not set the cctx status -// For cases where the outbound tx is unsuccessful, the cctx status is automatically set to Aborted in the ProcessFailedOutbound function, so we can just return and error to trigger that -func (k Keeper) ProcessSuccessfulOutbound(ctx sdk.Context, cctx *types.CrossChainTx, valueReceived string) { - oldStatus := cctx.CctxStatus.Status - switch oldStatus { - case types.CctxStatus_PendingRevert: - cctx.SetReverted("Outbound succeeded, revert executed") - case types.CctxStatus_PendingOutbound: - cctx.SetOutBoundMined("Outbound succeeded, mined") - default: - return - } - cctx.GetCurrentOutboundParam().TxFinalizationStatus = types.TxFinalizationStatus_Executed - newStatus := cctx.CctxStatus.Status.String() - EmitOutboundSuccess(ctx, valueReceived, oldStatus.String(), newStatus, cctx.Index) -} - -// ProcessFailedOutbound processes a failed outbound transaction. It does the following things in one function: -// -// 1. For Admin Tx or a withdrawal from Zeta chain, it aborts the CCTX -// -// 2. For other CCTX -// - If the CCTX is in PendingOutbound, it creates a revert tx and sets the finalization status of the current outbound tx to executed -// - If the CCTX is in PendingRevert, it sets the Status to Aborted -// -// 3. Emit an event for the failed outbound transaction -// -// 4. Set the finalization status of the current outbound tx to executed. If a revert tx is is created, the finalization status is not set, it would get set when the revert is processed via a subsequent transaction -// -// This function sets CCTX status , in cases where the outbound tx is successful, but tx itself fails -// This is done because SaveSuccessfulOutbound does not set the cctx status -// For cases where the outbound tx is unsuccessful, the cctx status is automatically set to Aborted in the ProcessFailedOutbound function, so we can just return and error to trigger that -func (k Keeper) ProcessFailedOutbound(ctx sdk.Context, cctx *types.CrossChainTx, valueReceived string) error { - oldStatus := cctx.CctxStatus.Status - // The following logic is used to handler the mentioned conditions separately. The reason being - // All admin tx is created using a policy message , there is no associated inbound tx , therefore we do not need any revert logic - // For transactions which originated from ZEVM , we can process the outbound in the same block as there is no TSS signing required for the revert - // For all other transactions we need to create a revert tx and set the status to pending revert - - if cctx.InboundParams.CoinType == coin.CoinType_Cmd { - // if the cctx is of coin type cmd or the sender chain is zeta chain, then we do not revert, the cctx is aborted - cctx.GetCurrentOutboundParam().TxFinalizationStatus = types.TxFinalizationStatus_Executed - cctx.SetAbort("Outbound failed") - } else if chains.IsZetaChain(cctx.InboundParams.SenderChainId) { - switch cctx.InboundParams.CoinType { - // Try revert if the coin-type is ZETA - case coin.CoinType_Zeta: - { - err := k.processFailedOutboundForZEVM(ctx, cctx) - if err != nil { - return cosmoserrors.Wrap(err, "ProcessFailedOutboundForZEVMTx") - } - } - // For all other coin-types, we do not revert, the cctx is aborted - default: - { - cctx.GetCurrentOutboundParam().TxFinalizationStatus = types.TxFinalizationStatus_Executed - cctx.SetAbort("Outbound failed") - } - } - } else { - err := k.processFailedOutboundForExternalChainTx(ctx, cctx, oldStatus) - if err != nil { - return cosmoserrors.Wrap(err, "ProcessFailedOutBoundForExternalChainTx") - } - } - newStatus := cctx.CctxStatus.Status.String() - EmitOutboundFailure(ctx, valueReceived, oldStatus.String(), newStatus, cctx.Index) - return nil -} - -// processFailedOutboundForExternalChainTx processes the failed outbound transaction for external chain tx -func (k Keeper) processFailedOutboundForExternalChainTx( - ctx sdk.Context, - cctx *types.CrossChainTx, - oldStatus types.CctxStatus, -) error { - switch oldStatus { - case types.CctxStatus_PendingOutbound: - - gasLimit, err := k.GetRevertGasLimit(ctx, *cctx) - if err != nil { - return cosmoserrors.Wrap(err, "GetRevertGasLimit") - } - if gasLimit == 0 { - // use same gas limit of outbound as a fallback -- should not happen - gasLimit = cctx.OutboundParams[0].GasLimit - } - - // create new OutboundParams for the revert - err = cctx.AddRevertOutbound(gasLimit) - if err != nil { - return cosmoserrors.Wrap(err, "AddRevertOutbound") - } - - err = k.PayGasAndUpdateCctx( - ctx, - cctx.InboundParams.SenderChainId, - cctx, - cctx.OutboundParams[0].Amount, - false, - ) - if err != nil { - return err - } - err = k.UpdateNonce(ctx, cctx.InboundParams.SenderChainId, cctx) - if err != nil { - return err - } - // Not setting the finalization status here, the required changes have been made while creating the revert tx - cctx.SetPendingRevert("Outbound failed, start revert") - case types.CctxStatus_PendingRevert: - cctx.GetCurrentOutboundParam().TxFinalizationStatus = types.TxFinalizationStatus_Executed - cctx.SetAbort("Outbound failed: revert failed; abort TX") - } - return nil -} - -// processFailedOutboundForZEVM processes the failed outbound transaction for ZEVM -func (k Keeper) processFailedOutboundForZEVM(ctx sdk.Context, cctx *types.CrossChainTx) error { - indexBytes, err := cctx.GetCCTXIndexBytes() - if err != nil { - // Return err to save the failed outbound ad set to aborted - return fmt.Errorf("failed reverting GetCCTXIndexBytes: %s", err.Error()) - } - // Finalize the older outbound tx - cctx.GetCurrentOutboundParam().TxFinalizationStatus = types.TxFinalizationStatus_Executed - - // create new OutboundParams for the revert. We use the fixed gas limit for revert when calling zEVM - err = cctx.AddRevertOutbound(fungiblekeeper.ZEVMGasLimitDepositAndCall.Uint64()) - if err != nil { - // Return err to save the failed outbound ad set to aborted - return fmt.Errorf("failed AddRevertOutbound: %s", err.Error()) - } - - // Trying to revert the transaction this would get set to a finalized status in the same block as this does not need a TSS singing - cctx.SetPendingRevert("Outbound failed, trying revert") - data, err := base64.StdEncoding.DecodeString(cctx.RelayedMessage) - if err != nil { - return fmt.Errorf("failed decoding relayed message: %s", err.Error()) - } - - // Fetch the original sender and receiver from the CCTX , since this is a revert the sender with be the receiver in the new tx - originalSender := ethcommon.HexToAddress(cctx.InboundParams.Sender) - // This transaction will always have two outbounds, the following logic is just an added precaution. - // The contract call or token deposit would go the original sender. - originalReceiver := ethcommon.HexToAddress(cctx.GetCurrentOutboundParam().Receiver) - orginalReceiverChainID := cctx.GetCurrentOutboundParam().ReceiverChainId - if len(cctx.OutboundParams) == 2 { - // If there are 2 outbound tx, then the original receiver is the receiver in the first outbound tx - originalReceiver = ethcommon.HexToAddress(cctx.OutboundParams[0].Receiver) - orginalReceiverChainID = cctx.OutboundParams[0].ReceiverChainId - } - - // Call evm to revert the transaction - // If revert fails, we set it to abort directly there is no way to refund here as the revert failed - _, err = k.fungibleKeeper.ZETARevertAndCallContract( - ctx, - originalSender, - originalReceiver, - cctx.InboundParams.SenderChainId, - orginalReceiverChainID, - cctx.GetCurrentOutboundParam().Amount.BigInt(), - data, - indexBytes, - ) - if err != nil { - return fmt.Errorf("failed ZETARevertAndCallContract: %s", err.Error()) - } - - cctx.SetReverted("Outbound failed, revert executed") - if len(ctx.TxBytes()) > 0 { - // add event for tendermint transaction hash format - hash := tmbytes.HexBytes(tmtypes.Tx(ctx.TxBytes()).Hash()) - ethTxHash := ethcommon.BytesToHash(hash) - cctx.GetCurrentOutboundParam().Hash = ethTxHash.String() - // #nosec G701 always positive - cctx.GetCurrentOutboundParam().ObservedExternalHeight = uint64(ctx.BlockHeight()) - } - cctx.GetCurrentOutboundParam().TxFinalizationStatus = types.TxFinalizationStatus_Executed - return nil -} diff --git a/x/crosschain/keeper/process_outbound_test.go b/x/crosschain/keeper/validate_outbound_test.go similarity index 79% rename from x/crosschain/keeper/process_outbound_test.go rename to x/crosschain/keeper/validate_outbound_test.go index ee734341df..9db1daaa7b 100644 --- a/x/crosschain/keeper/process_outbound_test.go +++ b/x/crosschain/keeper/validate_outbound_test.go @@ -21,28 +21,31 @@ import ( observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) -func TestKeeper_ProcessSuccessfulOutbound(t *testing.T) { +func TestKeeper_ValidateSuccessfulOutbound(t *testing.T) { k, ctx, _, _ := keepertest.CrosschainKeeper(t) cctx := sample.CrossChainTx(t, "test") // transition to reverted if pending revert cctx.CctxStatus.Status = types.CctxStatus_PendingRevert - k.ProcessSuccessfulOutbound(ctx, cctx, sample.String()) + k.ValidateOutboundObservers(ctx, cctx, observertypes.BallotStatus_BallotFinalized_SuccessObservation, sample.String()) require.Equal(t, cctx.CctxStatus.Status, types.CctxStatus_Reverted) // transition to outbound mined if pending outbound cctx.CctxStatus.Status = types.CctxStatus_PendingOutbound - k.ProcessSuccessfulOutbound(ctx, cctx, sample.String()) + k.ValidateOutboundObservers(ctx, cctx, observertypes.BallotStatus_BallotFinalized_SuccessObservation, sample.String()) require.Equal(t, cctx.CctxStatus.Status, types.CctxStatus_OutboundMined) // do nothing if it's in any other state - k.ProcessSuccessfulOutbound(ctx, cctx, sample.String()) + k.ValidateOutboundObservers(ctx, cctx, observertypes.BallotStatus_BallotFinalized_SuccessObservation, sample.String()) require.Equal(t, cctx.CctxStatus.Status, types.CctxStatus_OutboundMined) } -func TestKeeper_ProcessFailedOutbound(t *testing.T) { - t.Run("successfully process failed outbound set to aborted for type cmd", func(t *testing.T) { +func TestKeeper_ValidateFailedOutbound(t *testing.T) { + t.Run("successfully validates failed outbound set to aborted for type cmd", func(t *testing.T) { k, ctx, _, _ := keepertest.CrosschainKeeper(t) cctx := sample.CrossChainTx(t, "test") cctx.InboundParams.CoinType = coin.CoinType_Cmd - err := k.ProcessFailedOutbound(ctx, cctx, sample.String()) + cctx.InboundParams.SenderChainId = chains.Ethereum.ChainId + cctx.OutboundParams[0].ReceiverChainId = chains.Ethereum.ChainId + cctx.OutboundParams[1].ReceiverChainId = chains.Ethereum.ChainId + err := k.ValidateOutboundObservers(ctx, cctx, observertypes.BallotStatus_BallotFinalized_FailureObservation, sample.String()) require.NoError(t, err) require.Equal(t, cctx.CctxStatus.Status, types.CctxStatus_Aborted) require.Equal(t, cctx.GetCurrentOutboundParam().TxFinalizationStatus, types.TxFinalizationStatus_Executed) @@ -53,7 +56,9 @@ func TestKeeper_ProcessFailedOutbound(t *testing.T) { cctx := sample.CrossChainTx(t, "test") cctx.InboundParams.CoinType = coin.CoinType_ERC20 cctx.InboundParams.SenderChainId = chains.ZetaChainMainnet.ChainId - err := k.ProcessFailedOutbound(ctx, cctx, sample.String()) + cctx.OutboundParams[0].ReceiverChainId = chains.Ethereum.ChainId + cctx.OutboundParams[1].ReceiverChainId = chains.Ethereum.ChainId + err := k.ValidateOutboundObservers(ctx, cctx, observertypes.BallotStatus_BallotFinalized_FailureObservation, sample.String()) require.NoError(t, err) require.Equal(t, cctx.CctxStatus.Status, types.CctxStatus_Aborted) require.Equal(t, cctx.GetCurrentOutboundParam().TxFinalizationStatus, types.TxFinalizationStatus_Executed) @@ -64,13 +69,15 @@ func TestKeeper_ProcessFailedOutbound(t *testing.T) { cctx := sample.CrossChainTx(t, "test") cctx.InboundParams.CoinType = coin.CoinType_Gas cctx.InboundParams.SenderChainId = chains.ZetaChainMainnet.ChainId - err := k.ProcessFailedOutbound(ctx, cctx, sample.String()) + cctx.OutboundParams[0].ReceiverChainId = chains.Ethereum.ChainId + cctx.OutboundParams[1].ReceiverChainId = chains.Ethereum.ChainId + err := k.ValidateOutboundObservers(ctx, cctx, observertypes.BallotStatus_BallotFinalized_FailureObservation, sample.String()) require.NoError(t, err) require.Equal(t, cctx.CctxStatus.Status, types.CctxStatus_Aborted) require.Equal(t, cctx.GetCurrentOutboundParam().TxFinalizationStatus, types.TxFinalizationStatus_Executed) }) - t.Run("successfully process failed outbound if original sender is a address", func(t *testing.T) { + t.Run("successfully validate failed outbound if original sender is a address", func(t *testing.T) { k, ctx, sdkk, _ := keepertest.CrosschainKeeper(t) receiver := sample.EthAddress() cctx := GetERC20Cctx(t, receiver, chains.Goerli, "", big.NewInt(42)) @@ -82,32 +89,32 @@ func TestKeeper_ProcessFailedOutbound(t *testing.T) { ) require.NoError(t, err) cctx.InboundParams.SenderChainId = chains.ZetaChainMainnet.ChainId - err = k.ProcessFailedOutbound(ctx, cctx, sample.String()) + err = k.ValidateOutboundObservers(ctx, cctx, observertypes.BallotStatus_BallotFinalized_FailureObservation, sample.String()) require.NoError(t, err) require.Equal(t, types.CctxStatus_Reverted, cctx.CctxStatus.Status) require.Equal(t, cctx.GetCurrentOutboundParam().TxFinalizationStatus, types.TxFinalizationStatus_Executed) }) - t.Run("unable to process failed outbound if GetCCTXIndexBytes fails", func(t *testing.T) { + t.Run("unable to validate failed outbound if GetCCTXIndexBytes fails", func(t *testing.T) { k, ctx, _, _ := keepertest.CrosschainKeeper(t) receiver := sample.EthAddress() cctx := GetERC20Cctx(t, receiver, chains.Goerli, "", big.NewInt(42)) cctx.InboundParams.CoinType = coin.CoinType_Zeta cctx.Index = "" cctx.InboundParams.SenderChainId = chains.ZetaChainMainnet.ChainId - err := k.ProcessFailedOutbound(ctx, cctx, sample.String()) + err := k.ValidateOutboundObservers(ctx, cctx, observertypes.BallotStatus_BallotFinalized_FailureObservation, sample.String()) require.ErrorContains(t, err, "failed reverting GetCCTXIndexBytes") }) - t.Run("unable to process failed outbound if Adding Revert fails", func(t *testing.T) { + t.Run("unable to validate failed outbound if Adding Revert fails", func(t *testing.T) { k, ctx, _, _ := keepertest.CrosschainKeeper(t) cctx := sample.CrossChainTx(t, "test") cctx.InboundParams.SenderChainId = chains.ZetaChainMainnet.ChainId cctx.InboundParams.CoinType = coin.CoinType_Zeta - err := k.ProcessFailedOutbound(ctx, cctx, sample.String()) + err := k.ValidateOutboundObservers(ctx, cctx, observertypes.BallotStatus_BallotFinalized_FailureObservation, sample.String()) require.ErrorContains(t, err, "failed AddRevertOutbound") }) - t.Run("unable to process failed outbound if ZETARevertAndCallContract fails", func(t *testing.T) { + t.Run("unable to validate failed outbound if ZETARevertAndCallContract fails", func(t *testing.T) { k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ UseFungibleMock: true, }) @@ -125,7 +132,7 @@ func TestKeeper_ProcessFailedOutbound(t *testing.T) { cctx.GetCurrentOutboundParam().Amount.BigInt(), mock.Anything, mock.Anything).Return(nil, errorFailedZETARevertAndCallContract).Once() - err := k.ProcessFailedOutbound(ctx, cctx, sample.String()) + err := k.ValidateOutboundObservers(ctx, cctx, observertypes.BallotStatus_BallotFinalized_FailureObservation, sample.String()) require.ErrorContains(t, err, "failed ZETARevertAndCallContract") }) @@ -144,7 +151,7 @@ func TestKeeper_ProcessFailedOutbound(t *testing.T) { cctx.InboundParams.Sender = dAppContract.String() cctx.InboundParams.SenderChainId = chains.ZetaChainMainnet.ChainId - err = k.ProcessFailedOutbound(ctx, cctx, sample.String()) + err = k.ValidateOutboundObservers(ctx, cctx, observertypes.BallotStatus_BallotFinalized_FailureObservation, sample.String()) require.NoError(t, err) require.Equal(t, types.CctxStatus_Reverted, cctx.CctxStatus.Status) require.Equal(t, cctx.GetCurrentOutboundParam().TxFinalizationStatus, types.TxFinalizationStatus_Executed) @@ -171,7 +178,7 @@ func TestKeeper_ProcessFailedOutbound(t *testing.T) { require.Equal(t, dAppContract.Bytes(), valSenderAddress) }) - t.Run("successfully process failed outbound set to pending revert", func(t *testing.T) { + t.Run("successfully validate failed outbound set to pending revert", func(t *testing.T) { k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ UseFungibleMock: true, UseObserverMock: true, @@ -196,14 +203,14 @@ func TestKeeper_ProcessFailedOutbound(t *testing.T) { cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) cctx.CctxStatus.Status = types.CctxStatus_PendingOutbound - err := k.ProcessFailedOutbound(ctx, cctx, sample.String()) + err := k.ValidateOutboundObservers(ctx, cctx, observertypes.BallotStatus_BallotFinalized_FailureObservation, sample.String()) require.NoError(t, err) require.Equal(t, cctx.CctxStatus.Status, types.CctxStatus_PendingRevert) require.Equal(t, types.TxFinalizationStatus_NotFinalized, cctx.GetCurrentOutboundParam().TxFinalizationStatus) require.Equal(t, types.TxFinalizationStatus_Executed, cctx.OutboundParams[0].TxFinalizationStatus) }) - t.Run("successfully process failed outbound set to pending revert if gas limit is 0", func(t *testing.T) { + t.Run("successfully validate failed outbound set to pending revert if gas limit is 0", func(t *testing.T) { k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ UseFungibleMock: true, UseObserverMock: true, @@ -228,14 +235,14 @@ func TestKeeper_ProcessFailedOutbound(t *testing.T) { cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) cctx.CctxStatus.Status = types.CctxStatus_PendingOutbound - err := k.ProcessFailedOutbound(ctx, cctx, sample.String()) + err := k.ValidateOutboundObservers(ctx, cctx, observertypes.BallotStatus_BallotFinalized_FailureObservation, sample.String()) require.NoError(t, err) require.Equal(t, cctx.CctxStatus.Status, types.CctxStatus_PendingRevert) require.Equal(t, types.TxFinalizationStatus_NotFinalized, cctx.GetCurrentOutboundParam().TxFinalizationStatus) require.Equal(t, types.TxFinalizationStatus_Executed, cctx.OutboundParams[0].TxFinalizationStatus) }) - t.Run("unable to process revert when update nonce fails", func(t *testing.T) { + t.Run("unable to validate revert when update nonce fails", func(t *testing.T) { k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ UseFungibleMock: true, UseObserverMock: true, @@ -261,12 +268,12 @@ func TestKeeper_ProcessFailedOutbound(t *testing.T) { cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) cctx.CctxStatus.Status = types.CctxStatus_PendingOutbound - err := k.ProcessFailedOutbound(ctx, cctx, sample.String()) + err := k.ValidateOutboundObservers(ctx, cctx, observertypes.BallotStatus_BallotFinalized_FailureObservation, sample.String()) require.ErrorIs(t, err, types.ErrCannotFindReceiverNonce) require.Equal(t, cctx.CctxStatus.Status, types.CctxStatus_PendingOutbound) }) - t.Run("unable to process revert when PayGasAndUpdateCctx fails", func(t *testing.T) { + t.Run("unable to validate revert when PayGasAndUpdateCctx fails", func(t *testing.T) { k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ UseFungibleMock: true, UseObserverMock: true, @@ -289,12 +296,12 @@ func TestKeeper_ProcessFailedOutbound(t *testing.T) { cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) cctx.CctxStatus.Status = types.CctxStatus_PendingOutbound - err := k.ProcessFailedOutbound(ctx, cctx, sample.String()) + err := k.ValidateOutboundObservers(ctx, cctx, observertypes.BallotStatus_BallotFinalized_FailureObservation, sample.String()) require.ErrorIs(t, err, observertypes.ErrSupportedChains) require.Equal(t, cctx.CctxStatus.Status, types.CctxStatus_PendingOutbound) }) - t.Run("unable to process revert when GetRevertGasLimit fails", func(t *testing.T) { + t.Run("unable to validate revert when GetRevertGasLimit fails", func(t *testing.T) { k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ UseFungibleMock: true, }) @@ -314,18 +321,18 @@ func TestKeeper_ProcessFailedOutbound(t *testing.T) { cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) cctx.CctxStatus.Status = types.CctxStatus_PendingOutbound - err := k.ProcessFailedOutbound(ctx, cctx, sample.String()) + err := k.ValidateOutboundObservers(ctx, cctx, observertypes.BallotStatus_BallotFinalized_FailureObservation, sample.String()) require.ErrorIs(t, err, types.ErrForeignCoinNotFound) require.Equal(t, cctx.CctxStatus.Status, types.CctxStatus_PendingOutbound) }) } -func TestKeeper_ProcessOutbound(t *testing.T) { - t.Run("successfully process outbound with ballot finalized to success", func(t *testing.T) { +func TestKeeper_ValidateOutboundObservers(t *testing.T) { + t.Run("successfully validate outbound with ballot finalized to success", func(t *testing.T) { k, ctx, _, _ := keepertest.CrosschainKeeper(t) cctx := GetERC20Cctx(t, sample.EthAddress(), chains.Goerli, "", big.NewInt(42)) cctx.CctxStatus.Status = types.CctxStatus_PendingOutbound - err := k.ProcessOutbound( + err := k.ValidateOutboundObservers( ctx, cctx, observertypes.BallotStatus_BallotFinalized_SuccessObservation, @@ -336,12 +343,12 @@ func TestKeeper_ProcessOutbound(t *testing.T) { }) t.Run( - "successfully process outbound with ballot finalized to failed and old status is Pending Revert", + "successfully validate outbound with ballot finalized to failed and old status is Pending Revert", func(t *testing.T) { k, ctx, _, _ := keepertest.CrosschainKeeper(t) cctx := GetERC20Cctx(t, sample.EthAddress(), chains.Goerli, "", big.NewInt(42)) cctx.CctxStatus.Status = types.CctxStatus_PendingRevert - err := k.ProcessOutbound( + err := k.ValidateOutboundObservers( ctx, cctx, observertypes.BallotStatus_BallotFinalized_FailureObservation, @@ -353,12 +360,12 @@ func TestKeeper_ProcessOutbound(t *testing.T) { }, ) - t.Run("successfully process outbound with ballot finalized to failed and coin-type is CMD", func(t *testing.T) { + t.Run("successfully validate outbound with ballot finalized to failed and coin-type is CMD", func(t *testing.T) { k, ctx, _, _ := keepertest.CrosschainKeeper(t) cctx := GetERC20Cctx(t, sample.EthAddress(), chains.Goerli, "", big.NewInt(42)) cctx.CctxStatus.Status = types.CctxStatus_PendingOutbound cctx.InboundParams.CoinType = coin.CoinType_Cmd - err := k.ProcessOutbound( + err := k.ValidateOutboundObservers( ctx, cctx, observertypes.BallotStatus_BallotFinalized_FailureObservation, @@ -369,16 +376,16 @@ func TestKeeper_ProcessOutbound(t *testing.T) { require.Equal(t, cctx.GetCurrentOutboundParam().TxFinalizationStatus, types.TxFinalizationStatus_Executed) }) - t.Run("do not process if cctx invalid", func(t *testing.T) { + t.Run("do not validate if cctx invalid", func(t *testing.T) { k, ctx, _, _ := keepertest.CrosschainKeeper(t) cctx := GetERC20Cctx(t, sample.EthAddress(), chains.Goerli, "", big.NewInt(42)) cctx.CctxStatus.Status = types.CctxStatus_PendingOutbound cctx.InboundParams = nil - err := k.ProcessOutbound(ctx, cctx, observertypes.BallotStatus_BallotInProgress, sample.String()) + err := k.ValidateOutboundObservers(ctx, cctx, observertypes.BallotStatus_BallotInProgress, sample.String()) require.Error(t, err) }) - t.Run("do not process outbound on error, no new outbound created", func(t *testing.T) { + t.Run("do not validate outbound on error, no new outbound created", func(t *testing.T) { k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ UseFungibleMock: true, }) @@ -399,7 +406,7 @@ func TestKeeper_ProcessOutbound(t *testing.T) { Zrc20ContractAddress: sample.EthAddress().String(), }, false).Once() - err := k.ProcessOutbound( + err := k.ValidateOutboundObservers( ctx, cctx, observertypes.BallotStatus_BallotFinalized_FailureObservation, @@ -412,7 +419,7 @@ func TestKeeper_ProcessOutbound(t *testing.T) { require.Equal(t, cctx.GetCurrentOutboundParam().TxFinalizationStatus, types.TxFinalizationStatus_NotFinalized) }) - t.Run("do not process outbound if the cctx has already been reverted once", func(t *testing.T) { + t.Run("do not validate outbound if the cctx has already been reverted once", func(t *testing.T) { k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ UseFungibleMock: true, }) @@ -434,7 +441,7 @@ func TestKeeper_ProcessOutbound(t *testing.T) { // mock successful GetRevertGasLimit for ERC20 keepertest.MockGetRevertGasLimitForERC20(fungibleMock, asset, *senderChain, 100) - err := k.ProcessOutbound( + err := k.ValidateOutboundObservers( ctx, cctx, observertypes.BallotStatus_BallotFinalized_FailureObservation, @@ -469,7 +476,7 @@ func TestKeeper_ProcessOutbound(t *testing.T) { // mock successful UpdateNonce _ = keepertest.MockUpdateNonce(observerMock, *senderChain) - err := k.ProcessOutbound( + err := k.ValidateOutboundObservers( ctx, cctx, observertypes.BallotStatus_BallotFinalized_FailureObservation, From 2e826a78c00060eaf343d96c6429d7373826d3f2 Mon Sep 17 00:00:00 2001 From: skosito Date: Wed, 5 Jun 2024 15:15:36 +0200 Subject: [PATCH 04/38] fixes --- x/crosschain/keeper/cctx_gateway_zevm.go | 4 +- x/crosschain/keeper/cctx_orchestrator.go | 48 ++++++++++++------------ 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/x/crosschain/keeper/cctx_gateway_zevm.go b/x/crosschain/keeper/cctx_gateway_zevm.go index e37b45cb81..7714aa2ca0 100644 --- a/x/crosschain/keeper/cctx_gateway_zevm.go +++ b/x/crosschain/keeper/cctx_gateway_zevm.go @@ -36,7 +36,5 @@ Instead we use a temporary context to make changes and then commit the context o New CCTX status after preprocessing is returned. */ func (c CCTXGatewayZEVM) InitiateOutbound(ctx sdk.Context, cctx *types.CrossChainTx) (newCCTXStatus types.CctxStatus) { - isContractReverted, err := c.crosschainKeeper.HandleEVMDeposit(ctx, cctx) - - return c.crosschainKeeper.ValidateOutboundZEVM(ctx, cctx, err, isContractReverted) + return c.crosschainKeeper.ValidateOutboundZEVM(ctx, cctx) } diff --git a/x/crosschain/keeper/cctx_orchestrator.go b/x/crosschain/keeper/cctx_orchestrator.go index c4ef1fedd6..7837082ebd 100644 --- a/x/crosschain/keeper/cctx_orchestrator.go +++ b/x/crosschain/keeper/cctx_orchestrator.go @@ -16,16 +16,19 @@ import ( observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) -func (k Keeper) ValidateOutboundZEVM(ctx sdk.Context, cctx *types.CrossChainTx, zevmError error, isContractReverted bool) (newCCTXStatus types.CctxStatus) { - if zevmError != nil && !isContractReverted { +func (k Keeper) ValidateOutboundZEVM(ctx sdk.Context, cctx *types.CrossChainTx) (newCCTXStatus types.CctxStatus) { + tmpCtx, commit := ctx.CacheContext() + isContractReverted, err := k.HandleEVMDeposit(tmpCtx, cctx) + + if err != nil && !isContractReverted { // exceptional case; internal error; should abort CCTX - cctx.SetAbort(zevmError.Error()) + cctx.SetAbort(err.Error()) return types.CctxStatus_Aborted } - if zevmError != nil && isContractReverted { + if err != nil && isContractReverted { // contract call reverted; should refund via a revert tx - err := k.tryRevertOutbound(ctx, cctx, zevmError) + err := k.tryRevertOutboundZEVM(ctx, cctx, err) if err != nil { cctx.SetAbort(err.Error()) return types.CctxStatus_Aborted @@ -33,13 +36,12 @@ func (k Keeper) ValidateOutboundZEVM(ctx sdk.Context, cctx *types.CrossChainTx, return types.CctxStatus_PendingRevert } - _, commit := ctx.CacheContext() commit() cctx.SetOutBoundMined("Remote omnichain contract call completed") return types.CctxStatus_OutboundMined } -func (k Keeper) tryRevertOutbound(ctx sdk.Context, cctx *types.CrossChainTx, zevmError error) error { +func (k Keeper) tryRevertOutboundZEVM(ctx sdk.Context, cctx *types.CrossChainTx, zevmError error) error { senderChain := k.zetaObserverKeeper.GetSupportedChainFromChainID(ctx, cctx.InboundParams.SenderChainId) if senderChain == nil { return fmt.Errorf("invalid sender chain id %d", cctx.InboundParams.SenderChainId) @@ -91,9 +93,9 @@ func (k Keeper) ValidateOutboundObservers(ctx sdk.Context, cctx *types.CrossChai err := func() error { switch ballotStatus { case observertypes.BallotStatus_BallotFinalized_SuccessObservation: - k.processSuccessfulOutbound(tmpCtx, cctx, valueReceived) + k.validateSuccessfulOutboundObservers(tmpCtx, cctx, valueReceived) case observertypes.BallotStatus_BallotFinalized_FailureObservation: - err := k.processFailedOutbound(tmpCtx, cctx, valueReceived) + err := k.validateFailedOutboundObservers(tmpCtx, cctx, valueReceived) if err != nil { return err } @@ -111,7 +113,7 @@ func (k Keeper) ValidateOutboundObservers(ctx sdk.Context, cctx *types.CrossChai return nil } -// ProcessFailedOutbound processes a failed outbound transaction. It does the following things in one function: +// validateFailedOutboundObservers processes a failed outbound transaction. It does the following things in one function: // // 1. For Admin Tx or a withdrawal from Zeta chain, it aborts the CCTX // @@ -125,8 +127,8 @@ func (k Keeper) ValidateOutboundObservers(ctx sdk.Context, cctx *types.CrossChai // // This function sets CCTX status , in cases where the outbound tx is successful, but tx itself fails // This is done because SaveSuccessfulOutbound does not set the cctx status -// For cases where the outbound tx is unsuccessful, the cctx status is automatically set to Aborted in the ProcessFailedOutbound function, so we can just return and error to trigger that -func (k Keeper) processFailedOutbound(ctx sdk.Context, cctx *types.CrossChainTx, valueReceived string) error { +// For cases where the outbound tx is unsuccessful, the cctx status is automatically set to Aborted in the validateFailedOutboundObservers function, so we can just return and error to trigger that +func (k Keeper) validateFailedOutboundObservers(ctx sdk.Context, cctx *types.CrossChainTx, valueReceived string) error { oldStatus := cctx.CctxStatus.Status // The following logic is used to handler the mentioned conditions separately. The reason being // All admin tx is created using a policy message , there is no associated inbound tx , therefore we do not need any revert logic @@ -142,9 +144,9 @@ func (k Keeper) processFailedOutbound(ctx sdk.Context, cctx *types.CrossChainTx, // Try revert if the coin-type is ZETA case coin.CoinType_Zeta: { - err := k.processFailedOutboundForZEVM(ctx, cctx) + err := k.validateFailedOutboundObserversForZEVM(ctx, cctx) if err != nil { - return cosmoserrors.Wrap(err, "ProcessFailedOutboundForZEVMTx") + return cosmoserrors.Wrap(err, "validateFailedOutboundObserversForZEVMTx") } } // For all other coin-types, we do not revert, the cctx is aborted @@ -155,9 +157,9 @@ func (k Keeper) processFailedOutbound(ctx sdk.Context, cctx *types.CrossChainTx, } } } else { - err := k.processFailedOutboundForExternalChainTx(ctx, cctx, oldStatus) + err := k.validateFailedOutboundObserversForExternalChainTx(ctx, cctx, oldStatus) if err != nil { - return cosmoserrors.Wrap(err, "ProcessFailedOutBoundForExternalChainTx") + return cosmoserrors.Wrap(err, "validateFailedOutBoundObserversForExternalChainTx") } } newStatus := cctx.CctxStatus.Status.String() @@ -165,8 +167,8 @@ func (k Keeper) processFailedOutbound(ctx sdk.Context, cctx *types.CrossChainTx, return nil } -// processFailedOutboundForExternalChainTx processes the failed outbound transaction for external chain tx -func (k Keeper) processFailedOutboundForExternalChainTx( +// validateFailedOutboundObserversForExternalChainTx processes the failed outbound transaction for external chain tx +func (k Keeper) validateFailedOutboundObserversForExternalChainTx( ctx sdk.Context, cctx *types.CrossChainTx, oldStatus types.CctxStatus, @@ -212,7 +214,7 @@ func (k Keeper) processFailedOutboundForExternalChainTx( return nil } -// processSuccessfulOutbound processes a successful outbound transaction. It does the following things in one function: +// validateSuccessfulOutboundObservers processes a successful outbound transaction. It does the following things in one function: // // 1. Change the status of the CCTX from // - PendingRevert to Reverted @@ -224,8 +226,8 @@ func (k Keeper) processFailedOutboundForExternalChainTx( // // This function sets CCTX status , in cases where the outbound tx is successful, but tx itself fails // This is done because SaveSuccessfulOutbound does not set the cctx status -// For cases where the outbound tx is unsuccessful, the cctx status is automatically set to Aborted in the ProcessFailedOutbound function, so we can just return and error to trigger that -func (k Keeper) processSuccessfulOutbound(ctx sdk.Context, cctx *types.CrossChainTx, valueReceived string) { +// For cases where the outbound tx is unsuccessful, the cctx status is automatically set to Aborted in the validateFailedOutboundObservers function, so we can just return and error to trigger that +func (k Keeper) validateSuccessfulOutboundObservers(ctx sdk.Context, cctx *types.CrossChainTx, valueReceived string) { oldStatus := cctx.CctxStatus.Status switch oldStatus { case types.CctxStatus_PendingRevert: @@ -240,8 +242,8 @@ func (k Keeper) processSuccessfulOutbound(ctx sdk.Context, cctx *types.CrossChai EmitOutboundSuccess(ctx, valueReceived, oldStatus.String(), newStatus, cctx.Index) } -// processFailedOutboundForZEVM processes the failed outbound transaction for ZEVM -func (k Keeper) processFailedOutboundForZEVM(ctx sdk.Context, cctx *types.CrossChainTx) error { +// validateFailedOutboundObserversForZEVM processes the failed outbound transaction for ZEVM +func (k Keeper) validateFailedOutboundObserversForZEVM(ctx sdk.Context, cctx *types.CrossChainTx) error { indexBytes, err := cctx.GetCCTXIndexBytes() if err != nil { // Return err to save the failed outbound ad set to aborted From 6cc517e45f20e177617503d36df01a8c68a36d0d Mon Sep 17 00:00:00 2001 From: skosito Date: Wed, 5 Jun 2024 16:01:37 +0200 Subject: [PATCH 05/38] refactoring --- x/crosschain/keeper/cctx_orchestrator.go | 82 ++++--------- x/crosschain/keeper/validate_outbound_test.go | 112 +++++++++++++++--- 2 files changed, 122 insertions(+), 72 deletions(-) diff --git a/x/crosschain/keeper/cctx_orchestrator.go b/x/crosschain/keeper/cctx_orchestrator.go index 7837082ebd..44af8456b1 100644 --- a/x/crosschain/keeper/cctx_orchestrator.go +++ b/x/crosschain/keeper/cctx_orchestrator.go @@ -9,6 +9,7 @@ import ( tmtypes "github.com/cometbft/cometbft/types" sdk "github.com/cosmos/cosmos-sdk/types" ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/pkg/coin" "github.com/zeta-chain/zetacore/x/crosschain/types" @@ -26,74 +27,37 @@ func (k Keeper) ValidateOutboundZEVM(ctx sdk.Context, cctx *types.CrossChainTx) return types.CctxStatus_Aborted } + cctx.SetPendingOutbound("") + if err != nil && isContractReverted { // contract call reverted; should refund via a revert tx - err := k.tryRevertOutboundZEVM(ctx, cctx, err) + err := k.validateFailedOutbound(tmpCtx, cctx, types.CctxStatus_PendingOutbound, err.Error()) if err != nil { cctx.SetAbort(err.Error()) return types.CctxStatus_Aborted } + commit() return types.CctxStatus_PendingRevert } commit() - cctx.SetOutBoundMined("Remote omnichain contract call completed") + k.validateSuccessfulOutbound(ctx, cctx, "", false) return types.CctxStatus_OutboundMined } -func (k Keeper) tryRevertOutboundZEVM(ctx sdk.Context, cctx *types.CrossChainTx, zevmError error) error { - senderChain := k.zetaObserverKeeper.GetSupportedChainFromChainID(ctx, cctx.InboundParams.SenderChainId) - if senderChain == nil { - return fmt.Errorf("invalid sender chain id %d", cctx.InboundParams.SenderChainId) - } - // use same gas limit of outbound as a fallback -- should not be required - gasLimit, err := k.GetRevertGasLimit(ctx, *cctx) - if err != nil { - return fmt.Errorf("revert gas limit error: %s", err.Error()) - } - if gasLimit == 0 { - gasLimit = cctx.GetCurrentOutboundParam().GasLimit - } - - revertMessage := zevmError.Error() - err = cctx.AddRevertOutbound(gasLimit) - if err != nil { - return fmt.Errorf("revert outbound error: %s", err.Error()) - } - - // we create a new cached context, and we don't commit the previous one with EVM deposit - tmpCtxRevert, commitRevert := ctx.CacheContext() - err = func() error { - err := k.PayGasAndUpdateCctx( - tmpCtxRevert, - senderChain.ChainId, - cctx, - cctx.InboundParams.Amount, - false, - ) - if err != nil { - return err - } - - // update nonce using senderchain id as this is a revert tx and would go back to the original sender - return k.UpdateNonce(tmpCtxRevert, senderChain.ChainId, cctx) - }() - if err != nil { - return fmt.Errorf("deposit revert message: %s err : %s", revertMessage, err.Error()) - } - commitRevert() - cctx.SetPendingRevert(revertMessage) - return nil -} - // ValidateOutboundObservers processes the finalization of an outbound transaction based on the ballot status // The state is committed only if the individual steps are successful -func (k Keeper) ValidateOutboundObservers(ctx sdk.Context, cctx *types.CrossChainTx, ballotStatus observertypes.BallotStatus, valueReceived string) error { +func (k Keeper) ValidateOutboundObservers( + ctx sdk.Context, + cctx *types.CrossChainTx, + ballotStatus observertypes.BallotStatus, + valueReceived string, +) error { tmpCtx, commit := ctx.CacheContext() err := func() error { switch ballotStatus { case observertypes.BallotStatus_BallotFinalized_SuccessObservation: - k.validateSuccessfulOutboundObservers(tmpCtx, cctx, valueReceived) + k.validateSuccessfulOutbound(tmpCtx, cctx, valueReceived, true) case observertypes.BallotStatus_BallotFinalized_FailureObservation: err := k.validateFailedOutboundObservers(tmpCtx, cctx, valueReceived) if err != nil { @@ -157,9 +121,9 @@ func (k Keeper) validateFailedOutboundObservers(ctx sdk.Context, cctx *types.Cro } } } else { - err := k.validateFailedOutboundObserversForExternalChainTx(ctx, cctx, oldStatus) + err := k.validateFailedOutbound(ctx, cctx, oldStatus, "") if err != nil { - return cosmoserrors.Wrap(err, "validateFailedOutBoundObserversForExternalChainTx") + return cosmoserrors.Wrap(err, "validateFailedOutbound") } } newStatus := cctx.CctxStatus.Status.String() @@ -167,11 +131,12 @@ func (k Keeper) validateFailedOutboundObservers(ctx sdk.Context, cctx *types.Cro return nil } -// validateFailedOutboundObserversForExternalChainTx processes the failed outbound transaction for external chain tx -func (k Keeper) validateFailedOutboundObserversForExternalChainTx( +// validateFailedOutbound processes the failed outbound transaction +func (k Keeper) validateFailedOutbound( ctx sdk.Context, cctx *types.CrossChainTx, oldStatus types.CctxStatus, + revertMsg string, ) error { switch oldStatus { case types.CctxStatus_PendingOutbound: @@ -205,8 +170,11 @@ func (k Keeper) validateFailedOutboundObserversForExternalChainTx( if err != nil { return err } + if revertMsg == "" { + revertMsg = "Outbound failed, start revert" + } // Not setting the finalization status here, the required changes have been made while creating the revert tx - cctx.SetPendingRevert("Outbound failed, start revert") + cctx.SetPendingRevert(revertMsg) case types.CctxStatus_PendingRevert: cctx.GetCurrentOutboundParam().TxFinalizationStatus = types.TxFinalizationStatus_Executed cctx.SetAbort("Outbound failed: revert failed; abort TX") @@ -227,7 +195,7 @@ func (k Keeper) validateFailedOutboundObserversForExternalChainTx( // This function sets CCTX status , in cases where the outbound tx is successful, but tx itself fails // This is done because SaveSuccessfulOutbound does not set the cctx status // For cases where the outbound tx is unsuccessful, the cctx status is automatically set to Aborted in the validateFailedOutboundObservers function, so we can just return and error to trigger that -func (k Keeper) validateSuccessfulOutboundObservers(ctx sdk.Context, cctx *types.CrossChainTx, valueReceived string) { +func (k Keeper) validateSuccessfulOutbound(ctx sdk.Context, cctx *types.CrossChainTx, valueReceived string, emitEvent bool) { oldStatus := cctx.CctxStatus.Status switch oldStatus { case types.CctxStatus_PendingRevert: @@ -239,7 +207,9 @@ func (k Keeper) validateSuccessfulOutboundObservers(ctx sdk.Context, cctx *types } cctx.GetCurrentOutboundParam().TxFinalizationStatus = types.TxFinalizationStatus_Executed newStatus := cctx.CctxStatus.Status.String() - EmitOutboundSuccess(ctx, valueReceived, oldStatus.String(), newStatus, cctx.Index) + if emitEvent { + EmitOutboundSuccess(ctx, valueReceived, oldStatus.String(), newStatus, cctx.Index) + } } // validateFailedOutboundObserversForZEVM processes the failed outbound transaction for ZEVM diff --git a/x/crosschain/keeper/validate_outbound_test.go b/x/crosschain/keeper/validate_outbound_test.go index 9db1daaa7b..8d30b5b82e 100644 --- a/x/crosschain/keeper/validate_outbound_test.go +++ b/x/crosschain/keeper/validate_outbound_test.go @@ -26,14 +26,29 @@ func TestKeeper_ValidateSuccessfulOutbound(t *testing.T) { cctx := sample.CrossChainTx(t, "test") // transition to reverted if pending revert cctx.CctxStatus.Status = types.CctxStatus_PendingRevert - k.ValidateOutboundObservers(ctx, cctx, observertypes.BallotStatus_BallotFinalized_SuccessObservation, sample.String()) + k.ValidateOutboundObservers( + ctx, + cctx, + observertypes.BallotStatus_BallotFinalized_SuccessObservation, + sample.String(), + ) require.Equal(t, cctx.CctxStatus.Status, types.CctxStatus_Reverted) // transition to outbound mined if pending outbound cctx.CctxStatus.Status = types.CctxStatus_PendingOutbound - k.ValidateOutboundObservers(ctx, cctx, observertypes.BallotStatus_BallotFinalized_SuccessObservation, sample.String()) + k.ValidateOutboundObservers( + ctx, + cctx, + observertypes.BallotStatus_BallotFinalized_SuccessObservation, + sample.String(), + ) require.Equal(t, cctx.CctxStatus.Status, types.CctxStatus_OutboundMined) // do nothing if it's in any other state - k.ValidateOutboundObservers(ctx, cctx, observertypes.BallotStatus_BallotFinalized_SuccessObservation, sample.String()) + k.ValidateOutboundObservers( + ctx, + cctx, + observertypes.BallotStatus_BallotFinalized_SuccessObservation, + sample.String(), + ) require.Equal(t, cctx.CctxStatus.Status, types.CctxStatus_OutboundMined) } @@ -45,7 +60,12 @@ func TestKeeper_ValidateFailedOutbound(t *testing.T) { cctx.InboundParams.SenderChainId = chains.Ethereum.ChainId cctx.OutboundParams[0].ReceiverChainId = chains.Ethereum.ChainId cctx.OutboundParams[1].ReceiverChainId = chains.Ethereum.ChainId - err := k.ValidateOutboundObservers(ctx, cctx, observertypes.BallotStatus_BallotFinalized_FailureObservation, sample.String()) + err := k.ValidateOutboundObservers( + ctx, + cctx, + observertypes.BallotStatus_BallotFinalized_FailureObservation, + sample.String(), + ) require.NoError(t, err) require.Equal(t, cctx.CctxStatus.Status, types.CctxStatus_Aborted) require.Equal(t, cctx.GetCurrentOutboundParam().TxFinalizationStatus, types.TxFinalizationStatus_Executed) @@ -58,7 +78,12 @@ func TestKeeper_ValidateFailedOutbound(t *testing.T) { cctx.InboundParams.SenderChainId = chains.ZetaChainMainnet.ChainId cctx.OutboundParams[0].ReceiverChainId = chains.Ethereum.ChainId cctx.OutboundParams[1].ReceiverChainId = chains.Ethereum.ChainId - err := k.ValidateOutboundObservers(ctx, cctx, observertypes.BallotStatus_BallotFinalized_FailureObservation, sample.String()) + err := k.ValidateOutboundObservers( + ctx, + cctx, + observertypes.BallotStatus_BallotFinalized_FailureObservation, + sample.String(), + ) require.NoError(t, err) require.Equal(t, cctx.CctxStatus.Status, types.CctxStatus_Aborted) require.Equal(t, cctx.GetCurrentOutboundParam().TxFinalizationStatus, types.TxFinalizationStatus_Executed) @@ -71,7 +96,12 @@ func TestKeeper_ValidateFailedOutbound(t *testing.T) { cctx.InboundParams.SenderChainId = chains.ZetaChainMainnet.ChainId cctx.OutboundParams[0].ReceiverChainId = chains.Ethereum.ChainId cctx.OutboundParams[1].ReceiverChainId = chains.Ethereum.ChainId - err := k.ValidateOutboundObservers(ctx, cctx, observertypes.BallotStatus_BallotFinalized_FailureObservation, sample.String()) + err := k.ValidateOutboundObservers( + ctx, + cctx, + observertypes.BallotStatus_BallotFinalized_FailureObservation, + sample.String(), + ) require.NoError(t, err) require.Equal(t, cctx.CctxStatus.Status, types.CctxStatus_Aborted) require.Equal(t, cctx.GetCurrentOutboundParam().TxFinalizationStatus, types.TxFinalizationStatus_Executed) @@ -89,7 +119,12 @@ func TestKeeper_ValidateFailedOutbound(t *testing.T) { ) require.NoError(t, err) cctx.InboundParams.SenderChainId = chains.ZetaChainMainnet.ChainId - err = k.ValidateOutboundObservers(ctx, cctx, observertypes.BallotStatus_BallotFinalized_FailureObservation, sample.String()) + err = k.ValidateOutboundObservers( + ctx, + cctx, + observertypes.BallotStatus_BallotFinalized_FailureObservation, + sample.String(), + ) require.NoError(t, err) require.Equal(t, types.CctxStatus_Reverted, cctx.CctxStatus.Status) require.Equal(t, cctx.GetCurrentOutboundParam().TxFinalizationStatus, types.TxFinalizationStatus_Executed) @@ -101,7 +136,12 @@ func TestKeeper_ValidateFailedOutbound(t *testing.T) { cctx.InboundParams.CoinType = coin.CoinType_Zeta cctx.Index = "" cctx.InboundParams.SenderChainId = chains.ZetaChainMainnet.ChainId - err := k.ValidateOutboundObservers(ctx, cctx, observertypes.BallotStatus_BallotFinalized_FailureObservation, sample.String()) + err := k.ValidateOutboundObservers( + ctx, + cctx, + observertypes.BallotStatus_BallotFinalized_FailureObservation, + sample.String(), + ) require.ErrorContains(t, err, "failed reverting GetCCTXIndexBytes") }) @@ -110,7 +150,12 @@ func TestKeeper_ValidateFailedOutbound(t *testing.T) { cctx := sample.CrossChainTx(t, "test") cctx.InboundParams.SenderChainId = chains.ZetaChainMainnet.ChainId cctx.InboundParams.CoinType = coin.CoinType_Zeta - err := k.ValidateOutboundObservers(ctx, cctx, observertypes.BallotStatus_BallotFinalized_FailureObservation, sample.String()) + err := k.ValidateOutboundObservers( + ctx, + cctx, + observertypes.BallotStatus_BallotFinalized_FailureObservation, + sample.String(), + ) require.ErrorContains(t, err, "failed AddRevertOutbound") }) @@ -132,7 +177,12 @@ func TestKeeper_ValidateFailedOutbound(t *testing.T) { cctx.GetCurrentOutboundParam().Amount.BigInt(), mock.Anything, mock.Anything).Return(nil, errorFailedZETARevertAndCallContract).Once() - err := k.ValidateOutboundObservers(ctx, cctx, observertypes.BallotStatus_BallotFinalized_FailureObservation, sample.String()) + err := k.ValidateOutboundObservers( + ctx, + cctx, + observertypes.BallotStatus_BallotFinalized_FailureObservation, + sample.String(), + ) require.ErrorContains(t, err, "failed ZETARevertAndCallContract") }) @@ -151,7 +201,12 @@ func TestKeeper_ValidateFailedOutbound(t *testing.T) { cctx.InboundParams.Sender = dAppContract.String() cctx.InboundParams.SenderChainId = chains.ZetaChainMainnet.ChainId - err = k.ValidateOutboundObservers(ctx, cctx, observertypes.BallotStatus_BallotFinalized_FailureObservation, sample.String()) + err = k.ValidateOutboundObservers( + ctx, + cctx, + observertypes.BallotStatus_BallotFinalized_FailureObservation, + sample.String(), + ) require.NoError(t, err) require.Equal(t, types.CctxStatus_Reverted, cctx.CctxStatus.Status) require.Equal(t, cctx.GetCurrentOutboundParam().TxFinalizationStatus, types.TxFinalizationStatus_Executed) @@ -203,7 +258,12 @@ func TestKeeper_ValidateFailedOutbound(t *testing.T) { cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) cctx.CctxStatus.Status = types.CctxStatus_PendingOutbound - err := k.ValidateOutboundObservers(ctx, cctx, observertypes.BallotStatus_BallotFinalized_FailureObservation, sample.String()) + err := k.ValidateOutboundObservers( + ctx, + cctx, + observertypes.BallotStatus_BallotFinalized_FailureObservation, + sample.String(), + ) require.NoError(t, err) require.Equal(t, cctx.CctxStatus.Status, types.CctxStatus_PendingRevert) require.Equal(t, types.TxFinalizationStatus_NotFinalized, cctx.GetCurrentOutboundParam().TxFinalizationStatus) @@ -235,7 +295,12 @@ func TestKeeper_ValidateFailedOutbound(t *testing.T) { cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) cctx.CctxStatus.Status = types.CctxStatus_PendingOutbound - err := k.ValidateOutboundObservers(ctx, cctx, observertypes.BallotStatus_BallotFinalized_FailureObservation, sample.String()) + err := k.ValidateOutboundObservers( + ctx, + cctx, + observertypes.BallotStatus_BallotFinalized_FailureObservation, + sample.String(), + ) require.NoError(t, err) require.Equal(t, cctx.CctxStatus.Status, types.CctxStatus_PendingRevert) require.Equal(t, types.TxFinalizationStatus_NotFinalized, cctx.GetCurrentOutboundParam().TxFinalizationStatus) @@ -268,7 +333,12 @@ func TestKeeper_ValidateFailedOutbound(t *testing.T) { cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) cctx.CctxStatus.Status = types.CctxStatus_PendingOutbound - err := k.ValidateOutboundObservers(ctx, cctx, observertypes.BallotStatus_BallotFinalized_FailureObservation, sample.String()) + err := k.ValidateOutboundObservers( + ctx, + cctx, + observertypes.BallotStatus_BallotFinalized_FailureObservation, + sample.String(), + ) require.ErrorIs(t, err, types.ErrCannotFindReceiverNonce) require.Equal(t, cctx.CctxStatus.Status, types.CctxStatus_PendingOutbound) }) @@ -296,7 +366,12 @@ func TestKeeper_ValidateFailedOutbound(t *testing.T) { cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) cctx.CctxStatus.Status = types.CctxStatus_PendingOutbound - err := k.ValidateOutboundObservers(ctx, cctx, observertypes.BallotStatus_BallotFinalized_FailureObservation, sample.String()) + err := k.ValidateOutboundObservers( + ctx, + cctx, + observertypes.BallotStatus_BallotFinalized_FailureObservation, + sample.String(), + ) require.ErrorIs(t, err, observertypes.ErrSupportedChains) require.Equal(t, cctx.CctxStatus.Status, types.CctxStatus_PendingOutbound) }) @@ -321,7 +396,12 @@ func TestKeeper_ValidateFailedOutbound(t *testing.T) { cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) cctx.CctxStatus.Status = types.CctxStatus_PendingOutbound - err := k.ValidateOutboundObservers(ctx, cctx, observertypes.BallotStatus_BallotFinalized_FailureObservation, sample.String()) + err := k.ValidateOutboundObservers( + ctx, + cctx, + observertypes.BallotStatus_BallotFinalized_FailureObservation, + sample.String(), + ) require.ErrorIs(t, err, types.ErrForeignCoinNotFound) require.Equal(t, cctx.CctxStatus.Status, types.CctxStatus_PendingOutbound) }) From e2d3c8bd46a7c8b24c135808442c5b928809e5f9 Mon Sep 17 00:00:00 2001 From: skosito Date: Wed, 5 Jun 2024 16:02:37 +0200 Subject: [PATCH 06/38] changelog --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index 6f0fa7e632..fd7290d5ea 100644 --- a/changelog.md +++ b/changelog.md @@ -23,6 +23,7 @@ * [2291](https://github.com/zeta-chain/node/pull/2291) - initialize cctx gateway interface * [2289](https://github.com/zeta-chain/node/pull/2289) - add an authorization list to keep track of all authorizations on the chain * [2305](https://github.com/zeta-chain/node/pull/2305) - add new messages `MsgAddAuthorization` and `MsgRemoveAuthorization` that can be used to update the authorization list +* [2317](https://github.com/zeta-chain/node/pull/2317) - add ValidateOutbound method for cctx orchestrator ### Refactor From 5f91691535d3c5cc0629d6f1ee887b64914a1b63 Mon Sep 17 00:00:00 2001 From: skosito Date: Wed, 5 Jun 2024 16:12:16 +0200 Subject: [PATCH 07/38] make generate --- x/crosschain/keeper/cctx_orchestrator.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/x/crosschain/keeper/cctx_orchestrator.go b/x/crosschain/keeper/cctx_orchestrator.go index 44af8456b1..369cfd808f 100644 --- a/x/crosschain/keeper/cctx_orchestrator.go +++ b/x/crosschain/keeper/cctx_orchestrator.go @@ -195,7 +195,12 @@ func (k Keeper) validateFailedOutbound( // This function sets CCTX status , in cases where the outbound tx is successful, but tx itself fails // This is done because SaveSuccessfulOutbound does not set the cctx status // For cases where the outbound tx is unsuccessful, the cctx status is automatically set to Aborted in the validateFailedOutboundObservers function, so we can just return and error to trigger that -func (k Keeper) validateSuccessfulOutbound(ctx sdk.Context, cctx *types.CrossChainTx, valueReceived string, emitEvent bool) { +func (k Keeper) validateSuccessfulOutbound( + ctx sdk.Context, + cctx *types.CrossChainTx, + valueReceived string, + emitEvent bool, +) { oldStatus := cctx.CctxStatus.Status switch oldStatus { case types.CctxStatus_PendingRevert: From 6cb69cd7479ad6e7139a36d475f759334d886b6c Mon Sep 17 00:00:00 2001 From: skosito Date: Wed, 5 Jun 2024 16:53:42 +0200 Subject: [PATCH 08/38] fix initiate outbound tests --- x/crosschain/keeper/cctx_orchestrator.go | 4 ++++ x/crosschain/keeper/initiate_outbound_test.go | 16 +++++++--------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/x/crosschain/keeper/cctx_orchestrator.go b/x/crosschain/keeper/cctx_orchestrator.go index 369cfd808f..943bf4437a 100644 --- a/x/crosschain/keeper/cctx_orchestrator.go +++ b/x/crosschain/keeper/cctx_orchestrator.go @@ -140,6 +140,10 @@ func (k Keeper) validateFailedOutbound( ) error { switch oldStatus { case types.CctxStatus_PendingOutbound: + senderChain := k.zetaObserverKeeper.GetSupportedChainFromChainID(ctx, cctx.InboundParams.SenderChainId) + if senderChain == nil { + return fmt.Errorf("invalid sender chain id %d", cctx.InboundParams.SenderChainId) + } gasLimit, err := k.GetRevertGasLimit(ctx, *cctx) if err != nil { diff --git a/x/crosschain/keeper/initiate_outbound_test.go b/x/crosschain/keeper/initiate_outbound_test.go index 59d0eaee0b..f2feb7750c 100644 --- a/x/crosschain/keeper/initiate_outbound_test.go +++ b/x/crosschain/keeper/initiate_outbound_test.go @@ -118,7 +118,7 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) { }, ) - t.Run("unable to process zevm deposit HandleEVMDeposit revert fails at and GetRevertGasLimit", func(t *testing.T) { + t.Run("unable to process zevm deposit HandleEVMDeposit revert fails at GetRevertGasLimit", func(t *testing.T) { k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ UseFungibleMock: true, UseObserverMock: true, @@ -152,13 +152,12 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) { require.Equal(t, types.CctxStatus_Aborted, newStatus) require.Equal( t, - fmt.Sprintf("revert gas limit error: %s", types.ErrForeignCoinNotFound), + "GetRevertGasLimit: foreign coin not found for sender chain", cctx.CctxStatus.StatusMessage, ) }) - t.Run( - "unable to process zevm deposit HandleEVMDeposit revert fails at PayGasInERC20AndUpdateCctx", + t.Run("unable to process zevm deposit HandleEVMDeposit revert fails at PayGasInERC20AndUpdateCctx", func(t *testing.T) { k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ UseFungibleMock: true, @@ -197,7 +196,7 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) { require.Equal(t, types.CctxStatus_Aborted, newStatus) require.Equal( t, - fmt.Sprintf("deposit revert message: %s err : %s", errDeposit, observertypes.ErrSupportedChains), + "chain not supported", cctx.CctxStatus.StatusMessage, ) }, @@ -243,7 +242,7 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) { require.Equal(t, types.CctxStatus_Aborted, newStatus) require.Equal( t, - fmt.Sprintf("deposit revert message: %s err : %s", errDeposit, observertypes.ErrSupportedChains), + fmt.Sprintf("chain not supported"), cctx.CctxStatus.StatusMessage, ) }, @@ -332,8 +331,7 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) { require.Equal(t, updatedNonce, cctx.GetCurrentOutboundParam().TssNonce) }) - t.Run( - "unable to process zevm deposit HandleEVMDeposit revert fails as the cctx has already been reverted", + t.Run("unable to process zevm deposit HandleEVMDeposit revert fails as the cctx has already been reverted", func(t *testing.T) { k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ UseFungibleMock: true, @@ -370,7 +368,7 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) { require.Contains( t, cctx.CctxStatus.StatusMessage, - fmt.Sprintf("revert outbound error: %s", "cannot revert a revert tx"), + fmt.Sprintf("cannot revert a revert tx"), ) }, ) From 47b7b885fa2e8225f9389b4980f77870041699fc Mon Sep 17 00:00:00 2001 From: skosito Date: Wed, 5 Jun 2024 17:27:10 +0200 Subject: [PATCH 09/38] fix validate outbound tests --- x/crosschain/keeper/cctx_orchestrator.go | 2 +- x/crosschain/keeper/validate_outbound_test.go | 21 ++++++++++++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/x/crosschain/keeper/cctx_orchestrator.go b/x/crosschain/keeper/cctx_orchestrator.go index 943bf4437a..6aaa50a124 100644 --- a/x/crosschain/keeper/cctx_orchestrator.go +++ b/x/crosschain/keeper/cctx_orchestrator.go @@ -142,7 +142,7 @@ func (k Keeper) validateFailedOutbound( case types.CctxStatus_PendingOutbound: senderChain := k.zetaObserverKeeper.GetSupportedChainFromChainID(ctx, cctx.InboundParams.SenderChainId) if senderChain == nil { - return fmt.Errorf("invalid sender chain id %d", cctx.InboundParams.SenderChainId) + return observertypes.ErrSupportedChains } gasLimit, err := k.GetRevertGasLimit(ctx, *cctx) diff --git a/x/crosschain/keeper/validate_outbound_test.go b/x/crosschain/keeper/validate_outbound_test.go index 8d30b5b82e..8e218ff0cc 100644 --- a/x/crosschain/keeper/validate_outbound_test.go +++ b/x/crosschain/keeper/validate_outbound_test.go @@ -253,6 +253,9 @@ func TestKeeper_ValidateFailedOutbound(t *testing.T) { // mock successful PayGasAndUpdateCctx keepertest.MockPayGasAndUpdateCCTX(fungibleMock, observerMock, ctx, *k, *senderChain, asset) + observerMock.On("GetSupportedChainFromChainID", mock.Anything, senderChain.ChainId). + Return(senderChain) + // mock successful UpdateNonce _ = keepertest.MockUpdateNonce(observerMock, *senderChain) @@ -290,6 +293,9 @@ func TestKeeper_ValidateFailedOutbound(t *testing.T) { // mock successful PayGasAndUpdateCctx keepertest.MockPayGasAndUpdateCCTX(fungibleMock, observerMock, ctx, *k, *senderChain, asset) + observerMock.On("GetSupportedChainFromChainID", mock.Anything, senderChain.ChainId). + Return(senderChain) + // mock successful UpdateNonce _ = keepertest.MockUpdateNonce(observerMock, *senderChain) @@ -327,6 +333,9 @@ func TestKeeper_ValidateFailedOutbound(t *testing.T) { // mock successful PayGasAndUpdateCctx keepertest.MockPayGasAndUpdateCCTX(fungibleMock, observerMock, ctx, *k, *senderChain, asset) + observerMock.On("GetSupportedChainFromChainID", mock.Anything, senderChain.ChainId). + Return(senderChain) + // mock failed UpdateNonce observerMock.On("GetChainNonces", mock.Anything, senderChain.ChainName.String()). Return(observertypes.ChainNonces{}, false) @@ -360,7 +369,11 @@ func TestKeeper_ValidateFailedOutbound(t *testing.T) { // mock successful GetRevertGasLimit for ERC20 keepertest.MockGetRevertGasLimitForERC20(fungibleMock, asset, *senderChain, 100) - // mock successful PayGasAndUpdateCctx + // mock first check + observerMock.On("GetSupportedChainFromChainID", mock.Anything, senderChain.ChainId). + Return(senderChain).Once() + + // mock failed PayGasAndUpdateCctx observerMock.On("GetSupportedChainFromChainID", mock.Anything, senderChain.ChainId). Return(nil).Once() @@ -422,8 +435,7 @@ func TestKeeper_ValidateOutboundObservers(t *testing.T) { require.Equal(t, cctx.CctxStatus.Status, types.CctxStatus_OutboundMined) }) - t.Run( - "successfully validate outbound with ballot finalized to failed and old status is Pending Revert", + t.Run("successfully validate outbound with ballot finalized to failed and old status is Pending Revert", func(t *testing.T) { k, ctx, _, _ := keepertest.CrosschainKeeper(t) cctx := GetERC20Cctx(t, sample.EthAddress(), chains.Goerli, "", big.NewInt(42)) @@ -553,6 +565,9 @@ func TestKeeper_ValidateOutboundObservers(t *testing.T) { // mock successful PayGasAndUpdateCctx keepertest.MockPayGasAndUpdateCCTX(fungibleMock, observerMock, ctx, *k, *senderChain, asset) + observerMock.On("GetSupportedChainFromChainID", mock.Anything, senderChain.ChainId). + Return(senderChain) + // mock successful UpdateNonce _ = keepertest.MockUpdateNonce(observerMock, *senderChain) From 08dfea16f5fc817e994273907f09da17b5418786 Mon Sep 17 00:00:00 2001 From: skosito Date: Wed, 5 Jun 2024 17:43:28 +0200 Subject: [PATCH 10/38] fix tests --- x/crosschain/keeper/initiate_outbound_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/crosschain/keeper/initiate_outbound_test.go b/x/crosschain/keeper/initiate_outbound_test.go index f2feb7750c..b01cc47a5f 100644 --- a/x/crosschain/keeper/initiate_outbound_test.go +++ b/x/crosschain/keeper/initiate_outbound_test.go @@ -112,7 +112,7 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) { require.Equal(t, types.CctxStatus_Aborted, newStatus) require.Equal( t, - fmt.Sprintf("invalid sender chain id %d", cctx.InboundParams.SenderChainId), + fmt.Sprintf("chain not supported"), cctx.CctxStatus.StatusMessage, ) }, From 0bba576043b9dc631f3717a869ce4223ef3b0c85 Mon Sep 17 00:00:00 2001 From: skosito Date: Wed, 5 Jun 2024 18:24:24 +0200 Subject: [PATCH 11/38] fix e2e test --- x/crosschain/keeper/cctx_orchestrator.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/x/crosschain/keeper/cctx_orchestrator.go b/x/crosschain/keeper/cctx_orchestrator.go index 6aaa50a124..209f1bfdb2 100644 --- a/x/crosschain/keeper/cctx_orchestrator.go +++ b/x/crosschain/keeper/cctx_orchestrator.go @@ -5,6 +5,7 @@ import ( "fmt" cosmoserrors "cosmossdk.io/errors" + "cosmossdk.io/math" tmbytes "github.com/cometbft/cometbft/libs/bytes" tmtypes "github.com/cometbft/cometbft/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -30,14 +31,15 @@ func (k Keeper) ValidateOutboundZEVM(ctx sdk.Context, cctx *types.CrossChainTx) cctx.SetPendingOutbound("") if err != nil && isContractReverted { + tmpCtxRevert, commitRevert := ctx.CacheContext() // contract call reverted; should refund via a revert tx - err := k.validateFailedOutbound(tmpCtx, cctx, types.CctxStatus_PendingOutbound, err.Error()) + err := k.validateFailedOutbound(tmpCtxRevert, cctx, types.CctxStatus_PendingOutbound, err.Error(), cctx.InboundParams.Amount) if err != nil { cctx.SetAbort(err.Error()) return types.CctxStatus_Aborted } - commit() + commitRevert() return types.CctxStatus_PendingRevert } commit() @@ -121,7 +123,7 @@ func (k Keeper) validateFailedOutboundObservers(ctx sdk.Context, cctx *types.Cro } } } else { - err := k.validateFailedOutbound(ctx, cctx, oldStatus, "") + err := k.validateFailedOutbound(ctx, cctx, oldStatus, "", cctx.GetCurrentOutboundParam().Amount) if err != nil { return cosmoserrors.Wrap(err, "validateFailedOutbound") } @@ -137,6 +139,7 @@ func (k Keeper) validateFailedOutbound( cctx *types.CrossChainTx, oldStatus types.CctxStatus, revertMsg string, + inputAmount math.Uint, // TODO: find different way for this ) error { switch oldStatus { case types.CctxStatus_PendingOutbound: @@ -164,7 +167,7 @@ func (k Keeper) validateFailedOutbound( ctx, cctx.InboundParams.SenderChainId, cctx, - cctx.OutboundParams[0].Amount, + inputAmount, false, ) if err != nil { From 20c775e23f71ba17c31b1c89291a702a2d33fc1b Mon Sep 17 00:00:00 2001 From: skosito Date: Wed, 5 Jun 2024 18:29:37 +0200 Subject: [PATCH 12/38] fix unit tests --- x/crosschain/keeper/msg_server_vote_outbound_tx_test.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/x/crosschain/keeper/msg_server_vote_outbound_tx_test.go b/x/crosschain/keeper/msg_server_vote_outbound_tx_test.go index 427876d702..f91456bc75 100644 --- a/x/crosschain/keeper/msg_server_vote_outbound_tx_test.go +++ b/x/crosschain/keeper/msg_server_vote_outbound_tx_test.go @@ -203,6 +203,8 @@ func TestKeeper_VoteOutbound(t *testing.T) { // Successfully mock ProcessOutbound keepertest.MockGetRevertGasLimitForERC20(fungibleMock, asset, *senderChain, 100) keepertest.MockPayGasAndUpdateCCTX(fungibleMock, observerMock, ctx, *k, *senderChain, asset) + observerMock.On("GetSupportedChainFromChainID", mock.Anything, senderChain.ChainId). + Return(senderChain) _ = keepertest.MockUpdateNonce(observerMock, *senderChain) //Successfully mock SaveOutBound @@ -264,7 +266,8 @@ func TestKeeper_VoteOutbound(t *testing.T) { keepertest.MockPayGasAndUpdateCCTX(fungibleMock, observerMock, ctx, *k, *senderChain, asset) observerMock.On("GetChainNonces", mock.Anything, senderChain.ChainName.String()). Return(observertypes.ChainNonces{}, false) - + observerMock.On("GetSupportedChainFromChainID", mock.Anything, senderChain.ChainId). + Return(senderChain) //Successfully mock SaveOutBound keepertest.MockSaveOutBound(observerMock, ctx, cctx, tss) oldParamsLen := len(cctx.OutboundParams) @@ -328,6 +331,9 @@ func TestKeeper_VoteOutbound(t *testing.T) { fungibleMock.On("GetForeignCoinFromAsset", mock.Anything, mock.Anything, mock.Anything). Return(fungibletypes.ForeignCoins{}, false) + observerMock.On("GetSupportedChainFromChainID", mock.Anything, senderChain.ChainId). + Return(senderChain) + //Successfully mock SaveFailedOutbound keepertest.MockSaveOutBound(observerMock, ctx, cctx, tss) From 1f153e88ccbf9d546505dae1a060542b6774797c Mon Sep 17 00:00:00 2001 From: skosito Date: Wed, 5 Jun 2024 18:47:30 +0200 Subject: [PATCH 13/38] make generate --- x/crosschain/keeper/cctx_orchestrator.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/x/crosschain/keeper/cctx_orchestrator.go b/x/crosschain/keeper/cctx_orchestrator.go index 209f1bfdb2..d5f88b3c3c 100644 --- a/x/crosschain/keeper/cctx_orchestrator.go +++ b/x/crosschain/keeper/cctx_orchestrator.go @@ -33,7 +33,13 @@ func (k Keeper) ValidateOutboundZEVM(ctx sdk.Context, cctx *types.CrossChainTx) if err != nil && isContractReverted { tmpCtxRevert, commitRevert := ctx.CacheContext() // contract call reverted; should refund via a revert tx - err := k.validateFailedOutbound(tmpCtxRevert, cctx, types.CctxStatus_PendingOutbound, err.Error(), cctx.InboundParams.Amount) + err := k.validateFailedOutbound( + tmpCtxRevert, + cctx, + types.CctxStatus_PendingOutbound, + err.Error(), + cctx.InboundParams.Amount, + ) if err != nil { cctx.SetAbort(err.Error()) return types.CctxStatus_Aborted From 0b5f4bef9df27e96a51da12f823e98bc663e7c53 Mon Sep 17 00:00:00 2001 From: skosito Date: Thu, 6 Jun 2024 16:54:55 +0200 Subject: [PATCH 14/38] cleanup --- x/crosschain/keeper/cctx_gateway_zevm.go | 18 +----------------- x/crosschain/keeper/cctx_orchestrator.go | 18 +++++++++++++++++- ...bound_test.go => cctx_orchestrator_test.go} | 0 3 files changed, 18 insertions(+), 18 deletions(-) rename x/crosschain/keeper/{validate_outbound_test.go => cctx_orchestrator_test.go} (100%) diff --git a/x/crosschain/keeper/cctx_gateway_zevm.go b/x/crosschain/keeper/cctx_gateway_zevm.go index 7714aa2ca0..d89e5ffe50 100644 --- a/x/crosschain/keeper/cctx_gateway_zevm.go +++ b/x/crosschain/keeper/cctx_gateway_zevm.go @@ -18,23 +18,7 @@ func NewCCTXGatewayZEVM(crosschainKeeper Keeper) CCTXGatewayZEVM { } } -/* -InitiateOutbound handles evm deposit and call ValidateOutbound. -TODO (https://github.com/zeta-chain/node/issues/2278): move remaining of this comment to ValidateOutbound once it's added. - - - If the deposit is successful, the CCTX status is changed to OutboundMined. - - - If the deposit returns an internal error i.e if HandleEVMDeposit() returns an error, but isContractReverted is false, the CCTX status is changed to Aborted. - - - If the deposit is reverted, the function tries to create a revert cctx with status PendingRevert. - - - If the creation of revert tx also fails it changes the status to Aborted. - -Note : Aborted CCTXs are not refunded in this function. The refund is done using a separate refunding mechanism. -We do not return an error from this function , as all changes need to be persisted to the state. -Instead we use a temporary context to make changes and then commit the context on for the happy path ,i.e cctx is set to OutboundMined. -New CCTX status after preprocessing is returned. -*/ +// InitiateOutbound immediately calls ValidateOutbound because cctx can be validated without votes func (c CCTXGatewayZEVM) InitiateOutbound(ctx sdk.Context, cctx *types.CrossChainTx) (newCCTXStatus types.CctxStatus) { return c.crosschainKeeper.ValidateOutboundZEVM(ctx, cctx) } diff --git a/x/crosschain/keeper/cctx_orchestrator.go b/x/crosschain/keeper/cctx_orchestrator.go index d5f88b3c3c..adafe108dc 100644 --- a/x/crosschain/keeper/cctx_orchestrator.go +++ b/x/crosschain/keeper/cctx_orchestrator.go @@ -18,6 +18,22 @@ import ( observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) +/* +ValidateOutboundObservers processes the finalization of an outbound transaction if receiver is ZEVM + +- If the deposit is successful, the CCTX status is changed to OutboundMined. + +- If the deposit returns an internal error i.e if HandleEVMDeposit() returns an error, but isContractReverted is false, the CCTX status is changed to Aborted. + +- If the deposit is reverted, the function tries to create a revert cctx with status PendingRevert. + +- If the creation of revert tx also fails it changes the status to Aborted. + +Note : Aborted CCTXs are not refunded in this function. The refund is done using a separate refunding mechanism. +We do not return an error from this function , as all changes need to be persisted to the state. +Instead we use a temporary context to make changes and then commit the context on for the happy path ,i.e cctx is set to OutboundMined. +New CCTX status after preprocessing is returned. +*/ func (k Keeper) ValidateOutboundZEVM(ctx sdk.Context, cctx *types.CrossChainTx) (newCCTXStatus types.CctxStatus) { tmpCtx, commit := ctx.CacheContext() isContractReverted, err := k.HandleEVMDeposit(tmpCtx, cctx) @@ -203,7 +219,7 @@ func (k Keeper) validateFailedOutbound( // // 2. Set the finalization status of the current outbound tx to executed // -// 3. Emit an event for the successful outbound transaction +// 3. Emit an event for the successful outbound transaction if flag is provided // // This function sets CCTX status , in cases where the outbound tx is successful, but tx itself fails // This is done because SaveSuccessfulOutbound does not set the cctx status diff --git a/x/crosschain/keeper/validate_outbound_test.go b/x/crosschain/keeper/cctx_orchestrator_test.go similarity index 100% rename from x/crosschain/keeper/validate_outbound_test.go rename to x/crosschain/keeper/cctx_orchestrator_test.go From 939442532ff15194bf7c87721833a9161d5d0cae Mon Sep 17 00:00:00 2001 From: skosito Date: Thu, 6 Jun 2024 17:08:22 +0200 Subject: [PATCH 15/38] try to split initiate and validate outbound for zevm --- x/crosschain/keeper/cctx_gateway_zevm.go | 14 ++++++++++++-- x/crosschain/keeper/cctx_orchestrator.go | 18 ++++-------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/x/crosschain/keeper/cctx_gateway_zevm.go b/x/crosschain/keeper/cctx_gateway_zevm.go index d89e5ffe50..08af8f9a74 100644 --- a/x/crosschain/keeper/cctx_gateway_zevm.go +++ b/x/crosschain/keeper/cctx_gateway_zevm.go @@ -18,7 +18,17 @@ func NewCCTXGatewayZEVM(crosschainKeeper Keeper) CCTXGatewayZEVM { } } -// InitiateOutbound immediately calls ValidateOutbound because cctx can be validated without votes +// InitiateOutbound handles evm deposit and immediately validates pending outbound func (c CCTXGatewayZEVM) InitiateOutbound(ctx sdk.Context, cctx *types.CrossChainTx) (newCCTXStatus types.CctxStatus) { - return c.crosschainKeeper.ValidateOutboundZEVM(ctx, cctx) + tmpCtx, _ := ctx.CacheContext() + isContractReverted, err := c.crosschainKeeper.HandleEVMDeposit(tmpCtx, cctx) + + if err != nil && !isContractReverted { + // exceptional case; internal error; should abort CCTX + cctx.SetAbort(err.Error()) + return types.CctxStatus_Aborted + } + + cctx.SetPendingOutbound("") + return c.crosschainKeeper.ValidateOutboundZEVM(ctx, cctx, err, isContractReverted) } diff --git a/x/crosschain/keeper/cctx_orchestrator.go b/x/crosschain/keeper/cctx_orchestrator.go index adafe108dc..db059401c8 100644 --- a/x/crosschain/keeper/cctx_orchestrator.go +++ b/x/crosschain/keeper/cctx_orchestrator.go @@ -34,26 +34,16 @@ We do not return an error from this function , as all changes need to be persist Instead we use a temporary context to make changes and then commit the context on for the happy path ,i.e cctx is set to OutboundMined. New CCTX status after preprocessing is returned. */ -func (k Keeper) ValidateOutboundZEVM(ctx sdk.Context, cctx *types.CrossChainTx) (newCCTXStatus types.CctxStatus) { +func (k Keeper) ValidateOutboundZEVM(ctx sdk.Context, cctx *types.CrossChainTx, depositErr error, isContractReverted bool) (newCCTXStatus types.CctxStatus) { tmpCtx, commit := ctx.CacheContext() - isContractReverted, err := k.HandleEVMDeposit(tmpCtx, cctx) - - if err != nil && !isContractReverted { - // exceptional case; internal error; should abort CCTX - cctx.SetAbort(err.Error()) - return types.CctxStatus_Aborted - } - - cctx.SetPendingOutbound("") - - if err != nil && isContractReverted { + if depositErr != nil && isContractReverted { tmpCtxRevert, commitRevert := ctx.CacheContext() // contract call reverted; should refund via a revert tx err := k.validateFailedOutbound( tmpCtxRevert, cctx, types.CctxStatus_PendingOutbound, - err.Error(), + depositErr.Error(), cctx.InboundParams.Amount, ) if err != nil { @@ -64,8 +54,8 @@ func (k Keeper) ValidateOutboundZEVM(ctx sdk.Context, cctx *types.CrossChainTx) commitRevert() return types.CctxStatus_PendingRevert } + k.validateSuccessfulOutbound(tmpCtx, cctx, "", false) commit() - k.validateSuccessfulOutbound(ctx, cctx, "", false) return types.CctxStatus_OutboundMined } From 992c55bb3bc69c3f4c2eac658e9f891400b8c847 Mon Sep 17 00:00:00 2001 From: skosito Date: Thu, 6 Jun 2024 19:45:12 +0200 Subject: [PATCH 16/38] make generate --- changelog.md | 2 +- x/crosschain/keeper/cctx_orchestrator.go | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index 14690478df..09a30198ac 100644 --- a/changelog.md +++ b/changelog.md @@ -21,9 +21,9 @@ * [2279](https://github.com/zeta-chain/node/pull/2279) - add a CCTXGateway field to chain static data * [2275](https://github.com/zeta-chain/node/pull/2275) - add ChainInfo singleton state variable in authority * [2291](https://github.com/zeta-chain/node/pull/2291) - initialize cctx gateway interface +* [2317](https://github.com/zeta-chain/node/pull/2317) - add ValidateOutbound method for cctx orchestrator * [2289](https://github.com/zeta-chain/node/pull/2289) - add an authorization list to keep track of all authorizations on the chain * [2305](https://github.com/zeta-chain/node/pull/2305) - add new messages `MsgAddAuthorization` and `MsgRemoveAuthorization` that can be used to update the authorization list -* [2317](https://github.com/zeta-chain/node/pull/2317) - add ValidateOutbound method for cctx orchestrator * [2313](https://github.com/zeta-chain/node/pull/2313) - add `CheckAuthorization` function to replace the `IsAuthorized` function. The new function uses the authorization list to verify the signer's authorization. * [2312](https://github.com/zeta-chain/node/pull/2312) - add queries `ShowAuthorization` and `ListAuthorizations` diff --git a/x/crosschain/keeper/cctx_orchestrator.go b/x/crosschain/keeper/cctx_orchestrator.go index db059401c8..408f490927 100644 --- a/x/crosschain/keeper/cctx_orchestrator.go +++ b/x/crosschain/keeper/cctx_orchestrator.go @@ -34,7 +34,12 @@ We do not return an error from this function , as all changes need to be persist Instead we use a temporary context to make changes and then commit the context on for the happy path ,i.e cctx is set to OutboundMined. New CCTX status after preprocessing is returned. */ -func (k Keeper) ValidateOutboundZEVM(ctx sdk.Context, cctx *types.CrossChainTx, depositErr error, isContractReverted bool) (newCCTXStatus types.CctxStatus) { +func (k Keeper) ValidateOutboundZEVM( + ctx sdk.Context, + cctx *types.CrossChainTx, + depositErr error, + isContractReverted bool, +) (newCCTXStatus types.CctxStatus) { tmpCtx, commit := ctx.CacheContext() if depositErr != nil && isContractReverted { tmpCtxRevert, commitRevert := ctx.CacheContext() From bd125a82b7b4c7227ca0ea6267f0e60a41f297bd Mon Sep 17 00:00:00 2001 From: skosito Date: Thu, 6 Jun 2024 19:54:22 +0200 Subject: [PATCH 17/38] cleanup --- x/crosschain/keeper/cctx_orchestrator.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/x/crosschain/keeper/cctx_orchestrator.go b/x/crosschain/keeper/cctx_orchestrator.go index 408f490927..2e813de452 100644 --- a/x/crosschain/keeper/cctx_orchestrator.go +++ b/x/crosschain/keeper/cctx_orchestrator.go @@ -19,11 +19,12 @@ import ( ) /* -ValidateOutboundObservers processes the finalization of an outbound transaction if receiver is ZEVM +ValidateOutboundZEVM processes the finalization of an outbound transaction if receiver is ZEVM. +It takes deposit error and information if contract revert happened during deposit, to make a decision: -- If the deposit is successful, the CCTX status is changed to OutboundMined. +- If the deposit was successful, the CCTX status is changed to OutboundMined. -- If the deposit returns an internal error i.e if HandleEVMDeposit() returns an error, but isContractReverted is false, the CCTX status is changed to Aborted. +- If the deposit returned an internal error, but isContractReverted is false, the CCTX status is changed to Aborted. - If the deposit is reverted, the function tries to create a revert cctx with status PendingRevert. @@ -206,7 +207,7 @@ func (k Keeper) validateFailedOutbound( return nil } -// validateSuccessfulOutboundObservers processes a successful outbound transaction. It does the following things in one function: +// validateSuccessfulOutbound processes a successful outbound transaction. It does the following things in one function: // // 1. Change the status of the CCTX from // - PendingRevert to Reverted From 833398576cdcd8de19abab1cdc397169967ff8bd Mon Sep 17 00:00:00 2001 From: skosito Date: Thu, 6 Jun 2024 19:59:38 +0200 Subject: [PATCH 18/38] cleanup --- x/crosschain/keeper/cctx_orchestrator.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/x/crosschain/keeper/cctx_orchestrator.go b/x/crosschain/keeper/cctx_orchestrator.go index 2e813de452..e263251cb0 100644 --- a/x/crosschain/keeper/cctx_orchestrator.go +++ b/x/crosschain/keeper/cctx_orchestrator.go @@ -31,8 +31,8 @@ It takes deposit error and information if contract revert happened during deposi - If the creation of revert tx also fails it changes the status to Aborted. Note : Aborted CCTXs are not refunded in this function. The refund is done using a separate refunding mechanism. -We do not return an error from this function , as all changes need to be persisted to the state. -Instead we use a temporary context to make changes and then commit the context on for the happy path ,i.e cctx is set to OutboundMined. +We do not return an error from this function, as all changes need to be persisted to the state. +Instead we use a temporary context to make changes and then commit the context on for the happy path, i.e cctx is set to OutboundMined. New CCTX status after preprocessing is returned. */ func (k Keeper) ValidateOutboundZEVM( @@ -65,8 +65,8 @@ func (k Keeper) ValidateOutboundZEVM( return types.CctxStatus_OutboundMined } -// ValidateOutboundObservers processes the finalization of an outbound transaction based on the ballot status -// The state is committed only if the individual steps are successful +// ValidateOutboundObservers processes the finalization of an outbound transaction based on the ballot status. +// The state is committed only if the individual steps are successful. func (k Keeper) ValidateOutboundObservers( ctx sdk.Context, cctx *types.CrossChainTx, @@ -97,7 +97,7 @@ func (k Keeper) ValidateOutboundObservers( return nil } -// validateFailedOutboundObservers processes a failed outbound transaction. It does the following things in one function: +// validateFailedOutboundObservers processes a failed outbound transaction for observers. It does the following things in one function: // // 1. For Admin Tx or a withdrawal from Zeta chain, it aborts the CCTX // @@ -115,8 +115,8 @@ func (k Keeper) ValidateOutboundObservers( func (k Keeper) validateFailedOutboundObservers(ctx sdk.Context, cctx *types.CrossChainTx, valueReceived string) error { oldStatus := cctx.CctxStatus.Status // The following logic is used to handler the mentioned conditions separately. The reason being - // All admin tx is created using a policy message , there is no associated inbound tx , therefore we do not need any revert logic - // For transactions which originated from ZEVM , we can process the outbound in the same block as there is no TSS signing required for the revert + // All admin tx is created using a policy message, there is no associated inbound tx, therefore we do not need any revert logic + // For transactions which originated from ZEVM, we can process the outbound in the same block as there is no TSS signing required for the revert // For all other transactions we need to create a revert tx and set the status to pending revert if cctx.InboundParams.CoinType == coin.CoinType_Cmd { @@ -217,7 +217,7 @@ func (k Keeper) validateFailedOutbound( // // 3. Emit an event for the successful outbound transaction if flag is provided // -// This function sets CCTX status , in cases where the outbound tx is successful, but tx itself fails +// This function sets CCTX status, in cases where the outbound tx is successful, but tx itself fails // This is done because SaveSuccessfulOutbound does not set the cctx status // For cases where the outbound tx is unsuccessful, the cctx status is automatically set to Aborted in the validateFailedOutboundObservers function, so we can just return and error to trigger that func (k Keeper) validateSuccessfulOutbound( @@ -246,7 +246,7 @@ func (k Keeper) validateSuccessfulOutbound( func (k Keeper) validateFailedOutboundObserversForZEVM(ctx sdk.Context, cctx *types.CrossChainTx) error { indexBytes, err := cctx.GetCCTXIndexBytes() if err != nil { - // Return err to save the failed outbound ad set to aborted + // Return err to save the failed outbound and set to aborted return fmt.Errorf("failed reverting GetCCTXIndexBytes: %s", err.Error()) } // Finalize the older outbound tx From b497cbc5e1868dcfa6780c2a7c3a2a3246242560 Mon Sep 17 00:00:00 2001 From: skosito Date: Thu, 6 Jun 2024 21:22:53 +0200 Subject: [PATCH 19/38] e2e test fix --- x/crosschain/keeper/cctx_gateway_zevm.go | 9 +++++++-- x/crosschain/keeper/cctx_orchestrator.go | 4 +--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/x/crosschain/keeper/cctx_gateway_zevm.go b/x/crosschain/keeper/cctx_gateway_zevm.go index 08af8f9a74..5b61990215 100644 --- a/x/crosschain/keeper/cctx_gateway_zevm.go +++ b/x/crosschain/keeper/cctx_gateway_zevm.go @@ -20,7 +20,7 @@ func NewCCTXGatewayZEVM(crosschainKeeper Keeper) CCTXGatewayZEVM { // InitiateOutbound handles evm deposit and immediately validates pending outbound func (c CCTXGatewayZEVM) InitiateOutbound(ctx sdk.Context, cctx *types.CrossChainTx) (newCCTXStatus types.CctxStatus) { - tmpCtx, _ := ctx.CacheContext() + tmpCtx, commit := ctx.CacheContext() isContractReverted, err := c.crosschainKeeper.HandleEVMDeposit(tmpCtx, cctx) if err != nil && !isContractReverted { @@ -30,5 +30,10 @@ func (c CCTXGatewayZEVM) InitiateOutbound(ctx sdk.Context, cctx *types.CrossChai } cctx.SetPendingOutbound("") - return c.crosschainKeeper.ValidateOutboundZEVM(ctx, cctx, err, isContractReverted) + newCCTXStatus = c.crosschainKeeper.ValidateOutboundZEVM(ctx, cctx, err, isContractReverted) + if newCCTXStatus == types.CctxStatus_OutboundMined { + commit() + } + + return newCCTXStatus } diff --git a/x/crosschain/keeper/cctx_orchestrator.go b/x/crosschain/keeper/cctx_orchestrator.go index e263251cb0..bf207cb6a5 100644 --- a/x/crosschain/keeper/cctx_orchestrator.go +++ b/x/crosschain/keeper/cctx_orchestrator.go @@ -41,7 +41,6 @@ func (k Keeper) ValidateOutboundZEVM( depositErr error, isContractReverted bool, ) (newCCTXStatus types.CctxStatus) { - tmpCtx, commit := ctx.CacheContext() if depositErr != nil && isContractReverted { tmpCtxRevert, commitRevert := ctx.CacheContext() // contract call reverted; should refund via a revert tx @@ -60,8 +59,7 @@ func (k Keeper) ValidateOutboundZEVM( commitRevert() return types.CctxStatus_PendingRevert } - k.validateSuccessfulOutbound(tmpCtx, cctx, "", false) - commit() + k.validateSuccessfulOutbound(ctx, cctx, "", false) return types.CctxStatus_OutboundMined } From d437a394abf468a3fadae9568a9f2f18fe6f273e Mon Sep 17 00:00:00 2001 From: skosito Date: Fri, 7 Jun 2024 20:24:02 +0200 Subject: [PATCH 20/38] PR comment --- x/crosschain/keeper/cctx_orchestrator.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/x/crosschain/keeper/cctx_orchestrator.go b/x/crosschain/keeper/cctx_orchestrator.go index bf207cb6a5..01d2d7b325 100644 --- a/x/crosschain/keeper/cctx_orchestrator.go +++ b/x/crosschain/keeper/cctx_orchestrator.go @@ -139,7 +139,7 @@ func (k Keeper) validateFailedOutboundObservers(ctx sdk.Context, cctx *types.Cro } } } else { - err := k.validateFailedOutbound(ctx, cctx, oldStatus, "", cctx.GetCurrentOutboundParam().Amount) + err := k.validateFailedOutbound(ctx, cctx, oldStatus, "Outbound failed, start revert", cctx.GetCurrentOutboundParam().Amount) if err != nil { return cosmoserrors.Wrap(err, "validateFailedOutbound") } @@ -193,9 +193,6 @@ func (k Keeper) validateFailedOutbound( if err != nil { return err } - if revertMsg == "" { - revertMsg = "Outbound failed, start revert" - } // Not setting the finalization status here, the required changes have been made while creating the revert tx cctx.SetPendingRevert(revertMsg) case types.CctxStatus_PendingRevert: From c683384e57141b844e48e9f45446c59c334b1a99 Mon Sep 17 00:00:00 2001 From: skosito Date: Fri, 7 Jun 2024 20:29:42 +0200 Subject: [PATCH 21/38] fix typo after merge --- x/crosschain/keeper/cctx_orchestrator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/crosschain/keeper/cctx_orchestrator.go b/x/crosschain/keeper/cctx_orchestrator.go index 01d2d7b325..083eb62ccb 100644 --- a/x/crosschain/keeper/cctx_orchestrator.go +++ b/x/crosschain/keeper/cctx_orchestrator.go @@ -226,7 +226,7 @@ func (k Keeper) validateSuccessfulOutbound( case types.CctxStatus_PendingRevert: cctx.SetReverted("Outbound succeeded, revert executed") case types.CctxStatus_PendingOutbound: - cctx.SetOutBoundMined("Outbound succeeded, mined") + cctx.SetOutboundMined("Outbound succeeded, mined") default: return } From 16f858f09b9871a1ce0e8f76e6f529d8ae25b594 Mon Sep 17 00:00:00 2001 From: skosito Date: Mon, 10 Jun 2024 16:45:20 +0200 Subject: [PATCH 22/38] renaming --- ...ctx_orchestrator.go => cctx_orchestrator_validate_outbound.go} | 0 ...trator_test.go => cctx_orchestrator_validate_outbound_test.go} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename x/crosschain/keeper/{cctx_orchestrator.go => cctx_orchestrator_validate_outbound.go} (100%) rename x/crosschain/keeper/{cctx_orchestrator_test.go => cctx_orchestrator_validate_outbound_test.go} (100%) diff --git a/x/crosschain/keeper/cctx_orchestrator.go b/x/crosschain/keeper/cctx_orchestrator_validate_outbound.go similarity index 100% rename from x/crosschain/keeper/cctx_orchestrator.go rename to x/crosschain/keeper/cctx_orchestrator_validate_outbound.go diff --git a/x/crosschain/keeper/cctx_orchestrator_test.go b/x/crosschain/keeper/cctx_orchestrator_validate_outbound_test.go similarity index 100% rename from x/crosschain/keeper/cctx_orchestrator_test.go rename to x/crosschain/keeper/cctx_orchestrator_validate_outbound_test.go From 59c6d5f4bd3fed0026bf475a34cc41dcb3a6e512 Mon Sep 17 00:00:00 2001 From: skosito Date: Mon, 10 Jun 2024 16:56:43 +0200 Subject: [PATCH 23/38] move validate inbound for observers logic to separate method --- .../cctx_orchestrator_validate_inbound.go | 26 +++++++++++++++++++ .../keeper/msg_server_vote_inbound_tx.go | 17 +++--------- 2 files changed, 30 insertions(+), 13 deletions(-) create mode 100644 x/crosschain/keeper/cctx_orchestrator_validate_inbound.go diff --git a/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go b/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go new file mode 100644 index 0000000000..2663dfd574 --- /dev/null +++ b/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go @@ -0,0 +1,26 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +func (k Keeper) ValidateInboundObservers(ctx sdk.Context, msg *types.MsgVoteInbound) (*types.CrossChainTx, error) { + tss, tssFound := k.zetaObserverKeeper.GetTSS(ctx) + if !tssFound { + return nil, types.ErrCannotFindTSSKeys + } + // create a new CCTX from the inbound message.The status of the new CCTX is set to PendingInbound. + cctx, err := types.NewCCTX(ctx, *msg, tss.TssPubkey) + if err != nil { + return nil, err + } + // Initiate outbound, the process function manages the state commit and cctx status change. + // If the process fails, the changes to the evm state are rolled back. + _, err = k.InitiateOutbound(ctx, &cctx) + if err != nil { + return nil, err + } + + return &cctx, nil +} diff --git a/x/crosschain/keeper/msg_server_vote_inbound_tx.go b/x/crosschain/keeper/msg_server_vote_inbound_tx.go index b118d2bc43..6de4e1d562 100644 --- a/x/crosschain/keeper/msg_server_vote_inbound_tx.go +++ b/x/crosschain/keeper/msg_server_vote_inbound_tx.go @@ -97,23 +97,14 @@ func (k msgServer) VoteInbound( if !finalized { return &types.MsgVoteInboundResponse{}, nil } - tss, tssFound := k.zetaObserverKeeper.GetTSS(ctx) - if !tssFound { - return nil, types.ErrCannotFindTSSKeys - } - // create a new CCTX from the inbound message.The status of the new CCTX is set to PendingInbound. - cctx, err := types.NewCCTX(ctx, *msg, tss.TssPubkey) - if err != nil { - return nil, err - } - // Initiate outbound, the process function manages the state commit and cctx status change. - // If the process fails, the changes to the evm state are rolled back. - _, err = k.InitiateOutbound(ctx, &cctx) + + cctx, err := k.ValidateInboundObservers(ctx, msg) if err != nil { return nil, err } + // Save the inbound CCTX to the store. This is called irrespective of the status of the CCTX or the outcome of the process function. - k.SaveInbound(ctx, &cctx, msg.EventIndex) + k.SaveInbound(ctx, cctx, msg.EventIndex) return &types.MsgVoteInboundResponse{}, nil } From 36b38f8a8fa6fea0d4f0f0ff87ff4e08cf7d7975 Mon Sep 17 00:00:00 2001 From: skosito Date: Wed, 12 Jun 2024 04:21:11 +0200 Subject: [PATCH 24/38] Move cctx gateway outside of crosschain keeper wip --- app/app.go | 9 +- x/crosschain/keeper/cctx_gateway_observers.go | 37 +- x/crosschain/keeper/cctx_gateway_zevm.go | 10 +- x/crosschain/keeper/cctx_gateways.go | 31 + .../cctx_orchestrator_validate_inbound.go | 7 +- x/crosschain/keeper/evm_hooks.go | 67 +- x/crosschain/keeper/initiate_outbound.go | 23 +- x/crosschain/keeper/initiate_outbound_test.go | 1000 ++++++++--------- x/crosschain/keeper/keeper.go | 8 - .../keeper/msg_server_vote_inbound_tx.go | 2 +- 10 files changed, 611 insertions(+), 583 deletions(-) create mode 100644 x/crosschain/keeper/cctx_gateways.go diff --git a/app/app.go b/app/app.go index 243a0e7665..83a3c5b1bf 100644 --- a/app/app.go +++ b/app/app.go @@ -105,7 +105,6 @@ import ( "github.com/zeta-chain/zetacore/app/ante" "github.com/zeta-chain/zetacore/docs/openapi" - "github.com/zeta-chain/zetacore/pkg/chains" zetamempool "github.com/zeta-chain/zetacore/pkg/mempool" srvflags "github.com/zeta-chain/zetacore/server/flags" authoritymodule "github.com/zeta-chain/zetacore/x/authority" @@ -598,13 +597,7 @@ func New( app.LightclientKeeper, ) - // initializing map of cctx gateways so crosschain module can decide which one to use - // based on chain info of destination chain - cctxGateways := map[chains.CCTXGateway]crosschainkeeper.CCTXGateway{ - chains.CCTXGateway_observers: crosschainkeeper.NewCCTXGatewayObservers(app.CrosschainKeeper), - chains.CCTXGateway_zevm: crosschainkeeper.NewCCTXGatewayZEVM(app.CrosschainKeeper), - } - app.CrosschainKeeper.SetCCTXGateways(cctxGateways) + crosschainkeeper.InitCCTXGateways(app.CrosschainKeeper) // initialize ibccrosschain keeper and set it to the crosschain keeper // there is a circular dependency between the two keepers, crosschain keeper must be initialized first diff --git a/x/crosschain/keeper/cctx_gateway_observers.go b/x/crosschain/keeper/cctx_gateway_observers.go index 556d242214..9150536127 100644 --- a/x/crosschain/keeper/cctx_gateway_observers.go +++ b/x/crosschain/keeper/cctx_gateway_observers.go @@ -3,6 +3,7 @@ package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/x/crosschain/types" ) @@ -32,29 +33,37 @@ InitiateOutbound updates the store so observers can use the PendingCCTX query: */ func (c CCTXGatewayObservers) InitiateOutbound( ctx sdk.Context, - cctx *types.CrossChainTx, + config InitiateOutboundConfig, ) (newCCTXStatus types.CctxStatus) { tmpCtx, commit := ctx.CacheContext() - outboundReceiverChainID := cctx.GetCurrentOutboundParam().ReceiverChainId + outboundReceiverChainID := config.CCTX.GetCurrentOutboundParam().ReceiverChainId + // TODO: does this condition make sense? + noEthereumTxEvent := false + if chains.IsZetaChain(config.CCTX.InboundParams.SenderChainId) { + noEthereumTxEvent = true + } + err := func() error { - err := c.crosschainKeeper.PayGasAndUpdateCctx( - tmpCtx, - outboundReceiverChainID, - cctx, - cctx.InboundParams.Amount, - false, - ) - if err != nil { - return err + if config.PayGas { + err := c.crosschainKeeper.PayGasAndUpdateCctx( + tmpCtx, + outboundReceiverChainID, + config.CCTX, + config.CCTX.InboundParams.Amount, + noEthereumTxEvent, + ) + if err != nil { + return err + } } - return c.crosschainKeeper.UpdateNonce(tmpCtx, outboundReceiverChainID, cctx) + return c.crosschainKeeper.UpdateNonce(tmpCtx, outboundReceiverChainID, config.CCTX) }() if err != nil { // do not commit anything here as the CCTX should be aborted - cctx.SetAbort(err.Error()) + config.CCTX.SetAbort(err.Error()) return types.CctxStatus_Aborted } commit() - cctx.SetPendingOutbound("") + config.CCTX.SetPendingOutbound("") return types.CctxStatus_PendingOutbound } diff --git a/x/crosschain/keeper/cctx_gateway_zevm.go b/x/crosschain/keeper/cctx_gateway_zevm.go index 5b61990215..057e9a7fd0 100644 --- a/x/crosschain/keeper/cctx_gateway_zevm.go +++ b/x/crosschain/keeper/cctx_gateway_zevm.go @@ -19,18 +19,18 @@ func NewCCTXGatewayZEVM(crosschainKeeper Keeper) CCTXGatewayZEVM { } // InitiateOutbound handles evm deposit and immediately validates pending outbound -func (c CCTXGatewayZEVM) InitiateOutbound(ctx sdk.Context, cctx *types.CrossChainTx) (newCCTXStatus types.CctxStatus) { +func (c CCTXGatewayZEVM) InitiateOutbound(ctx sdk.Context, config InitiateOutboundConfig) (newCCTXStatus types.CctxStatus) { tmpCtx, commit := ctx.CacheContext() - isContractReverted, err := c.crosschainKeeper.HandleEVMDeposit(tmpCtx, cctx) + isContractReverted, err := c.crosschainKeeper.HandleEVMDeposit(tmpCtx, config.CCTX) if err != nil && !isContractReverted { // exceptional case; internal error; should abort CCTX - cctx.SetAbort(err.Error()) + config.CCTX.SetAbort(err.Error()) return types.CctxStatus_Aborted } - cctx.SetPendingOutbound("") - newCCTXStatus = c.crosschainKeeper.ValidateOutboundZEVM(ctx, cctx, err, isContractReverted) + config.CCTX.SetPendingOutbound("") + newCCTXStatus = c.crosschainKeeper.ValidateOutboundZEVM(ctx, config.CCTX, err, isContractReverted) if newCCTXStatus == types.CctxStatus_OutboundMined { commit() } diff --git a/x/crosschain/keeper/cctx_gateways.go b/x/crosschain/keeper/cctx_gateways.go new file mode 100644 index 0000000000..25259b1281 --- /dev/null +++ b/x/crosschain/keeper/cctx_gateways.go @@ -0,0 +1,31 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/zeta-chain/zetacore/pkg/chains" + "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +// CCTXGateway is interface implemented by every gateway. It is one of interfaces used for communication +// between CCTX gateways and crosschain module, and it is called by crosschain module. +type CCTXGateway interface { + // Initiate a new outbound, this tells the CCTXGateway to carry out the action to execute the outbound. + // It is the only entry point to initiate an outbound and it returns new CCTX status after it is completed. + InitiateOutbound(ctx sdk.Context, config InitiateOutboundConfig) (newCCTXStatus types.CctxStatus) +} + +var cctxGateways map[chains.CCTXGateway]CCTXGateway + +// initializing map of cctx gateways so crosschain module can decide which one to use +// based on chain info of destination chain +func InitCCTXGateways(keeper Keeper) { + cctxGateways = map[chains.CCTXGateway]CCTXGateway{ + chains.CCTXGateway_observers: NewCCTXGatewayObservers(keeper), + chains.CCTXGateway_zevm: NewCCTXGatewayZEVM(keeper), + } +} + +func ResolveCCTXGateway(c chains.CCTXGateway) (CCTXGateway, bool) { + cctxGateway, ok := cctxGateways[c] + return cctxGateway, ok +} diff --git a/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go b/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go index 2663dfd574..8ac0ac5b79 100644 --- a/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go +++ b/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go @@ -5,7 +5,7 @@ import ( "github.com/zeta-chain/zetacore/x/crosschain/types" ) -func (k Keeper) ValidateInboundObservers(ctx sdk.Context, msg *types.MsgVoteInbound) (*types.CrossChainTx, error) { +func (k Keeper) ValidateInboundObservers(ctx sdk.Context, msg *types.MsgVoteInbound, payGas bool) (*types.CrossChainTx, error) { tss, tssFound := k.zetaObserverKeeper.GetTSS(ctx) if !tssFound { return nil, types.ErrCannotFindTSSKeys @@ -17,7 +17,10 @@ func (k Keeper) ValidateInboundObservers(ctx sdk.Context, msg *types.MsgVoteInbo } // Initiate outbound, the process function manages the state commit and cctx status change. // If the process fails, the changes to the evm state are rolled back. - _, err = k.InitiateOutbound(ctx, &cctx) + _, err = k.InitiateOutbound(ctx, InitiateOutboundConfig{ + CCTX: &cctx, + PayGas: payGas, + }) if err != nil { return nil, err } diff --git a/x/crosschain/keeper/evm_hooks.go b/x/crosschain/keeper/evm_hooks.go index 863bdd991b..37e7a931a8 100644 --- a/x/crosschain/keeper/evm_hooks.go +++ b/x/crosschain/keeper/evm_hooks.go @@ -3,6 +3,7 @@ package keeper import ( "encoding/base64" "encoding/hex" + "errors" "fmt" "math/big" @@ -76,6 +77,18 @@ func (k Keeper) ProcessLogs( if connectorZEVMAddr == (ethcommon.Address{}) { return fmt.Errorf("connectorZEVM address is empty") } + + // These cannot be processed without TSS keys, return an error if TSS is not found + tss, found := k.zetaObserverKeeper.GetTSS(ctx) + if !found { + return errorsmod.Wrap(types.ErrCannotFindTSSKeys, "Cannot process logs without TSS keys") + } + + // Do not process withdrawal events if inbound is disabled + if !k.zetaObserverKeeper.IsInboundEnabled(ctx) { + return observertypes.ErrInboundDisabled + } + for _, log := range logs { eventZrc20Withdrawal, errZrc20 := ParseZRC20WithdrawalEvent(*log) eventZetaSent, errZetaSent := ParseZetaSentEvent(*log, connectorZEVMAddr) @@ -90,18 +103,6 @@ func (k Keeper) ProcessLogs( continue } - // We have found either eventZrc20Withdrawal or eventZetaSent - // These cannot be processed without TSS keys, return an error if TSS is not found - tss, found := k.zetaObserverKeeper.GetTSS(ctx) - if !found { - return errorsmod.Wrap(types.ErrCannotFindTSSKeys, "Cannot process logs without TSS keys") - } - - // Do not process withdrawal events if inbound is disabled - if !k.zetaObserverKeeper.IsInboundEnabled(ctx) { - return observertypes.ErrInboundDisabled - } - // if eventZrc20Withdrawal is not nil we will try to validate it and see if it can be processed if eventZrc20Withdrawal != nil { // Check if the contract is a registered ZRC20 contract. If its not a registered ZRC20 contract, we can discard this event as it is not relevant @@ -188,13 +189,15 @@ func (k Keeper) ProcessZRC20WithdrawalEvent( event.Raw.Index, ) - // Create a new cctx with status as pending Inbound, this is created directly from the event without waiting for any observer votes - cctx, err := types.NewCCTX(ctx, *msg, tss.TssPubkey) + cctx, err := k.ValidateInboundObservers(ctx, msg, false) if err != nil { - return fmt.Errorf("ProcessZRC20WithdrawalEvent: failed to initialize cctx: %s", err.Error()) + return err } - cctx.SetPendingOutbound("ZRC20 withdrawal event setting to pending outbound directly") - // Get gas price and amount + + if cctx.CctxStatus.Status == types.CctxStatus_Aborted { + return errors.New("cctx aborted") + } + gasprice, found := k.GetGasPrice(ctx, receiverChain.ChainId) if !found { return fmt.Errorf("gasprice not found for %s", receiverChain) @@ -202,8 +205,9 @@ func (k Keeper) ProcessZRC20WithdrawalEvent( cctx.GetCurrentOutboundParam().GasPrice = fmt.Sprintf("%d", gasprice.Prices[gasprice.MedianIndex]) cctx.GetCurrentOutboundParam().Amount = cctx.InboundParams.Amount - EmitZRCWithdrawCreated(ctx, cctx) - return k.ProcessCCTX(ctx, cctx, receiverChain) + EmitZRCWithdrawCreated(ctx, *cctx) + + return k.ProcessCCTX(ctx, *cctx, receiverChain) } func (k Keeper) ProcessZetaSentEvent( @@ -267,26 +271,17 @@ func (k Keeper) ProcessZetaSentEvent( event.Raw.Index, ) - // create a new cctx with status as pending Inbound, - // this is created directly from the event without waiting for any observer votes - cctx, err := types.NewCCTX(ctx, *msg, tss.TssPubkey) + cctx, err := k.ValidateInboundObservers(ctx, msg, true) if err != nil { - return fmt.Errorf("ProcessZetaSentEvent: failed to initialize cctx: %s", err.Error()) + return err } - cctx.SetPendingOutbound("ZetaSent event setting to pending outbound directly") - if err := k.PayGasAndUpdateCctx( - ctx, - receiverChain.ChainId, - &cctx, - amount, - true, - ); err != nil { - return fmt.Errorf("ProcessWithdrawalEvent: pay gas failed: %s", err.Error()) + if cctx.CctxStatus.Status == types.CctxStatus_Aborted { + return errors.New("cctx aborted") } - EmitZetaWithdrawCreated(ctx, cctx) - return k.ProcessCCTX(ctx, cctx, receiverChain) + EmitZetaWithdrawCreated(ctx, *cctx) + return k.ProcessCCTX(ctx, *cctx, receiverChain) } func (k Keeper) ProcessCCTX(ctx sdk.Context, cctx types.CrossChainTx, receiverChain *chains.Chain) error { @@ -295,10 +290,6 @@ func (k Keeper) ProcessCCTX(ctx sdk.Context, cctx types.CrossChainTx, receiverCh cctx.InboundParams.ObservedHash = inCctxIndex } - if err := k.UpdateNonce(ctx, receiverChain.ChainId, &cctx); err != nil { - return fmt.Errorf("ProcessWithdrawalEvent: update nonce failed: %s", err.Error()) - } - k.SetCctxAndNonceToCctxAndInboundHashToCctx(ctx, cctx) ctx.Logger().Debug("ProcessCCTX successful \n") return nil diff --git a/x/crosschain/keeper/initiate_outbound.go b/x/crosschain/keeper/initiate_outbound.go index 8174f6ec4e..b9063ceadd 100644 --- a/x/crosschain/keeper/initiate_outbound.go +++ b/x/crosschain/keeper/initiate_outbound.go @@ -10,14 +10,23 @@ import ( "github.com/zeta-chain/zetacore/x/crosschain/types" ) +// TODO: this is just a tmp solution, tbd if info can be passed to CCTX constructor somehow +// and not initialize CCTX using MsgVoteInbound but for example (InboundParams, OutboundParams) +// then PayGas can be decided based on GasPrice already presend in OutboundParams +// check if msg.Digest can be replaced to calculate index +type InitiateOutboundConfig struct { + CCTX *types.CrossChainTx + PayGas bool +} + // InitiateOutbound initiates the outbound for the CCTX depending on the CCTX gateway. // It does a conditional dispatch to correct CCTX gateway based on the receiver chain // which handles the state changes and error handling. -func (k Keeper) InitiateOutbound(ctx sdk.Context, cctx *types.CrossChainTx) (types.CctxStatus, error) { - receiverChainID := cctx.GetCurrentOutboundParam().ReceiverChainId +func (k Keeper) InitiateOutbound(ctx sdk.Context, config InitiateOutboundConfig) (types.CctxStatus, error) { + receiverChainID := config.CCTX.GetCurrentOutboundParam().ReceiverChainId chainInfo := chains.GetChainFromChainID(receiverChainID) if chainInfo == nil { - return cctx.CctxStatus.Status, cosmoserrors.Wrap( + return config.CCTX.CctxStatus.Status, cosmoserrors.Wrap( types.ErrInitiatitingOutbound, fmt.Sprintf( "chain info not found for %d", receiverChainID, @@ -25,9 +34,9 @@ func (k Keeper) InitiateOutbound(ctx sdk.Context, cctx *types.CrossChainTx) (typ ) } - cctxGateway, ok := k.cctxGateways[chainInfo.CctxGateway] - if !ok { - return cctx.CctxStatus.Status, cosmoserrors.Wrap( + cctxGateway, found := ResolveCCTXGateway(chainInfo.CctxGateway) + if !found { + return config.CCTX.CctxStatus.Status, cosmoserrors.Wrap( types.ErrInitiatitingOutbound, fmt.Sprintf( "CCTXGateway not defined for receiver chain %d", receiverChainID, @@ -35,5 +44,5 @@ func (k Keeper) InitiateOutbound(ctx sdk.Context, cctx *types.CrossChainTx) (typ ) } - return cctxGateway.InitiateOutbound(ctx, cctx), nil + return cctxGateway.InitiateOutbound(ctx, config), nil } diff --git a/x/crosschain/keeper/initiate_outbound_test.go b/x/crosschain/keeper/initiate_outbound_test.go index b01cc47a5f..8441d389da 100644 --- a/x/crosschain/keeper/initiate_outbound_test.go +++ b/x/crosschain/keeper/initiate_outbound_test.go @@ -1,502 +1,502 @@ package keeper_test -import ( - "fmt" - "math/big" - "testing" - - sdkmath "cosmossdk.io/math" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - "github.com/zeta-chain/zetacore/pkg/chains" - "github.com/zeta-chain/zetacore/pkg/coin" - keepertest "github.com/zeta-chain/zetacore/testutil/keeper" - "github.com/zeta-chain/zetacore/testutil/sample" - "github.com/zeta-chain/zetacore/x/crosschain/keeper" - "github.com/zeta-chain/zetacore/x/crosschain/types" - fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" - observertypes "github.com/zeta-chain/zetacore/x/observer/types" -) - -func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) { - t.Run("process zevm deposit successfully", func(t *testing.T) { - k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ - UseFungibleMock: true, - }) - - // Setup mock data - fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) - receiver := sample.EthAddress() - amount := big.NewInt(42) - - // expect DepositCoinZeta to be called - fungibleMock.On("ZETADepositAndCallContract", mock.Anything, - mock.Anything, - receiver, int64(0), amount, mock.Anything, mock.Anything).Return(nil, nil) - - // call InitiateOutbound - cctx := sample.CrossChainTx(t, "test") - cctx.CctxStatus = &types.Status{Status: types.CctxStatus_PendingInbound} - cctx.GetCurrentOutboundParam().Receiver = receiver.String() - cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId - cctx.GetInboundParams().Amount = sdkmath.NewUintFromBigInt(amount) - cctx.InboundParams.CoinType = coin.CoinType_Zeta - cctx.GetInboundParams().SenderChainId = 0 - newStatus, err := k.InitiateOutbound(ctx, cctx) - require.NoError(t, err) - require.Equal(t, types.CctxStatus_OutboundMined, cctx.CctxStatus.Status) - require.Equal(t, types.CctxStatus_OutboundMined, newStatus) - }) - - t.Run("unable to process zevm deposit HandleEVMDeposit returns err without reverting", func(t *testing.T) { - k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ - UseFungibleMock: true, - }) - - // Setup mock data - fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) - receiver := sample.EthAddress() - amount := big.NewInt(42) - - // mock unsuccessful HandleEVMDeposit which does not revert - - fungibleMock.On("ZETADepositAndCallContract", mock.Anything, mock.Anything, receiver, int64(0), amount, mock.Anything, mock.Anything). - Return(nil, fmt.Errorf("deposit error")) - - // call InitiateOutbound - cctx := sample.CrossChainTx(t, "test") - cctx.CctxStatus = &types.Status{Status: types.CctxStatus_PendingInbound} - cctx.GetCurrentOutboundParam().Receiver = receiver.String() - cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId - cctx.GetInboundParams().Amount = sdkmath.NewUintFromBigInt(amount) - cctx.InboundParams.CoinType = coin.CoinType_Zeta - cctx.GetInboundParams().SenderChainId = 0 - newStatus, err := k.InitiateOutbound(ctx, cctx) - require.NoError(t, err) - require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) - require.Equal(t, types.CctxStatus_Aborted, newStatus) - require.Equal(t, "deposit error", cctx.CctxStatus.StatusMessage) - }) - - t.Run( - "unable to process zevm deposit HandleEVMDeposit reverts fails at GetSupportedChainFromChainID", - func(t *testing.T) { - k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ - UseFungibleMock: true, - UseObserverMock: true, - }) - - // Setup mock data - fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) - observerMock := keepertest.GetCrosschainObserverMock(t, k) - receiver := sample.EthAddress() - amount := big.NewInt(42) - senderChain := getValidEthChain() - errDeposit := fmt.Errorf("deposit failed") - - // Setup expected calls - // mock unsuccessful HandleEVMDeposit which reverts , i.e returns err and isContractReverted = true - keepertest.MockRevertForHandleEVMDeposit(fungibleMock, receiver, amount, senderChain.ChainId, errDeposit) - - // mock unsuccessful GetSupportedChainFromChainID - observerMock.On("GetSupportedChainFromChainID", mock.Anything, senderChain.ChainId). - Return(nil) - - // call InitiateOutbound - cctx := GetERC20Cctx(t, receiver, *senderChain, "", amount) - cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId - newStatus, err := k.InitiateOutbound(ctx, cctx) - require.NoError(t, err) - require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) - require.Equal(t, types.CctxStatus_Aborted, newStatus) - require.Equal( - t, - fmt.Sprintf("chain not supported"), - cctx.CctxStatus.StatusMessage, - ) - }, - ) - - t.Run("unable to process zevm deposit HandleEVMDeposit revert fails at GetRevertGasLimit", func(t *testing.T) { - k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ - UseFungibleMock: true, - UseObserverMock: true, - }) - - // Setup mock data - fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) - observerMock := keepertest.GetCrosschainObserverMock(t, k) - receiver := sample.EthAddress() - amount := big.NewInt(42) - senderChain := getValidEthChain() - asset := "" - errDeposit := fmt.Errorf("deposit failed") - - // Setup expected calls - keepertest.MockRevertForHandleEVMDeposit(fungibleMock, receiver, amount, senderChain.ChainId, errDeposit) - - // Mock successful GetSupportedChainFromChainID - keepertest.MockGetSupportedChainFromChainID(observerMock, senderChain) - - // mock unsuccessful GetRevertGasLimit for ERC20 - fungibleMock.On("GetForeignCoinFromAsset", mock.Anything, asset, senderChain.ChainId). - Return(fungibletypes.ForeignCoins{}, false) - - // call InitiateOutbound - cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) - cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId - newStatus, err := k.InitiateOutbound(ctx, cctx) - require.NoError(t, err) - require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) - require.Equal(t, types.CctxStatus_Aborted, newStatus) - require.Equal( - t, - "GetRevertGasLimit: foreign coin not found for sender chain", - cctx.CctxStatus.StatusMessage, - ) - }) - - t.Run("unable to process zevm deposit HandleEVMDeposit revert fails at PayGasInERC20AndUpdateCctx", - func(t *testing.T) { - k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ - UseFungibleMock: true, - UseObserverMock: true, - }) - - // Setup mock data - fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) - receiver := sample.EthAddress() - amount := big.NewInt(42) - senderChain := getValidEthChain() - asset := "" - - // Setup expected calls - errDeposit := fmt.Errorf("deposit failed") - keepertest.MockRevertForHandleEVMDeposit(fungibleMock, receiver, amount, senderChain.ChainId, errDeposit) - - observerMock := keepertest.GetCrosschainObserverMock(t, k) - - // Mock successful GetSupportedChainFromChainID - keepertest.MockGetSupportedChainFromChainID(observerMock, senderChain) - - // mock successful GetRevertGasLimit for ERC20 - keepertest.MockGetRevertGasLimitForERC20(fungibleMock, asset, *senderChain, 100) - - // mock unsuccessful PayGasInERC20AndUpdateCctx - observerMock.On("GetSupportedChainFromChainID", mock.Anything, senderChain.ChainId). - Return(nil).Once() - - // call InitiateOutbound - cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) - cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId - newStatus, err := k.InitiateOutbound(ctx, cctx) - require.NoError(t, err) - require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) - require.Equal(t, types.CctxStatus_Aborted, newStatus) - require.Equal( - t, - "chain not supported", - cctx.CctxStatus.StatusMessage, - ) - }, - ) - - t.Run( - "uunable to process zevm deposit HandleEVMDeposit revert fails at PayGasInERC20AndUpdateCctx with gas limit is 0", - func(t *testing.T) { - k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ - UseFungibleMock: true, - UseObserverMock: true, - }) - - // Setup mock data - fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) - receiver := sample.EthAddress() - amount := big.NewInt(42) - senderChain := getValidEthChain() - asset := "" - - // Setup expected calls - errDeposit := fmt.Errorf("deposit failed") - keepertest.MockRevertForHandleEVMDeposit(fungibleMock, receiver, amount, senderChain.ChainId, errDeposit) - - observerMock := keepertest.GetCrosschainObserverMock(t, k) - - // Mock successful GetSupportedChainFromChainID - keepertest.MockGetSupportedChainFromChainID(observerMock, senderChain) - - // mock successful GetRevertGasLimit for ERC20 - keepertest.MockGetRevertGasLimitForERC20(fungibleMock, asset, *senderChain, 0) - - // mock unsuccessful PayGasInERC20AndUpdateCctx - observerMock.On("GetSupportedChainFromChainID", mock.Anything, senderChain.ChainId). - Return(nil).Once() - - // call InitiateOutbound - cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) - cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId - newStatus, err := k.InitiateOutbound(ctx, cctx) - require.NoError(t, err) - require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) - require.Equal(t, types.CctxStatus_Aborted, newStatus) - require.Equal( - t, - fmt.Sprintf("chain not supported"), - cctx.CctxStatus.StatusMessage, - ) - }, - ) - - t.Run("unable to process zevm deposit HandleEVMDeposit reverts fails at UpdateNonce", func(t *testing.T) { - k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ - UseFungibleMock: true, - UseObserverMock: true, - }) - - // Setup mock data - fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) - observerMock := keepertest.GetCrosschainObserverMock(t, k) - receiver := sample.EthAddress() - amount := big.NewInt(42) - senderChain := getValidEthChain() - asset := "" - errDeposit := fmt.Errorf("deposit failed") - - // Setup expected calls - // mock unsuccessful HandleEVMDeposit which reverts , i.e returns err and isContractReverted = true - keepertest.MockRevertForHandleEVMDeposit(fungibleMock, receiver, amount, senderChain.ChainId, errDeposit) - - // Mock successful GetSupportedChainFromChainID - keepertest.MockGetSupportedChainFromChainID(observerMock, senderChain) - - // mock successful GetRevertGasLimit for ERC20 - keepertest.MockGetRevertGasLimitForERC20(fungibleMock, asset, *senderChain, 100) - - // mock successful PayGasAndUpdateCctx - keepertest.MockPayGasAndUpdateCCTX(fungibleMock, observerMock, ctx, *k, *senderChain, asset) - - // Mock unsuccessful UpdateNonce - observerMock.On("GetChainNonces", mock.Anything, senderChain.ChainName.String()). - Return(observertypes.ChainNonces{}, false) - - // call InitiateOutbound - cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) - cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId - newStatus, err := k.InitiateOutbound(ctx, cctx) - require.NoError(t, err) - require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) - require.Equal(t, types.CctxStatus_Aborted, newStatus) - require.Contains(t, cctx.CctxStatus.StatusMessage, "cannot find receiver chain nonce") - }) - - t.Run("unable to process zevm deposit HandleEVMDeposit revert successfully", func(t *testing.T) { - k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ - UseFungibleMock: true, - UseObserverMock: true, - }) - - // Setup mock data - fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) - observerMock := keepertest.GetCrosschainObserverMock(t, k) - receiver := sample.EthAddress() - amount := big.NewInt(42) - senderChain := getValidEthChain() - asset := "" - errDeposit := fmt.Errorf("deposit failed") - - // Setup expected calls - // mock unsuccessful HandleEVMDeposit which reverts , i.e returns err and isContractReverted = true - keepertest.MockRevertForHandleEVMDeposit(fungibleMock, receiver, amount, senderChain.ChainId, errDeposit) - - // Mock successful GetSupportedChainFromChainID - keepertest.MockGetSupportedChainFromChainID(observerMock, senderChain) - - // mock successful GetRevertGasLimit for ERC20 - keepertest.MockGetRevertGasLimitForERC20(fungibleMock, asset, *senderChain, 100) - - // mock successful PayGasAndUpdateCctx - keepertest.MockPayGasAndUpdateCCTX(fungibleMock, observerMock, ctx, *k, *senderChain, asset) - // mock successful UpdateNonce - updatedNonce := keepertest.MockUpdateNonce(observerMock, *senderChain) - - // call InitiateOutbound - cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) - cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId - newStatus, err := k.InitiateOutbound(ctx, cctx) - require.NoError(t, err) - require.Equal(t, types.CctxStatus_PendingRevert, cctx.CctxStatus.Status) - require.Equal(t, types.CctxStatus_PendingRevert, newStatus) - require.Equal(t, errDeposit.Error(), cctx.CctxStatus.StatusMessage) - require.Equal(t, updatedNonce, cctx.GetCurrentOutboundParam().TssNonce) - }) - - t.Run("unable to process zevm deposit HandleEVMDeposit revert fails as the cctx has already been reverted", - func(t *testing.T) { - k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ - UseFungibleMock: true, - UseObserverMock: true, - }) - - // Setup mock data - fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) - observerMock := keepertest.GetCrosschainObserverMock(t, k) - receiver := sample.EthAddress() - amount := big.NewInt(42) - senderChain := getValidEthChain() - asset := "" - errDeposit := fmt.Errorf("deposit failed") - - // Setup expected calls - // mock unsuccessful HandleEVMDeposit which reverts , i.e returns err and isContractReverted = true - keepertest.MockRevertForHandleEVMDeposit(fungibleMock, receiver, amount, senderChain.ChainId, errDeposit) - - // Mock successful GetSupportedChainFromChainID - keepertest.MockGetSupportedChainFromChainID(observerMock, senderChain) - - // mock successful GetRevertGasLimit for ERC20 - keepertest.MockGetRevertGasLimitForERC20(fungibleMock, asset, *senderChain, 100) - - // call InitiateOutbound - cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) - cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId - cctx.OutboundParams = append(cctx.OutboundParams, cctx.GetCurrentOutboundParam()) - newStatus, err := k.InitiateOutbound(ctx, cctx) - require.NoError(t, err) - require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) - require.Equal(t, types.CctxStatus_Aborted, newStatus) - require.Contains( - t, - cctx.CctxStatus.StatusMessage, - fmt.Sprintf("cannot revert a revert tx"), - ) - }, - ) -} - -func TestKeeper_InitiateOutboundProcessCrosschainMsgPassing(t *testing.T) { - t.Run("process crosschain msg passing successfully", func(t *testing.T) { - k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ - UseFungibleMock: true, - UseObserverMock: true, - }) - - // Setup mock data - fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) - observerMock := keepertest.GetCrosschainObserverMock(t, k) - receiver := sample.EthAddress() - amount := big.NewInt(42) - receiverChain := getValidEthChain() - - // mock successful PayGasAndUpdateCctx - keepertest.MockPayGasAndUpdateCCTX(fungibleMock, observerMock, ctx, *k, *receiverChain, "") - - // mock successful UpdateNonce - updatedNonce := keepertest.MockUpdateNonce(observerMock, *receiverChain) - - // call InitiateOutbound - cctx := GetERC20Cctx(t, receiver, *receiverChain, "", amount) - newStatus, err := k.InitiateOutbound(ctx, cctx) - require.NoError(t, err) - require.Equal(t, types.CctxStatus_PendingOutbound, cctx.CctxStatus.Status) - require.Equal(t, types.CctxStatus_PendingOutbound, newStatus) - require.Equal(t, updatedNonce, cctx.GetCurrentOutboundParam().TssNonce) - }) - - t.Run("unable to process crosschain msg passing PayGasAndUpdateCctx fails", func(t *testing.T) { - k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ - UseFungibleMock: true, - UseObserverMock: true, - }) - - // Setup mock data - observerMock := keepertest.GetCrosschainObserverMock(t, k) - receiver := sample.EthAddress() - amount := big.NewInt(42) - receiverChain := getValidEthChain() - - // mock unsuccessful PayGasAndUpdateCctx - observerMock.On("GetSupportedChainFromChainID", mock.Anything, receiverChain.ChainId). - Return(nil).Once() - - // call InitiateOutbound - cctx := GetERC20Cctx(t, receiver, *receiverChain, "", amount) - newStatus, err := k.InitiateOutbound(ctx, cctx) - require.NoError(t, err) - require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) - require.Equal(t, types.CctxStatus_Aborted, newStatus) - require.Equal(t, observertypes.ErrSupportedChains.Error(), cctx.CctxStatus.StatusMessage) - }) - - t.Run("unable to process crosschain msg passing UpdateNonce fails", func(t *testing.T) { - k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ - UseFungibleMock: true, - UseObserverMock: true, - }) - - // Setup mock data - fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) - observerMock := keepertest.GetCrosschainObserverMock(t, k) - receiver := sample.EthAddress() - amount := big.NewInt(42) - receiverChain := getValidEthChain() - - // mock successful PayGasAndUpdateCctx - keepertest.MockPayGasAndUpdateCCTX(fungibleMock, observerMock, ctx, *k, *receiverChain, "") - - // mock unsuccessful UpdateNonce - observerMock.On("GetChainNonces", mock.Anything, receiverChain.ChainName.String()). - Return(observertypes.ChainNonces{}, false) - - // call InitiateOutbound - cctx := GetERC20Cctx(t, receiver, *receiverChain, "", amount) - newStatus, err := k.InitiateOutbound(ctx, cctx) - require.NoError(t, err) - require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) - require.Equal(t, types.CctxStatus_Aborted, newStatus) - require.Contains(t, cctx.CctxStatus.StatusMessage, "cannot find receiver chain nonce") - }) -} - -func TestKeeper_InitiateOutboundFailures(t *testing.T) { - t.Run("should fail if chain info can not be found for receiver chain id", func(t *testing.T) { - k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ - UseFungibleMock: true, - UseObserverMock: true, - }) - - // Setup mock data - receiver := sample.EthAddress() - amount := big.NewInt(42) - receiverChain := getValidEthChain() - receiverChain.ChainId = 123 - // call InitiateOutbound - cctx := GetERC20Cctx(t, receiver, *receiverChain, "", amount) - newStatus, err := k.InitiateOutbound(ctx, cctx) - require.Error(t, err) - require.Equal(t, types.CctxStatus_PendingInbound, newStatus) - require.ErrorContains(t, err, "chain info not found") - }) - - t.Run("should fail if cctx gateway not found for receiver chain id", func(t *testing.T) { - k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ - UseFungibleMock: true, - UseObserverMock: true, - }) - - // reset cctx gateways - k.SetCCTXGateways(map[chains.CCTXGateway]keeper.CCTXGateway{}) - - // Setup mock data - receiver := sample.EthAddress() - amount := big.NewInt(42) - receiverChain := getValidEthChain() - // call InitiateOutbound - cctx := GetERC20Cctx(t, receiver, *receiverChain, "", amount) - newStatus, err := k.InitiateOutbound(ctx, cctx) - require.Equal(t, types.CctxStatus_PendingInbound, newStatus) - require.NotNil(t, err) - require.ErrorContains(t, err, "CCTXGateway not defined for receiver chain") - }) - -} +// import ( +// "fmt" +// "math/big" +// "testing" + +// sdkmath "cosmossdk.io/math" +// "github.com/stretchr/testify/mock" +// "github.com/stretchr/testify/require" + +// "github.com/zeta-chain/zetacore/pkg/chains" +// "github.com/zeta-chain/zetacore/pkg/coin" +// keepertest "github.com/zeta-chain/zetacore/testutil/keeper" +// "github.com/zeta-chain/zetacore/testutil/sample" +// "github.com/zeta-chain/zetacore/x/crosschain/keeper" +// "github.com/zeta-chain/zetacore/x/crosschain/types" +// fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" +// observertypes "github.com/zeta-chain/zetacore/x/observer/types" +// ) + +// func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) { +// t.Run("process zevm deposit successfully", func(t *testing.T) { +// k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ +// UseFungibleMock: true, +// }) + +// // Setup mock data +// fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) +// receiver := sample.EthAddress() +// amount := big.NewInt(42) + +// // expect DepositCoinZeta to be called +// fungibleMock.On("ZETADepositAndCallContract", mock.Anything, +// mock.Anything, +// receiver, int64(0), amount, mock.Anything, mock.Anything).Return(nil, nil) + +// // call InitiateOutbound +// cctx := sample.CrossChainTx(t, "test") +// cctx.CctxStatus = &types.Status{Status: types.CctxStatus_PendingInbound} +// cctx.GetCurrentOutboundParam().Receiver = receiver.String() +// cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId +// cctx.GetInboundParams().Amount = sdkmath.NewUintFromBigInt(amount) +// cctx.InboundParams.CoinType = coin.CoinType_Zeta +// cctx.GetInboundParams().SenderChainId = 0 +// newStatus, err := k.InitiateOutbound(ctx, cctx) +// require.NoError(t, err) +// require.Equal(t, types.CctxStatus_OutboundMined, cctx.CctxStatus.Status) +// require.Equal(t, types.CctxStatus_OutboundMined, newStatus) +// }) + +// t.Run("unable to process zevm deposit HandleEVMDeposit returns err without reverting", func(t *testing.T) { +// k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ +// UseFungibleMock: true, +// }) + +// // Setup mock data +// fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) +// receiver := sample.EthAddress() +// amount := big.NewInt(42) + +// // mock unsuccessful HandleEVMDeposit which does not revert + +// fungibleMock.On("ZETADepositAndCallContract", mock.Anything, mock.Anything, receiver, int64(0), amount, mock.Anything, mock.Anything). +// Return(nil, fmt.Errorf("deposit error")) + +// // call InitiateOutbound +// cctx := sample.CrossChainTx(t, "test") +// cctx.CctxStatus = &types.Status{Status: types.CctxStatus_PendingInbound} +// cctx.GetCurrentOutboundParam().Receiver = receiver.String() +// cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId +// cctx.GetInboundParams().Amount = sdkmath.NewUintFromBigInt(amount) +// cctx.InboundParams.CoinType = coin.CoinType_Zeta +// cctx.GetInboundParams().SenderChainId = 0 +// newStatus, err := k.InitiateOutbound(ctx, cctx) +// require.NoError(t, err) +// require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) +// require.Equal(t, types.CctxStatus_Aborted, newStatus) +// require.Equal(t, "deposit error", cctx.CctxStatus.StatusMessage) +// }) + +// t.Run( +// "unable to process zevm deposit HandleEVMDeposit reverts fails at GetSupportedChainFromChainID", +// func(t *testing.T) { +// k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ +// UseFungibleMock: true, +// UseObserverMock: true, +// }) + +// // Setup mock data +// fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) +// observerMock := keepertest.GetCrosschainObserverMock(t, k) +// receiver := sample.EthAddress() +// amount := big.NewInt(42) +// senderChain := getValidEthChain() +// errDeposit := fmt.Errorf("deposit failed") + +// // Setup expected calls +// // mock unsuccessful HandleEVMDeposit which reverts , i.e returns err and isContractReverted = true +// keepertest.MockRevertForHandleEVMDeposit(fungibleMock, receiver, amount, senderChain.ChainId, errDeposit) + +// // mock unsuccessful GetSupportedChainFromChainID +// observerMock.On("GetSupportedChainFromChainID", mock.Anything, senderChain.ChainId). +// Return(nil) + +// // call InitiateOutbound +// cctx := GetERC20Cctx(t, receiver, *senderChain, "", amount) +// cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId +// newStatus, err := k.InitiateOutbound(ctx, cctx) +// require.NoError(t, err) +// require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) +// require.Equal(t, types.CctxStatus_Aborted, newStatus) +// require.Equal( +// t, +// fmt.Sprintf("chain not supported"), +// cctx.CctxStatus.StatusMessage, +// ) +// }, +// ) + +// t.Run("unable to process zevm deposit HandleEVMDeposit revert fails at GetRevertGasLimit", func(t *testing.T) { +// k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ +// UseFungibleMock: true, +// UseObserverMock: true, +// }) + +// // Setup mock data +// fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) +// observerMock := keepertest.GetCrosschainObserverMock(t, k) +// receiver := sample.EthAddress() +// amount := big.NewInt(42) +// senderChain := getValidEthChain() +// asset := "" +// errDeposit := fmt.Errorf("deposit failed") + +// // Setup expected calls +// keepertest.MockRevertForHandleEVMDeposit(fungibleMock, receiver, amount, senderChain.ChainId, errDeposit) + +// // Mock successful GetSupportedChainFromChainID +// keepertest.MockGetSupportedChainFromChainID(observerMock, senderChain) + +// // mock unsuccessful GetRevertGasLimit for ERC20 +// fungibleMock.On("GetForeignCoinFromAsset", mock.Anything, asset, senderChain.ChainId). +// Return(fungibletypes.ForeignCoins{}, false) + +// // call InitiateOutbound +// cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) +// cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId +// newStatus, err := k.InitiateOutbound(ctx, cctx) +// require.NoError(t, err) +// require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) +// require.Equal(t, types.CctxStatus_Aborted, newStatus) +// require.Equal( +// t, +// "GetRevertGasLimit: foreign coin not found for sender chain", +// cctx.CctxStatus.StatusMessage, +// ) +// }) + +// t.Run("unable to process zevm deposit HandleEVMDeposit revert fails at PayGasInERC20AndUpdateCctx", +// func(t *testing.T) { +// k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ +// UseFungibleMock: true, +// UseObserverMock: true, +// }) + +// // Setup mock data +// fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) +// receiver := sample.EthAddress() +// amount := big.NewInt(42) +// senderChain := getValidEthChain() +// asset := "" + +// // Setup expected calls +// errDeposit := fmt.Errorf("deposit failed") +// keepertest.MockRevertForHandleEVMDeposit(fungibleMock, receiver, amount, senderChain.ChainId, errDeposit) + +// observerMock := keepertest.GetCrosschainObserverMock(t, k) + +// // Mock successful GetSupportedChainFromChainID +// keepertest.MockGetSupportedChainFromChainID(observerMock, senderChain) + +// // mock successful GetRevertGasLimit for ERC20 +// keepertest.MockGetRevertGasLimitForERC20(fungibleMock, asset, *senderChain, 100) + +// // mock unsuccessful PayGasInERC20AndUpdateCctx +// observerMock.On("GetSupportedChainFromChainID", mock.Anything, senderChain.ChainId). +// Return(nil).Once() + +// // call InitiateOutbound +// cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) +// cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId +// newStatus, err := k.InitiateOutbound(ctx, cctx) +// require.NoError(t, err) +// require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) +// require.Equal(t, types.CctxStatus_Aborted, newStatus) +// require.Equal( +// t, +// "chain not supported", +// cctx.CctxStatus.StatusMessage, +// ) +// }, +// ) + +// t.Run( +// "uunable to process zevm deposit HandleEVMDeposit revert fails at PayGasInERC20AndUpdateCctx with gas limit is 0", +// func(t *testing.T) { +// k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ +// UseFungibleMock: true, +// UseObserverMock: true, +// }) + +// // Setup mock data +// fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) +// receiver := sample.EthAddress() +// amount := big.NewInt(42) +// senderChain := getValidEthChain() +// asset := "" + +// // Setup expected calls +// errDeposit := fmt.Errorf("deposit failed") +// keepertest.MockRevertForHandleEVMDeposit(fungibleMock, receiver, amount, senderChain.ChainId, errDeposit) + +// observerMock := keepertest.GetCrosschainObserverMock(t, k) + +// // Mock successful GetSupportedChainFromChainID +// keepertest.MockGetSupportedChainFromChainID(observerMock, senderChain) + +// // mock successful GetRevertGasLimit for ERC20 +// keepertest.MockGetRevertGasLimitForERC20(fungibleMock, asset, *senderChain, 0) + +// // mock unsuccessful PayGasInERC20AndUpdateCctx +// observerMock.On("GetSupportedChainFromChainID", mock.Anything, senderChain.ChainId). +// Return(nil).Once() + +// // call InitiateOutbound +// cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) +// cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId +// newStatus, err := k.InitiateOutbound(ctx, cctx) +// require.NoError(t, err) +// require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) +// require.Equal(t, types.CctxStatus_Aborted, newStatus) +// require.Equal( +// t, +// fmt.Sprintf("chain not supported"), +// cctx.CctxStatus.StatusMessage, +// ) +// }, +// ) + +// t.Run("unable to process zevm deposit HandleEVMDeposit reverts fails at UpdateNonce", func(t *testing.T) { +// k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ +// UseFungibleMock: true, +// UseObserverMock: true, +// }) + +// // Setup mock data +// fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) +// observerMock := keepertest.GetCrosschainObserverMock(t, k) +// receiver := sample.EthAddress() +// amount := big.NewInt(42) +// senderChain := getValidEthChain() +// asset := "" +// errDeposit := fmt.Errorf("deposit failed") + +// // Setup expected calls +// // mock unsuccessful HandleEVMDeposit which reverts , i.e returns err and isContractReverted = true +// keepertest.MockRevertForHandleEVMDeposit(fungibleMock, receiver, amount, senderChain.ChainId, errDeposit) + +// // Mock successful GetSupportedChainFromChainID +// keepertest.MockGetSupportedChainFromChainID(observerMock, senderChain) + +// // mock successful GetRevertGasLimit for ERC20 +// keepertest.MockGetRevertGasLimitForERC20(fungibleMock, asset, *senderChain, 100) + +// // mock successful PayGasAndUpdateCctx +// keepertest.MockPayGasAndUpdateCCTX(fungibleMock, observerMock, ctx, *k, *senderChain, asset) + +// // Mock unsuccessful UpdateNonce +// observerMock.On("GetChainNonces", mock.Anything, senderChain.ChainName.String()). +// Return(observertypes.ChainNonces{}, false) + +// // call InitiateOutbound +// cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) +// cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId +// newStatus, err := k.InitiateOutbound(ctx, cctx) +// require.NoError(t, err) +// require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) +// require.Equal(t, types.CctxStatus_Aborted, newStatus) +// require.Contains(t, cctx.CctxStatus.StatusMessage, "cannot find receiver chain nonce") +// }) + +// t.Run("unable to process zevm deposit HandleEVMDeposit revert successfully", func(t *testing.T) { +// k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ +// UseFungibleMock: true, +// UseObserverMock: true, +// }) + +// // Setup mock data +// fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) +// observerMock := keepertest.GetCrosschainObserverMock(t, k) +// receiver := sample.EthAddress() +// amount := big.NewInt(42) +// senderChain := getValidEthChain() +// asset := "" +// errDeposit := fmt.Errorf("deposit failed") + +// // Setup expected calls +// // mock unsuccessful HandleEVMDeposit which reverts , i.e returns err and isContractReverted = true +// keepertest.MockRevertForHandleEVMDeposit(fungibleMock, receiver, amount, senderChain.ChainId, errDeposit) + +// // Mock successful GetSupportedChainFromChainID +// keepertest.MockGetSupportedChainFromChainID(observerMock, senderChain) + +// // mock successful GetRevertGasLimit for ERC20 +// keepertest.MockGetRevertGasLimitForERC20(fungibleMock, asset, *senderChain, 100) + +// // mock successful PayGasAndUpdateCctx +// keepertest.MockPayGasAndUpdateCCTX(fungibleMock, observerMock, ctx, *k, *senderChain, asset) +// // mock successful UpdateNonce +// updatedNonce := keepertest.MockUpdateNonce(observerMock, *senderChain) + +// // call InitiateOutbound +// cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) +// cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId +// newStatus, err := k.InitiateOutbound(ctx, cctx) +// require.NoError(t, err) +// require.Equal(t, types.CctxStatus_PendingRevert, cctx.CctxStatus.Status) +// require.Equal(t, types.CctxStatus_PendingRevert, newStatus) +// require.Equal(t, errDeposit.Error(), cctx.CctxStatus.StatusMessage) +// require.Equal(t, updatedNonce, cctx.GetCurrentOutboundParam().TssNonce) +// }) + +// t.Run("unable to process zevm deposit HandleEVMDeposit revert fails as the cctx has already been reverted", +// func(t *testing.T) { +// k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ +// UseFungibleMock: true, +// UseObserverMock: true, +// }) + +// // Setup mock data +// fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) +// observerMock := keepertest.GetCrosschainObserverMock(t, k) +// receiver := sample.EthAddress() +// amount := big.NewInt(42) +// senderChain := getValidEthChain() +// asset := "" +// errDeposit := fmt.Errorf("deposit failed") + +// // Setup expected calls +// // mock unsuccessful HandleEVMDeposit which reverts , i.e returns err and isContractReverted = true +// keepertest.MockRevertForHandleEVMDeposit(fungibleMock, receiver, amount, senderChain.ChainId, errDeposit) + +// // Mock successful GetSupportedChainFromChainID +// keepertest.MockGetSupportedChainFromChainID(observerMock, senderChain) + +// // mock successful GetRevertGasLimit for ERC20 +// keepertest.MockGetRevertGasLimitForERC20(fungibleMock, asset, *senderChain, 100) + +// // call InitiateOutbound +// cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) +// cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId +// cctx.OutboundParams = append(cctx.OutboundParams, cctx.GetCurrentOutboundParam()) +// newStatus, err := k.InitiateOutbound(ctx, cctx) +// require.NoError(t, err) +// require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) +// require.Equal(t, types.CctxStatus_Aborted, newStatus) +// require.Contains( +// t, +// cctx.CctxStatus.StatusMessage, +// fmt.Sprintf("cannot revert a revert tx"), +// ) +// }, +// ) +// } + +// func TestKeeper_InitiateOutboundProcessCrosschainMsgPassing(t *testing.T) { +// t.Run("process crosschain msg passing successfully", func(t *testing.T) { +// k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ +// UseFungibleMock: true, +// UseObserverMock: true, +// }) + +// // Setup mock data +// fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) +// observerMock := keepertest.GetCrosschainObserverMock(t, k) +// receiver := sample.EthAddress() +// amount := big.NewInt(42) +// receiverChain := getValidEthChain() + +// // mock successful PayGasAndUpdateCctx +// keepertest.MockPayGasAndUpdateCCTX(fungibleMock, observerMock, ctx, *k, *receiverChain, "") + +// // mock successful UpdateNonce +// updatedNonce := keepertest.MockUpdateNonce(observerMock, *receiverChain) + +// // call InitiateOutbound +// cctx := GetERC20Cctx(t, receiver, *receiverChain, "", amount) +// newStatus, err := k.InitiateOutbound(ctx, cctx) +// require.NoError(t, err) +// require.Equal(t, types.CctxStatus_PendingOutbound, cctx.CctxStatus.Status) +// require.Equal(t, types.CctxStatus_PendingOutbound, newStatus) +// require.Equal(t, updatedNonce, cctx.GetCurrentOutboundParam().TssNonce) +// }) + +// t.Run("unable to process crosschain msg passing PayGasAndUpdateCctx fails", func(t *testing.T) { +// k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ +// UseFungibleMock: true, +// UseObserverMock: true, +// }) + +// // Setup mock data +// observerMock := keepertest.GetCrosschainObserverMock(t, k) +// receiver := sample.EthAddress() +// amount := big.NewInt(42) +// receiverChain := getValidEthChain() + +// // mock unsuccessful PayGasAndUpdateCctx +// observerMock.On("GetSupportedChainFromChainID", mock.Anything, receiverChain.ChainId). +// Return(nil).Once() + +// // call InitiateOutbound +// cctx := GetERC20Cctx(t, receiver, *receiverChain, "", amount) +// newStatus, err := k.InitiateOutbound(ctx, cctx) +// require.NoError(t, err) +// require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) +// require.Equal(t, types.CctxStatus_Aborted, newStatus) +// require.Equal(t, observertypes.ErrSupportedChains.Error(), cctx.CctxStatus.StatusMessage) +// }) + +// t.Run("unable to process crosschain msg passing UpdateNonce fails", func(t *testing.T) { +// k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ +// UseFungibleMock: true, +// UseObserverMock: true, +// }) + +// // Setup mock data +// fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) +// observerMock := keepertest.GetCrosschainObserverMock(t, k) +// receiver := sample.EthAddress() +// amount := big.NewInt(42) +// receiverChain := getValidEthChain() + +// // mock successful PayGasAndUpdateCctx +// keepertest.MockPayGasAndUpdateCCTX(fungibleMock, observerMock, ctx, *k, *receiverChain, "") + +// // mock unsuccessful UpdateNonce +// observerMock.On("GetChainNonces", mock.Anything, receiverChain.ChainName.String()). +// Return(observertypes.ChainNonces{}, false) + +// // call InitiateOutbound +// cctx := GetERC20Cctx(t, receiver, *receiverChain, "", amount) +// newStatus, err := k.InitiateOutbound(ctx, cctx) +// require.NoError(t, err) +// require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) +// require.Equal(t, types.CctxStatus_Aborted, newStatus) +// require.Contains(t, cctx.CctxStatus.StatusMessage, "cannot find receiver chain nonce") +// }) +// } + +// func TestKeeper_InitiateOutboundFailures(t *testing.T) { +// t.Run("should fail if chain info can not be found for receiver chain id", func(t *testing.T) { +// k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ +// UseFungibleMock: true, +// UseObserverMock: true, +// }) + +// // Setup mock data +// receiver := sample.EthAddress() +// amount := big.NewInt(42) +// receiverChain := getValidEthChain() +// receiverChain.ChainId = 123 +// // call InitiateOutbound +// cctx := GetERC20Cctx(t, receiver, *receiverChain, "", amount) +// newStatus, err := k.InitiateOutbound(ctx, cctx) +// require.Error(t, err) +// require.Equal(t, types.CctxStatus_PendingInbound, newStatus) +// require.ErrorContains(t, err, "chain info not found") +// }) + +// t.Run("should fail if cctx gateway not found for receiver chain id", func(t *testing.T) { +// k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ +// UseFungibleMock: true, +// UseObserverMock: true, +// }) + +// // reset cctx gateways +// k.SetCCTXGateways(map[chains.CCTXGateway]keeper.CCTXGateway{}) + +// // Setup mock data +// receiver := sample.EthAddress() +// amount := big.NewInt(42) +// receiverChain := getValidEthChain() +// // call InitiateOutbound +// cctx := GetERC20Cctx(t, receiver, *receiverChain, "", amount) +// newStatus, err := k.InitiateOutbound(ctx, cctx) +// require.Equal(t, types.CctxStatus_PendingInbound, newStatus) +// require.NotNil(t, err) +// require.ErrorContains(t, err, "CCTXGateway not defined for receiver chain") +// }) + +// } diff --git a/x/crosschain/keeper/keeper.go b/x/crosschain/keeper/keeper.go index b32543126c..fb42e9e90e 100644 --- a/x/crosschain/keeper/keeper.go +++ b/x/crosschain/keeper/keeper.go @@ -12,14 +12,6 @@ import ( "github.com/zeta-chain/zetacore/x/crosschain/types" ) -// CCTXGateway is interface implemented by every gateway. It is one of interfaces used for communication -// between CCTX gateways and crosschain module, and it is called by crosschain module. -type CCTXGateway interface { - // Initiate a new outbound, this tells the CCTXGateway to carry out the action to execute the outbound. - // It is the only entry point to initiate an outbound and it returns new CCTX status after it is completed. - InitiateOutbound(ctx sdk.Context, cctx *types.CrossChainTx) (newCCTXStatus types.CctxStatus) -} - type ( Keeper struct { cdc codec.Codec diff --git a/x/crosschain/keeper/msg_server_vote_inbound_tx.go b/x/crosschain/keeper/msg_server_vote_inbound_tx.go index 6de4e1d562..06f03cdbc9 100644 --- a/x/crosschain/keeper/msg_server_vote_inbound_tx.go +++ b/x/crosschain/keeper/msg_server_vote_inbound_tx.go @@ -98,7 +98,7 @@ func (k msgServer) VoteInbound( return &types.MsgVoteInboundResponse{}, nil } - cctx, err := k.ValidateInboundObservers(ctx, msg) + cctx, err := k.ValidateInboundObservers(ctx, msg, true) if err != nil { return nil, err } From 55833076b8bf9aa655f2e0ab86ee1caf2a258874 Mon Sep 17 00:00:00 2001 From: skosito Date: Wed, 12 Jun 2024 15:35:09 +0200 Subject: [PATCH 25/38] cleanup --- x/crosschain/keeper/cctx_gateway_observers.go | 9 +++++++++ x/crosschain/keeper/evm_hooks.go | 7 ------- x/crosschain/types/cctx.go | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/x/crosschain/keeper/cctx_gateway_observers.go b/x/crosschain/keeper/cctx_gateway_observers.go index 9150536127..a00f40ac67 100644 --- a/x/crosschain/keeper/cctx_gateway_observers.go +++ b/x/crosschain/keeper/cctx_gateway_observers.go @@ -1,6 +1,8 @@ package keeper import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/zeta-chain/zetacore/pkg/chains" @@ -55,6 +57,13 @@ func (c CCTXGatewayObservers) InitiateOutbound( if err != nil { return err } + } else { + gasprice, found := c.crosschainKeeper.GetGasPrice(ctx, config.CCTX.GetCurrentOutboundParam().ReceiverChainId) + if !found { + return fmt.Errorf("gasprice not found for %d", config.CCTX.GetCurrentOutboundParam().ReceiverChainId) + } + config.CCTX.GetCurrentOutboundParam().GasPrice = fmt.Sprintf("%d", gasprice.Prices[gasprice.MedianIndex]) + config.CCTX.GetCurrentOutboundParam().Amount = config.CCTX.InboundParams.Amount } return c.crosschainKeeper.UpdateNonce(tmpCtx, outboundReceiverChainID, config.CCTX) }() diff --git a/x/crosschain/keeper/evm_hooks.go b/x/crosschain/keeper/evm_hooks.go index 37e7a931a8..8ab7b7b72d 100644 --- a/x/crosschain/keeper/evm_hooks.go +++ b/x/crosschain/keeper/evm_hooks.go @@ -198,13 +198,6 @@ func (k Keeper) ProcessZRC20WithdrawalEvent( return errors.New("cctx aborted") } - gasprice, found := k.GetGasPrice(ctx, receiverChain.ChainId) - if !found { - return fmt.Errorf("gasprice not found for %s", receiverChain) - } - cctx.GetCurrentOutboundParam().GasPrice = fmt.Sprintf("%d", gasprice.Prices[gasprice.MedianIndex]) - cctx.GetCurrentOutboundParam().Amount = cctx.InboundParams.Amount - EmitZRCWithdrawCreated(ctx, *cctx) return k.ProcessCCTX(ctx, *cctx, receiverChain) diff --git a/x/crosschain/types/cctx.go b/x/crosschain/types/cctx.go index f99fbd6cbf..a4080cc968 100644 --- a/x/crosschain/types/cctx.go +++ b/x/crosschain/types/cctx.go @@ -168,7 +168,7 @@ func GetCctxIndexFromBytes(sendHash [32]byte) string { return fmt.Sprintf("0x%s", hex.EncodeToString(sendHash[:])) } -// NewCCTX creates a new CCTX.From a MsgVoteInbound message and a TSS pubkey. +// NewCCTX creates a new CCTX from a MsgVoteInbound message and a TSS pubkey. // It also validates the created cctx func NewCCTX(ctx sdk.Context, msg MsgVoteInbound, tssPubkey string) (CrossChainTx, error) { index := msg.Digest() From ec7465e29044587406e84114d920514456cd2cc2 Mon Sep 17 00:00:00 2001 From: skosito Date: Wed, 12 Jun 2024 15:46:36 +0200 Subject: [PATCH 26/38] changelog --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index 2d9250f403..8d70e3f7a8 100644 --- a/changelog.md +++ b/changelog.md @@ -22,6 +22,7 @@ * [2275](https://github.com/zeta-chain/node/pull/2275) - add ChainInfo singleton state variable in authority * [2291](https://github.com/zeta-chain/node/pull/2291) - initialize cctx gateway interface * [2317](https://github.com/zeta-chain/node/pull/2317) - add ValidateOutbound method for cctx orchestrator +* [2340](https://github.com/zeta-chain/node/pull/2340) - add ValidateInbound method for cctx orchestrator * [2289](https://github.com/zeta-chain/node/pull/2289) - add an authorization list to keep track of all authorizations on the chain * [2305](https://github.com/zeta-chain/node/pull/2305) - add new messages `MsgAddAuthorization` and `MsgRemoveAuthorization` that can be used to update the authorization list * [2313](https://github.com/zeta-chain/node/pull/2313) - add `CheckAuthorization` function to replace the `IsAuthorized` function. The new function uses the authorization list to verify the signer's authorization. From 303c1123b44519db3f0da10ca0203fd4b1d0716a Mon Sep 17 00:00:00 2001 From: skosito Date: Wed, 12 Jun 2024 15:52:16 +0200 Subject: [PATCH 27/38] make generate --- x/crosschain/keeper/cctx_gateway_zevm.go | 5 ++++- x/crosschain/keeper/cctx_gateways.go | 1 + x/crosschain/keeper/cctx_orchestrator_validate_inbound.go | 7 ++++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/x/crosschain/keeper/cctx_gateway_zevm.go b/x/crosschain/keeper/cctx_gateway_zevm.go index 057e9a7fd0..07a866d7d4 100644 --- a/x/crosschain/keeper/cctx_gateway_zevm.go +++ b/x/crosschain/keeper/cctx_gateway_zevm.go @@ -19,7 +19,10 @@ func NewCCTXGatewayZEVM(crosschainKeeper Keeper) CCTXGatewayZEVM { } // InitiateOutbound handles evm deposit and immediately validates pending outbound -func (c CCTXGatewayZEVM) InitiateOutbound(ctx sdk.Context, config InitiateOutboundConfig) (newCCTXStatus types.CctxStatus) { +func (c CCTXGatewayZEVM) InitiateOutbound( + ctx sdk.Context, + config InitiateOutboundConfig, +) (newCCTXStatus types.CctxStatus) { tmpCtx, commit := ctx.CacheContext() isContractReverted, err := c.crosschainKeeper.HandleEVMDeposit(tmpCtx, config.CCTX) diff --git a/x/crosschain/keeper/cctx_gateways.go b/x/crosschain/keeper/cctx_gateways.go index 25259b1281..db2234142f 100644 --- a/x/crosschain/keeper/cctx_gateways.go +++ b/x/crosschain/keeper/cctx_gateways.go @@ -2,6 +2,7 @@ package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/x/crosschain/types" ) diff --git a/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go b/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go index 8ac0ac5b79..8e0ae54021 100644 --- a/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go +++ b/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go @@ -2,10 +2,15 @@ package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/zeta-chain/zetacore/x/crosschain/types" ) -func (k Keeper) ValidateInboundObservers(ctx sdk.Context, msg *types.MsgVoteInbound, payGas bool) (*types.CrossChainTx, error) { +func (k Keeper) ValidateInboundObservers( + ctx sdk.Context, + msg *types.MsgVoteInbound, + payGas bool, +) (*types.CrossChainTx, error) { tss, tssFound := k.zetaObserverKeeper.GetTSS(ctx) if !tssFound { return nil, types.ErrCannotFindTSSKeys From f675251f821121681c6d4db2cb450c025428f2d2 Mon Sep 17 00:00:00 2001 From: skosito Date: Wed, 12 Jun 2024 15:54:49 +0200 Subject: [PATCH 28/38] lint fixes --- .../cctx_orchestrator_validate_inbound.go | 6 +++++ x/crosschain/keeper/evm_hooks.go | 23 ++++--------------- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go b/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go index 8e0ae54021..17d2cc3a23 100644 --- a/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go +++ b/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go @@ -4,6 +4,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/zeta-chain/zetacore/x/crosschain/types" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) func (k Keeper) ValidateInboundObservers( @@ -15,6 +16,11 @@ func (k Keeper) ValidateInboundObservers( if !tssFound { return nil, types.ErrCannotFindTSSKeys } + + // Do not process if inbound is disabled + if !k.zetaObserverKeeper.IsInboundEnabled(ctx) { + return nil, observertypes.ErrInboundDisabled + } // create a new CCTX from the inbound message.The status of the new CCTX is set to PendingInbound. cctx, err := types.NewCCTX(ctx, *msg, tss.TssPubkey) if err != nil { diff --git a/x/crosschain/keeper/evm_hooks.go b/x/crosschain/keeper/evm_hooks.go index 8ab7b7b72d..6d4465f470 100644 --- a/x/crosschain/keeper/evm_hooks.go +++ b/x/crosschain/keeper/evm_hooks.go @@ -78,17 +78,6 @@ func (k Keeper) ProcessLogs( return fmt.Errorf("connectorZEVM address is empty") } - // These cannot be processed without TSS keys, return an error if TSS is not found - tss, found := k.zetaObserverKeeper.GetTSS(ctx) - if !found { - return errorsmod.Wrap(types.ErrCannotFindTSSKeys, "Cannot process logs without TSS keys") - } - - // Do not process withdrawal events if inbound is disabled - if !k.zetaObserverKeeper.IsInboundEnabled(ctx) { - return observertypes.ErrInboundDisabled - } - for _, log := range logs { eventZrc20Withdrawal, errZrc20 := ParseZRC20WithdrawalEvent(*log) eventZetaSent, errZetaSent := ParseZetaSentEvent(*log, connectorZEVMAddr) @@ -120,13 +109,13 @@ func (k Keeper) ProcessLogs( } // If the event is valid, we will process it and create a new CCTX // If the process fails, we will return an error and roll back the transaction - if err := k.ProcessZRC20WithdrawalEvent(ctx, eventZrc20Withdrawal, emittingContract, txOrigin, tss); err != nil { + if err := k.ProcessZRC20WithdrawalEvent(ctx, eventZrc20Withdrawal, emittingContract, txOrigin); err != nil { return err } } // if eventZetaSent is not nil we will try to validate it and see if it can be processed if eventZetaSent != nil { - if err := k.ProcessZetaSentEvent(ctx, eventZetaSent, emittingContract, txOrigin, tss); err != nil { + if err := k.ProcessZetaSentEvent(ctx, eventZetaSent, emittingContract, txOrigin); err != nil { return err } } @@ -141,7 +130,6 @@ func (k Keeper) ProcessZRC20WithdrawalEvent( event *zrc20.ZRC20Withdrawal, emittingContract ethcommon.Address, txOrigin string, - tss observertypes.TSS, ) error { ctx.Logger().Info(fmt.Sprintf("ZRC20 withdrawal to %s amount %d", hex.EncodeToString(event.To), event.Value)) @@ -200,7 +188,7 @@ func (k Keeper) ProcessZRC20WithdrawalEvent( EmitZRCWithdrawCreated(ctx, *cctx) - return k.ProcessCCTX(ctx, *cctx, receiverChain) + return k.ProcessCCTX(ctx, *cctx) } func (k Keeper) ProcessZetaSentEvent( @@ -208,7 +196,6 @@ func (k Keeper) ProcessZetaSentEvent( event *connectorzevm.ZetaConnectorZEVMZetaSent, emittingContract ethcommon.Address, txOrigin string, - tss observertypes.TSS, ) error { ctx.Logger().Info(fmt.Sprintf( "Zeta withdrawal to %s amount %d to chain with chainId %d", @@ -274,10 +261,10 @@ func (k Keeper) ProcessZetaSentEvent( } EmitZetaWithdrawCreated(ctx, *cctx) - return k.ProcessCCTX(ctx, *cctx, receiverChain) + return k.ProcessCCTX(ctx, *cctx) } -func (k Keeper) ProcessCCTX(ctx sdk.Context, cctx types.CrossChainTx, receiverChain *chains.Chain) error { +func (k Keeper) ProcessCCTX(ctx sdk.Context, cctx types.CrossChainTx) error { inCctxIndex, ok := ctx.Value("inCctxIndex").(string) if ok { cctx.InboundParams.ObservedHash = inCctxIndex From 0ef2093602f4822eae68cffd7fd50ffb21ea7b98 Mon Sep 17 00:00:00 2001 From: skosito Date: Wed, 12 Jun 2024 17:55:37 +0200 Subject: [PATCH 29/38] fix evm hooks tests --- x/crosschain/keeper/cctx_gateway_observers.go | 8 +-- x/crosschain/keeper/cctx_gateway_zevm.go | 6 +- x/crosschain/keeper/cctx_gateways.go | 9 ++- x/crosschain/keeper/evm_hooks.go | 1 - x/crosschain/keeper/evm_hooks_test.go | 56 ++++++++----------- x/crosschain/keeper/initiate_outbound.go | 4 +- 6 files changed, 39 insertions(+), 45 deletions(-) diff --git a/x/crosschain/keeper/cctx_gateway_observers.go b/x/crosschain/keeper/cctx_gateway_observers.go index a00f40ac67..0e4efb8ff6 100644 --- a/x/crosschain/keeper/cctx_gateway_observers.go +++ b/x/crosschain/keeper/cctx_gateway_observers.go @@ -36,7 +36,7 @@ InitiateOutbound updates the store so observers can use the PendingCCTX query: func (c CCTXGatewayObservers) InitiateOutbound( ctx sdk.Context, config InitiateOutboundConfig, -) (newCCTXStatus types.CctxStatus) { +) (newCCTXStatus types.CctxStatus, err error) { tmpCtx, commit := ctx.CacheContext() outboundReceiverChainID := config.CCTX.GetCurrentOutboundParam().ReceiverChainId // TODO: does this condition make sense? @@ -45,7 +45,7 @@ func (c CCTXGatewayObservers) InitiateOutbound( noEthereumTxEvent = true } - err := func() error { + err = func() error { if config.PayGas { err := c.crosschainKeeper.PayGasAndUpdateCctx( tmpCtx, @@ -70,9 +70,9 @@ func (c CCTXGatewayObservers) InitiateOutbound( if err != nil { // do not commit anything here as the CCTX should be aborted config.CCTX.SetAbort(err.Error()) - return types.CctxStatus_Aborted + return types.CctxStatus_Aborted, err } commit() config.CCTX.SetPendingOutbound("") - return types.CctxStatus_PendingOutbound + return types.CctxStatus_PendingOutbound, nil } diff --git a/x/crosschain/keeper/cctx_gateway_zevm.go b/x/crosschain/keeper/cctx_gateway_zevm.go index 07a866d7d4..622155fb32 100644 --- a/x/crosschain/keeper/cctx_gateway_zevm.go +++ b/x/crosschain/keeper/cctx_gateway_zevm.go @@ -22,14 +22,14 @@ func NewCCTXGatewayZEVM(crosschainKeeper Keeper) CCTXGatewayZEVM { func (c CCTXGatewayZEVM) InitiateOutbound( ctx sdk.Context, config InitiateOutboundConfig, -) (newCCTXStatus types.CctxStatus) { +) (newCCTXStatus types.CctxStatus, err error) { tmpCtx, commit := ctx.CacheContext() isContractReverted, err := c.crosschainKeeper.HandleEVMDeposit(tmpCtx, config.CCTX) if err != nil && !isContractReverted { // exceptional case; internal error; should abort CCTX config.CCTX.SetAbort(err.Error()) - return types.CctxStatus_Aborted + return types.CctxStatus_Aborted, err } config.CCTX.SetPendingOutbound("") @@ -38,5 +38,5 @@ func (c CCTXGatewayZEVM) InitiateOutbound( commit() } - return newCCTXStatus + return newCCTXStatus, nil } diff --git a/x/crosschain/keeper/cctx_gateways.go b/x/crosschain/keeper/cctx_gateways.go index db2234142f..621ed7f7c5 100644 --- a/x/crosschain/keeper/cctx_gateways.go +++ b/x/crosschain/keeper/cctx_gateways.go @@ -12,7 +12,7 @@ import ( type CCTXGateway interface { // Initiate a new outbound, this tells the CCTXGateway to carry out the action to execute the outbound. // It is the only entry point to initiate an outbound and it returns new CCTX status after it is completed. - InitiateOutbound(ctx sdk.Context, config InitiateOutboundConfig) (newCCTXStatus types.CctxStatus) + InitiateOutbound(ctx sdk.Context, config InitiateOutboundConfig) (newCCTXStatus types.CctxStatus, err error) } var cctxGateways map[chains.CCTXGateway]CCTXGateway @@ -26,7 +26,12 @@ func InitCCTXGateways(keeper Keeper) { } } -func ResolveCCTXGateway(c chains.CCTXGateway) (CCTXGateway, bool) { +func ResolveCCTXGateway(c chains.CCTXGateway, keeper Keeper) (CCTXGateway, bool) { + cctxGateways = map[chains.CCTXGateway]CCTXGateway{ + chains.CCTXGateway_observers: NewCCTXGatewayObservers(keeper), + chains.CCTXGateway_zevm: NewCCTXGatewayZEVM(keeper), + } + cctxGateway, ok := cctxGateways[c] return cctxGateway, ok } diff --git a/x/crosschain/keeper/evm_hooks.go b/x/crosschain/keeper/evm_hooks.go index 6d4465f470..399b12893e 100644 --- a/x/crosschain/keeper/evm_hooks.go +++ b/x/crosschain/keeper/evm_hooks.go @@ -131,7 +131,6 @@ func (k Keeper) ProcessZRC20WithdrawalEvent( emittingContract ethcommon.Address, txOrigin string, ) error { - ctx.Logger().Info(fmt.Sprintf("ZRC20 withdrawal to %s amount %d", hex.EncodeToString(event.To), event.Value)) foreignCoin, found := k.fungibleKeeper.GetForeignCoins(ctx, event.Raw.Address.Hex()) if !found { diff --git a/x/crosschain/keeper/evm_hooks_test.go b/x/crosschain/keeper/evm_hooks_test.go index 80f8c3b28b..bc67f34a99 100644 --- a/x/crosschain/keeper/evm_hooks_test.go +++ b/x/crosschain/keeper/evm_hooks_test.go @@ -211,9 +211,8 @@ func TestKeeper_ProcessZRC20WithdrawalEvent(t *testing.T) { event.Raw.Address = zrc20 emittingContract := sample.EthAddress() txOrigin := sample.EthAddress() - tss := sample.Tss() - err = k.ProcessZRC20WithdrawalEvent(ctx, event, emittingContract, txOrigin.Hex(), tss) + err = k.ProcessZRC20WithdrawalEvent(ctx, event, emittingContract, txOrigin.Hex()) require.NoError(t, err) cctxList := k.GetAllCrossChainTx(ctx) require.Len(t, cctxList, 1) @@ -237,9 +236,8 @@ func TestKeeper_ProcessZRC20WithdrawalEvent(t *testing.T) { event.Raw.Address = zrc20 emittingContract := sample.EthAddress() txOrigin := sample.EthAddress() - tss := sample.Tss() - err = k.ProcessZRC20WithdrawalEvent(ctx, event, emittingContract, txOrigin.Hex(), tss) + err = k.ProcessZRC20WithdrawalEvent(ctx, event, emittingContract, txOrigin.Hex()) require.NoError(t, err) cctxList := k.GetAllCrossChainTx(ctx) require.Len(t, cctxList, 1) @@ -262,9 +260,8 @@ func TestKeeper_ProcessZRC20WithdrawalEvent(t *testing.T) { setupGasCoin(t, ctx, zk.FungibleKeeper, sdkk.EvmKeeper, chainID, "ethereum", "ETH") emittingContract := sample.EthAddress() txOrigin := sample.EthAddress() - tss := sample.Tss() - err = k.ProcessZRC20WithdrawalEvent(ctx, event, emittingContract, txOrigin.Hex(), tss) + err = k.ProcessZRC20WithdrawalEvent(ctx, event, emittingContract, txOrigin.Hex()) require.ErrorContains(t, err, "cannot find foreign coin with emittingContract address") require.Empty(t, k.GetAllCrossChainTx(ctx)) }) @@ -283,9 +280,8 @@ func TestKeeper_ProcessZRC20WithdrawalEvent(t *testing.T) { event.Raw.Address = zrc20 emittingContract := sample.EthAddress() txOrigin := sample.EthAddress() - tss := sample.Tss() - err = k.ProcessZRC20WithdrawalEvent(ctx, event, emittingContract, txOrigin.Hex(), tss) + err = k.ProcessZRC20WithdrawalEvent(ctx, event, emittingContract, txOrigin.Hex()) require.ErrorContains(t, err, "chain not supported") require.Empty(t, k.GetAllCrossChainTx(ctx)) }) @@ -305,10 +301,10 @@ func TestKeeper_ProcessZRC20WithdrawalEvent(t *testing.T) { event.Raw.Address = zrc20 emittingContract := sample.EthAddress() txOrigin := sample.EthAddress() - tss := sample.Tss() + ctx = ctx.WithChainID("test_21-1") - err = k.ProcessZRC20WithdrawalEvent(ctx, event, emittingContract, txOrigin.Hex(), tss) + err = k.ProcessZRC20WithdrawalEvent(ctx, event, emittingContract, txOrigin.Hex()) require.ErrorContains(t, err, "failed to convert chainID: chain 21 not found") require.Empty(t, k.GetAllCrossChainTx(ctx)) }) @@ -329,9 +325,8 @@ func TestKeeper_ProcessZRC20WithdrawalEvent(t *testing.T) { event.To = ethcommon.Address{}.Bytes() emittingContract := sample.EthAddress() txOrigin := sample.EthAddress() - tss := sample.Tss() - err = k.ProcessZRC20WithdrawalEvent(ctx, event, emittingContract, txOrigin.Hex(), tss) + err = k.ProcessZRC20WithdrawalEvent(ctx, event, emittingContract, txOrigin.Hex()) require.ErrorContains(t, err, "cannot encode address") require.Empty(t, k.GetAllCrossChainTx(ctx)) }) @@ -354,13 +349,13 @@ func TestKeeper_ProcessZRC20WithdrawalEvent(t *testing.T) { event.Raw.Address = zrc20 emittingContract := sample.EthAddress() txOrigin := sample.EthAddress() - tss := sample.Tss() + fc, _ := zk.FungibleKeeper.GetForeignCoins(ctx, zrc20.Hex()) fungibleMock.On("GetForeignCoins", mock.Anything, mock.Anything).Return(fc, true) fungibleMock.On("QueryGasLimit", mock.Anything, mock.Anything). Return(big.NewInt(0), fmt.Errorf("error querying gas limit")) - err = k.ProcessZRC20WithdrawalEvent(ctx, event, emittingContract, txOrigin.Hex(), tss) + err = k.ProcessZRC20WithdrawalEvent(ctx, event, emittingContract, txOrigin.Hex()) require.ErrorContains(t, err, "error querying gas limit") require.Empty(t, k.GetAllCrossChainTx(ctx)) }) @@ -381,9 +376,8 @@ func TestKeeper_ProcessZRC20WithdrawalEvent(t *testing.T) { event.Raw.Address = zrc20 emittingContract := sample.EthAddress() txOrigin := sample.EthAddress() - tss := sample.Tss() - err = k.ProcessZRC20WithdrawalEvent(ctx, event, emittingContract, txOrigin.Hex(), tss) + err = k.ProcessZRC20WithdrawalEvent(ctx, event, emittingContract, txOrigin.Hex()) require.ErrorContains(t, err, "gasprice not found") require.Empty(t, k.GetAllCrossChainTx(ctx)) }) @@ -408,10 +402,9 @@ func TestKeeper_ProcessZRC20WithdrawalEvent(t *testing.T) { event.Raw.Address = zrc20 emittingContract := sample.EthAddress() txOrigin := sample.EthAddress() - tss := sample.Tss() - err = k.ProcessZRC20WithdrawalEvent(ctx, event, emittingContract, txOrigin.Hex(), tss) - require.ErrorContains(t, err, "ProcessWithdrawalEvent: update nonce failed") + err = k.ProcessZRC20WithdrawalEvent(ctx, event, emittingContract, txOrigin.Hex()) + require.ErrorContains(t, err, "nonce mismatch") require.Empty(t, k.GetAllCrossChainTx(ctx)) }) } @@ -489,9 +482,8 @@ func TestKeeper_ProcessZetaSentEvent(t *testing.T) { require.NoError(t, err) emittingContract := sample.EthAddress() txOrigin := sample.EthAddress() - tss := sample.Tss() - err = k.ProcessZetaSentEvent(ctx, event, emittingContract, txOrigin.Hex(), tss) + err = k.ProcessZetaSentEvent(ctx, event, emittingContract, txOrigin.Hex()) require.NoError(t, err) cctxList := k.GetAllCrossChainTx(ctx) require.Len(t, cctxList, 1) @@ -526,9 +518,8 @@ func TestKeeper_ProcessZetaSentEvent(t *testing.T) { require.NoError(t, err) emittingContract := sample.EthAddress() txOrigin := sample.EthAddress() - tss := sample.Tss() - err = k.ProcessZetaSentEvent(ctx, event, emittingContract, txOrigin.Hex(), tss) + err = k.ProcessZetaSentEvent(ctx, event, emittingContract, txOrigin.Hex()) require.ErrorContains(t, err, "ProcessZetaSentEvent: failed to burn coins from fungible") }) @@ -557,8 +548,8 @@ func TestKeeper_ProcessZetaSentEvent(t *testing.T) { require.NoError(t, err) emittingContract := sample.EthAddress() txOrigin := sample.EthAddress() - tss := sample.Tss() - err = k.ProcessZetaSentEvent(ctx, event, emittingContract, txOrigin.Hex(), tss) + + err = k.ProcessZetaSentEvent(ctx, event, emittingContract, txOrigin.Hex()) require.ErrorContains(t, err, "chain not supported") }) @@ -589,9 +580,9 @@ func TestKeeper_ProcessZetaSentEvent(t *testing.T) { require.NoError(t, err) emittingContract := sample.EthAddress() txOrigin := sample.EthAddress() - tss := sample.Tss() + ctx = ctx.WithChainID("test-21-1") - err = k.ProcessZetaSentEvent(ctx, event, emittingContract, txOrigin.Hex(), tss) + err = k.ProcessZetaSentEvent(ctx, event, emittingContract, txOrigin.Hex()) require.ErrorContains(t, err, "ProcessZetaSentEvent: failed to convert chainID") }) @@ -619,10 +610,9 @@ func TestKeeper_ProcessZetaSentEvent(t *testing.T) { require.NoError(t, err) emittingContract := sample.EthAddress() txOrigin := sample.EthAddress() - tss := sample.Tss() - err = k.ProcessZetaSentEvent(ctx, event, emittingContract, txOrigin.Hex(), tss) - require.ErrorContains(t, err, "ProcessWithdrawalEvent: pay gas failed") + err = k.ProcessZetaSentEvent(ctx, event, emittingContract, txOrigin.Hex()) + require.ErrorContains(t, err, "gas coin contract invalid address") }) t.Run("unable to process ZetaSentEvent if process cctx fails", func(t *testing.T) { @@ -658,9 +648,9 @@ func TestKeeper_ProcessZetaSentEvent(t *testing.T) { require.NoError(t, err) emittingContract := sample.EthAddress() txOrigin := sample.EthAddress() - tss := sample.Tss() - err = k.ProcessZetaSentEvent(ctx, event, emittingContract, txOrigin.Hex(), tss) - require.ErrorContains(t, err, "ProcessWithdrawalEvent: update nonce failed") + + err = k.ProcessZetaSentEvent(ctx, event, emittingContract, txOrigin.Hex()) + require.ErrorContains(t, err, "nonce mismatch") }) } diff --git a/x/crosschain/keeper/initiate_outbound.go b/x/crosschain/keeper/initiate_outbound.go index b9063ceadd..16324e9fb7 100644 --- a/x/crosschain/keeper/initiate_outbound.go +++ b/x/crosschain/keeper/initiate_outbound.go @@ -34,7 +34,7 @@ func (k Keeper) InitiateOutbound(ctx sdk.Context, config InitiateOutboundConfig) ) } - cctxGateway, found := ResolveCCTXGateway(chainInfo.CctxGateway) + cctxGateway, found := ResolveCCTXGateway(chainInfo.CctxGateway, k) if !found { return config.CCTX.CctxStatus.Status, cosmoserrors.Wrap( types.ErrInitiatitingOutbound, @@ -44,5 +44,5 @@ func (k Keeper) InitiateOutbound(ctx sdk.Context, config InitiateOutboundConfig) ) } - return cctxGateway.InitiateOutbound(ctx, config), nil + return cctxGateway.InitiateOutbound(ctx, config) } From a87f4cd8bb2d12b8b0a13711b793ae2f7c862af9 Mon Sep 17 00:00:00 2001 From: skosito Date: Wed, 12 Jun 2024 18:42:45 +0200 Subject: [PATCH 30/38] fix init outbound tests --- x/crosschain/keeper/initiate_outbound_test.go | 978 +++++++++--------- 1 file changed, 478 insertions(+), 500 deletions(-) diff --git a/x/crosschain/keeper/initiate_outbound_test.go b/x/crosschain/keeper/initiate_outbound_test.go index 8441d389da..ffeb8d9ad2 100644 --- a/x/crosschain/keeper/initiate_outbound_test.go +++ b/x/crosschain/keeper/initiate_outbound_test.go @@ -1,502 +1,480 @@ package keeper_test -// import ( -// "fmt" -// "math/big" -// "testing" - -// sdkmath "cosmossdk.io/math" -// "github.com/stretchr/testify/mock" -// "github.com/stretchr/testify/require" - -// "github.com/zeta-chain/zetacore/pkg/chains" -// "github.com/zeta-chain/zetacore/pkg/coin" -// keepertest "github.com/zeta-chain/zetacore/testutil/keeper" -// "github.com/zeta-chain/zetacore/testutil/sample" -// "github.com/zeta-chain/zetacore/x/crosschain/keeper" -// "github.com/zeta-chain/zetacore/x/crosschain/types" -// fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" -// observertypes "github.com/zeta-chain/zetacore/x/observer/types" -// ) - -// func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) { -// t.Run("process zevm deposit successfully", func(t *testing.T) { -// k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ -// UseFungibleMock: true, -// }) - -// // Setup mock data -// fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) -// receiver := sample.EthAddress() -// amount := big.NewInt(42) - -// // expect DepositCoinZeta to be called -// fungibleMock.On("ZETADepositAndCallContract", mock.Anything, -// mock.Anything, -// receiver, int64(0), amount, mock.Anything, mock.Anything).Return(nil, nil) - -// // call InitiateOutbound -// cctx := sample.CrossChainTx(t, "test") -// cctx.CctxStatus = &types.Status{Status: types.CctxStatus_PendingInbound} -// cctx.GetCurrentOutboundParam().Receiver = receiver.String() -// cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId -// cctx.GetInboundParams().Amount = sdkmath.NewUintFromBigInt(amount) -// cctx.InboundParams.CoinType = coin.CoinType_Zeta -// cctx.GetInboundParams().SenderChainId = 0 -// newStatus, err := k.InitiateOutbound(ctx, cctx) -// require.NoError(t, err) -// require.Equal(t, types.CctxStatus_OutboundMined, cctx.CctxStatus.Status) -// require.Equal(t, types.CctxStatus_OutboundMined, newStatus) -// }) - -// t.Run("unable to process zevm deposit HandleEVMDeposit returns err without reverting", func(t *testing.T) { -// k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ -// UseFungibleMock: true, -// }) - -// // Setup mock data -// fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) -// receiver := sample.EthAddress() -// amount := big.NewInt(42) - -// // mock unsuccessful HandleEVMDeposit which does not revert - -// fungibleMock.On("ZETADepositAndCallContract", mock.Anything, mock.Anything, receiver, int64(0), amount, mock.Anything, mock.Anything). -// Return(nil, fmt.Errorf("deposit error")) - -// // call InitiateOutbound -// cctx := sample.CrossChainTx(t, "test") -// cctx.CctxStatus = &types.Status{Status: types.CctxStatus_PendingInbound} -// cctx.GetCurrentOutboundParam().Receiver = receiver.String() -// cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId -// cctx.GetInboundParams().Amount = sdkmath.NewUintFromBigInt(amount) -// cctx.InboundParams.CoinType = coin.CoinType_Zeta -// cctx.GetInboundParams().SenderChainId = 0 -// newStatus, err := k.InitiateOutbound(ctx, cctx) -// require.NoError(t, err) -// require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) -// require.Equal(t, types.CctxStatus_Aborted, newStatus) -// require.Equal(t, "deposit error", cctx.CctxStatus.StatusMessage) -// }) - -// t.Run( -// "unable to process zevm deposit HandleEVMDeposit reverts fails at GetSupportedChainFromChainID", -// func(t *testing.T) { -// k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ -// UseFungibleMock: true, -// UseObserverMock: true, -// }) - -// // Setup mock data -// fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) -// observerMock := keepertest.GetCrosschainObserverMock(t, k) -// receiver := sample.EthAddress() -// amount := big.NewInt(42) -// senderChain := getValidEthChain() -// errDeposit := fmt.Errorf("deposit failed") - -// // Setup expected calls -// // mock unsuccessful HandleEVMDeposit which reverts , i.e returns err and isContractReverted = true -// keepertest.MockRevertForHandleEVMDeposit(fungibleMock, receiver, amount, senderChain.ChainId, errDeposit) - -// // mock unsuccessful GetSupportedChainFromChainID -// observerMock.On("GetSupportedChainFromChainID", mock.Anything, senderChain.ChainId). -// Return(nil) - -// // call InitiateOutbound -// cctx := GetERC20Cctx(t, receiver, *senderChain, "", amount) -// cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId -// newStatus, err := k.InitiateOutbound(ctx, cctx) -// require.NoError(t, err) -// require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) -// require.Equal(t, types.CctxStatus_Aborted, newStatus) -// require.Equal( -// t, -// fmt.Sprintf("chain not supported"), -// cctx.CctxStatus.StatusMessage, -// ) -// }, -// ) - -// t.Run("unable to process zevm deposit HandleEVMDeposit revert fails at GetRevertGasLimit", func(t *testing.T) { -// k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ -// UseFungibleMock: true, -// UseObserverMock: true, -// }) - -// // Setup mock data -// fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) -// observerMock := keepertest.GetCrosschainObserverMock(t, k) -// receiver := sample.EthAddress() -// amount := big.NewInt(42) -// senderChain := getValidEthChain() -// asset := "" -// errDeposit := fmt.Errorf("deposit failed") - -// // Setup expected calls -// keepertest.MockRevertForHandleEVMDeposit(fungibleMock, receiver, amount, senderChain.ChainId, errDeposit) - -// // Mock successful GetSupportedChainFromChainID -// keepertest.MockGetSupportedChainFromChainID(observerMock, senderChain) - -// // mock unsuccessful GetRevertGasLimit for ERC20 -// fungibleMock.On("GetForeignCoinFromAsset", mock.Anything, asset, senderChain.ChainId). -// Return(fungibletypes.ForeignCoins{}, false) - -// // call InitiateOutbound -// cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) -// cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId -// newStatus, err := k.InitiateOutbound(ctx, cctx) -// require.NoError(t, err) -// require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) -// require.Equal(t, types.CctxStatus_Aborted, newStatus) -// require.Equal( -// t, -// "GetRevertGasLimit: foreign coin not found for sender chain", -// cctx.CctxStatus.StatusMessage, -// ) -// }) - -// t.Run("unable to process zevm deposit HandleEVMDeposit revert fails at PayGasInERC20AndUpdateCctx", -// func(t *testing.T) { -// k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ -// UseFungibleMock: true, -// UseObserverMock: true, -// }) - -// // Setup mock data -// fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) -// receiver := sample.EthAddress() -// amount := big.NewInt(42) -// senderChain := getValidEthChain() -// asset := "" - -// // Setup expected calls -// errDeposit := fmt.Errorf("deposit failed") -// keepertest.MockRevertForHandleEVMDeposit(fungibleMock, receiver, amount, senderChain.ChainId, errDeposit) - -// observerMock := keepertest.GetCrosschainObserverMock(t, k) - -// // Mock successful GetSupportedChainFromChainID -// keepertest.MockGetSupportedChainFromChainID(observerMock, senderChain) - -// // mock successful GetRevertGasLimit for ERC20 -// keepertest.MockGetRevertGasLimitForERC20(fungibleMock, asset, *senderChain, 100) - -// // mock unsuccessful PayGasInERC20AndUpdateCctx -// observerMock.On("GetSupportedChainFromChainID", mock.Anything, senderChain.ChainId). -// Return(nil).Once() - -// // call InitiateOutbound -// cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) -// cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId -// newStatus, err := k.InitiateOutbound(ctx, cctx) -// require.NoError(t, err) -// require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) -// require.Equal(t, types.CctxStatus_Aborted, newStatus) -// require.Equal( -// t, -// "chain not supported", -// cctx.CctxStatus.StatusMessage, -// ) -// }, -// ) - -// t.Run( -// "uunable to process zevm deposit HandleEVMDeposit revert fails at PayGasInERC20AndUpdateCctx with gas limit is 0", -// func(t *testing.T) { -// k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ -// UseFungibleMock: true, -// UseObserverMock: true, -// }) - -// // Setup mock data -// fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) -// receiver := sample.EthAddress() -// amount := big.NewInt(42) -// senderChain := getValidEthChain() -// asset := "" - -// // Setup expected calls -// errDeposit := fmt.Errorf("deposit failed") -// keepertest.MockRevertForHandleEVMDeposit(fungibleMock, receiver, amount, senderChain.ChainId, errDeposit) - -// observerMock := keepertest.GetCrosschainObserverMock(t, k) - -// // Mock successful GetSupportedChainFromChainID -// keepertest.MockGetSupportedChainFromChainID(observerMock, senderChain) - -// // mock successful GetRevertGasLimit for ERC20 -// keepertest.MockGetRevertGasLimitForERC20(fungibleMock, asset, *senderChain, 0) - -// // mock unsuccessful PayGasInERC20AndUpdateCctx -// observerMock.On("GetSupportedChainFromChainID", mock.Anything, senderChain.ChainId). -// Return(nil).Once() - -// // call InitiateOutbound -// cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) -// cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId -// newStatus, err := k.InitiateOutbound(ctx, cctx) -// require.NoError(t, err) -// require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) -// require.Equal(t, types.CctxStatus_Aborted, newStatus) -// require.Equal( -// t, -// fmt.Sprintf("chain not supported"), -// cctx.CctxStatus.StatusMessage, -// ) -// }, -// ) - -// t.Run("unable to process zevm deposit HandleEVMDeposit reverts fails at UpdateNonce", func(t *testing.T) { -// k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ -// UseFungibleMock: true, -// UseObserverMock: true, -// }) - -// // Setup mock data -// fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) -// observerMock := keepertest.GetCrosschainObserverMock(t, k) -// receiver := sample.EthAddress() -// amount := big.NewInt(42) -// senderChain := getValidEthChain() -// asset := "" -// errDeposit := fmt.Errorf("deposit failed") - -// // Setup expected calls -// // mock unsuccessful HandleEVMDeposit which reverts , i.e returns err and isContractReverted = true -// keepertest.MockRevertForHandleEVMDeposit(fungibleMock, receiver, amount, senderChain.ChainId, errDeposit) - -// // Mock successful GetSupportedChainFromChainID -// keepertest.MockGetSupportedChainFromChainID(observerMock, senderChain) - -// // mock successful GetRevertGasLimit for ERC20 -// keepertest.MockGetRevertGasLimitForERC20(fungibleMock, asset, *senderChain, 100) - -// // mock successful PayGasAndUpdateCctx -// keepertest.MockPayGasAndUpdateCCTX(fungibleMock, observerMock, ctx, *k, *senderChain, asset) - -// // Mock unsuccessful UpdateNonce -// observerMock.On("GetChainNonces", mock.Anything, senderChain.ChainName.String()). -// Return(observertypes.ChainNonces{}, false) - -// // call InitiateOutbound -// cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) -// cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId -// newStatus, err := k.InitiateOutbound(ctx, cctx) -// require.NoError(t, err) -// require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) -// require.Equal(t, types.CctxStatus_Aborted, newStatus) -// require.Contains(t, cctx.CctxStatus.StatusMessage, "cannot find receiver chain nonce") -// }) - -// t.Run("unable to process zevm deposit HandleEVMDeposit revert successfully", func(t *testing.T) { -// k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ -// UseFungibleMock: true, -// UseObserverMock: true, -// }) - -// // Setup mock data -// fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) -// observerMock := keepertest.GetCrosschainObserverMock(t, k) -// receiver := sample.EthAddress() -// amount := big.NewInt(42) -// senderChain := getValidEthChain() -// asset := "" -// errDeposit := fmt.Errorf("deposit failed") - -// // Setup expected calls -// // mock unsuccessful HandleEVMDeposit which reverts , i.e returns err and isContractReverted = true -// keepertest.MockRevertForHandleEVMDeposit(fungibleMock, receiver, amount, senderChain.ChainId, errDeposit) - -// // Mock successful GetSupportedChainFromChainID -// keepertest.MockGetSupportedChainFromChainID(observerMock, senderChain) - -// // mock successful GetRevertGasLimit for ERC20 -// keepertest.MockGetRevertGasLimitForERC20(fungibleMock, asset, *senderChain, 100) - -// // mock successful PayGasAndUpdateCctx -// keepertest.MockPayGasAndUpdateCCTX(fungibleMock, observerMock, ctx, *k, *senderChain, asset) -// // mock successful UpdateNonce -// updatedNonce := keepertest.MockUpdateNonce(observerMock, *senderChain) - -// // call InitiateOutbound -// cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) -// cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId -// newStatus, err := k.InitiateOutbound(ctx, cctx) -// require.NoError(t, err) -// require.Equal(t, types.CctxStatus_PendingRevert, cctx.CctxStatus.Status) -// require.Equal(t, types.CctxStatus_PendingRevert, newStatus) -// require.Equal(t, errDeposit.Error(), cctx.CctxStatus.StatusMessage) -// require.Equal(t, updatedNonce, cctx.GetCurrentOutboundParam().TssNonce) -// }) - -// t.Run("unable to process zevm deposit HandleEVMDeposit revert fails as the cctx has already been reverted", -// func(t *testing.T) { -// k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ -// UseFungibleMock: true, -// UseObserverMock: true, -// }) - -// // Setup mock data -// fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) -// observerMock := keepertest.GetCrosschainObserverMock(t, k) -// receiver := sample.EthAddress() -// amount := big.NewInt(42) -// senderChain := getValidEthChain() -// asset := "" -// errDeposit := fmt.Errorf("deposit failed") - -// // Setup expected calls -// // mock unsuccessful HandleEVMDeposit which reverts , i.e returns err and isContractReverted = true -// keepertest.MockRevertForHandleEVMDeposit(fungibleMock, receiver, amount, senderChain.ChainId, errDeposit) - -// // Mock successful GetSupportedChainFromChainID -// keepertest.MockGetSupportedChainFromChainID(observerMock, senderChain) - -// // mock successful GetRevertGasLimit for ERC20 -// keepertest.MockGetRevertGasLimitForERC20(fungibleMock, asset, *senderChain, 100) - -// // call InitiateOutbound -// cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) -// cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId -// cctx.OutboundParams = append(cctx.OutboundParams, cctx.GetCurrentOutboundParam()) -// newStatus, err := k.InitiateOutbound(ctx, cctx) -// require.NoError(t, err) -// require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) -// require.Equal(t, types.CctxStatus_Aborted, newStatus) -// require.Contains( -// t, -// cctx.CctxStatus.StatusMessage, -// fmt.Sprintf("cannot revert a revert tx"), -// ) -// }, -// ) -// } - -// func TestKeeper_InitiateOutboundProcessCrosschainMsgPassing(t *testing.T) { -// t.Run("process crosschain msg passing successfully", func(t *testing.T) { -// k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ -// UseFungibleMock: true, -// UseObserverMock: true, -// }) - -// // Setup mock data -// fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) -// observerMock := keepertest.GetCrosschainObserverMock(t, k) -// receiver := sample.EthAddress() -// amount := big.NewInt(42) -// receiverChain := getValidEthChain() - -// // mock successful PayGasAndUpdateCctx -// keepertest.MockPayGasAndUpdateCCTX(fungibleMock, observerMock, ctx, *k, *receiverChain, "") - -// // mock successful UpdateNonce -// updatedNonce := keepertest.MockUpdateNonce(observerMock, *receiverChain) - -// // call InitiateOutbound -// cctx := GetERC20Cctx(t, receiver, *receiverChain, "", amount) -// newStatus, err := k.InitiateOutbound(ctx, cctx) -// require.NoError(t, err) -// require.Equal(t, types.CctxStatus_PendingOutbound, cctx.CctxStatus.Status) -// require.Equal(t, types.CctxStatus_PendingOutbound, newStatus) -// require.Equal(t, updatedNonce, cctx.GetCurrentOutboundParam().TssNonce) -// }) - -// t.Run("unable to process crosschain msg passing PayGasAndUpdateCctx fails", func(t *testing.T) { -// k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ -// UseFungibleMock: true, -// UseObserverMock: true, -// }) - -// // Setup mock data -// observerMock := keepertest.GetCrosschainObserverMock(t, k) -// receiver := sample.EthAddress() -// amount := big.NewInt(42) -// receiverChain := getValidEthChain() - -// // mock unsuccessful PayGasAndUpdateCctx -// observerMock.On("GetSupportedChainFromChainID", mock.Anything, receiverChain.ChainId). -// Return(nil).Once() - -// // call InitiateOutbound -// cctx := GetERC20Cctx(t, receiver, *receiverChain, "", amount) -// newStatus, err := k.InitiateOutbound(ctx, cctx) -// require.NoError(t, err) -// require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) -// require.Equal(t, types.CctxStatus_Aborted, newStatus) -// require.Equal(t, observertypes.ErrSupportedChains.Error(), cctx.CctxStatus.StatusMessage) -// }) - -// t.Run("unable to process crosschain msg passing UpdateNonce fails", func(t *testing.T) { -// k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ -// UseFungibleMock: true, -// UseObserverMock: true, -// }) - -// // Setup mock data -// fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) -// observerMock := keepertest.GetCrosschainObserverMock(t, k) -// receiver := sample.EthAddress() -// amount := big.NewInt(42) -// receiverChain := getValidEthChain() - -// // mock successful PayGasAndUpdateCctx -// keepertest.MockPayGasAndUpdateCCTX(fungibleMock, observerMock, ctx, *k, *receiverChain, "") - -// // mock unsuccessful UpdateNonce -// observerMock.On("GetChainNonces", mock.Anything, receiverChain.ChainName.String()). -// Return(observertypes.ChainNonces{}, false) - -// // call InitiateOutbound -// cctx := GetERC20Cctx(t, receiver, *receiverChain, "", amount) -// newStatus, err := k.InitiateOutbound(ctx, cctx) -// require.NoError(t, err) -// require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) -// require.Equal(t, types.CctxStatus_Aborted, newStatus) -// require.Contains(t, cctx.CctxStatus.StatusMessage, "cannot find receiver chain nonce") -// }) -// } - -// func TestKeeper_InitiateOutboundFailures(t *testing.T) { -// t.Run("should fail if chain info can not be found for receiver chain id", func(t *testing.T) { -// k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ -// UseFungibleMock: true, -// UseObserverMock: true, -// }) - -// // Setup mock data -// receiver := sample.EthAddress() -// amount := big.NewInt(42) -// receiverChain := getValidEthChain() -// receiverChain.ChainId = 123 -// // call InitiateOutbound -// cctx := GetERC20Cctx(t, receiver, *receiverChain, "", amount) -// newStatus, err := k.InitiateOutbound(ctx, cctx) -// require.Error(t, err) -// require.Equal(t, types.CctxStatus_PendingInbound, newStatus) -// require.ErrorContains(t, err, "chain info not found") -// }) - -// t.Run("should fail if cctx gateway not found for receiver chain id", func(t *testing.T) { -// k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ -// UseFungibleMock: true, -// UseObserverMock: true, -// }) - -// // reset cctx gateways -// k.SetCCTXGateways(map[chains.CCTXGateway]keeper.CCTXGateway{}) - -// // Setup mock data -// receiver := sample.EthAddress() -// amount := big.NewInt(42) -// receiverChain := getValidEthChain() -// // call InitiateOutbound -// cctx := GetERC20Cctx(t, receiver, *receiverChain, "", amount) -// newStatus, err := k.InitiateOutbound(ctx, cctx) -// require.Equal(t, types.CctxStatus_PendingInbound, newStatus) -// require.NotNil(t, err) -// require.ErrorContains(t, err, "CCTXGateway not defined for receiver chain") -// }) - -// } +import ( + "fmt" + "math/big" + "testing" + + sdkmath "cosmossdk.io/math" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/zetacore/pkg/chains" + "github.com/zeta-chain/zetacore/pkg/coin" + keepertest "github.com/zeta-chain/zetacore/testutil/keeper" + "github.com/zeta-chain/zetacore/testutil/sample" + "github.com/zeta-chain/zetacore/x/crosschain/keeper" + "github.com/zeta-chain/zetacore/x/crosschain/types" + fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" +) + +func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) { + t.Run("process zevm deposit successfully", func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ + UseFungibleMock: true, + }) + + // Setup mock data + fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) + receiver := sample.EthAddress() + amount := big.NewInt(42) + + // expect DepositCoinZeta to be called + fungibleMock.On("ZETADepositAndCallContract", mock.Anything, + mock.Anything, + receiver, int64(0), amount, mock.Anything, mock.Anything).Return(nil, nil) + + // call InitiateOutbound + cctx := sample.CrossChainTx(t, "test") + cctx.CctxStatus = &types.Status{Status: types.CctxStatus_PendingInbound} + cctx.GetCurrentOutboundParam().Receiver = receiver.String() + cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId + cctx.GetInboundParams().Amount = sdkmath.NewUintFromBigInt(amount) + cctx.InboundParams.CoinType = coin.CoinType_Zeta + cctx.GetInboundParams().SenderChainId = 0 + newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, PayGas: true}) + require.NoError(t, err) + require.Equal(t, types.CctxStatus_OutboundMined, cctx.CctxStatus.Status) + require.Equal(t, types.CctxStatus_OutboundMined, newStatus) + }) + + t.Run("unable to process zevm deposit HandleEVMDeposit returns err without reverting", func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ + UseFungibleMock: true, + }) + + // Setup mock data + fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) + receiver := sample.EthAddress() + amount := big.NewInt(42) + + // mock unsuccessful HandleEVMDeposit which does not revert + + fungibleMock.On("ZETADepositAndCallContract", mock.Anything, mock.Anything, receiver, int64(0), amount, mock.Anything, mock.Anything). + Return(nil, fmt.Errorf("deposit error")) + + // call InitiateOutbound + cctx := sample.CrossChainTx(t, "test") + cctx.CctxStatus = &types.Status{Status: types.CctxStatus_PendingInbound} + cctx.GetCurrentOutboundParam().Receiver = receiver.String() + cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId + cctx.GetInboundParams().Amount = sdkmath.NewUintFromBigInt(amount) + cctx.InboundParams.CoinType = coin.CoinType_Zeta + cctx.GetInboundParams().SenderChainId = 0 + newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, PayGas: true}) + require.ErrorContains(t, err, "deposit error") + require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) + require.Equal(t, types.CctxStatus_Aborted, newStatus) + require.Equal(t, "deposit error", cctx.CctxStatus.StatusMessage) + }) + + t.Run( + "unable to process zevm deposit HandleEVMDeposit reverts fails at GetSupportedChainFromChainID", + func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ + UseFungibleMock: true, + UseObserverMock: true, + }) + + // Setup mock data + fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) + observerMock := keepertest.GetCrosschainObserverMock(t, k) + receiver := sample.EthAddress() + amount := big.NewInt(42) + senderChain := getValidEthChain() + errDeposit := fmt.Errorf("deposit failed") + + // Setup expected calls + // mock unsuccessful HandleEVMDeposit which reverts , i.e returns err and isContractReverted = true + keepertest.MockRevertForHandleEVMDeposit(fungibleMock, receiver, amount, senderChain.ChainId, errDeposit) + + // mock unsuccessful GetSupportedChainFromChainID + observerMock.On("GetSupportedChainFromChainID", mock.Anything, senderChain.ChainId). + Return(nil) + + // call InitiateOutbound + cctx := GetERC20Cctx(t, receiver, *senderChain, "", amount) + cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId + newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, PayGas: true}) + require.NoError(t, err) + require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) + require.Equal(t, types.CctxStatus_Aborted, newStatus) + require.Equal( + t, + "chain not supported", + cctx.CctxStatus.StatusMessage, + ) + }, + ) + + t.Run("unable to process zevm deposit HandleEVMDeposit revert fails at GetRevertGasLimit", func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ + UseFungibleMock: true, + UseObserverMock: true, + }) + + // Setup mock data + fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) + observerMock := keepertest.GetCrosschainObserverMock(t, k) + receiver := sample.EthAddress() + amount := big.NewInt(42) + senderChain := getValidEthChain() + asset := "" + errDeposit := fmt.Errorf("deposit failed") + + // Setup expected calls + keepertest.MockRevertForHandleEVMDeposit(fungibleMock, receiver, amount, senderChain.ChainId, errDeposit) + + // Mock successful GetSupportedChainFromChainID + keepertest.MockGetSupportedChainFromChainID(observerMock, senderChain) + + // mock unsuccessful GetRevertGasLimit for ERC20 + fungibleMock.On("GetForeignCoinFromAsset", mock.Anything, asset, senderChain.ChainId). + Return(fungibletypes.ForeignCoins{}, false) + + // call InitiateOutbound + cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) + cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId + newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, PayGas: true}) + require.NoError(t, err) + require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) + require.Equal(t, types.CctxStatus_Aborted, newStatus) + require.Equal( + t, + "GetRevertGasLimit: foreign coin not found for sender chain", + cctx.CctxStatus.StatusMessage, + ) + }) + + t.Run("unable to process zevm deposit HandleEVMDeposit revert fails at PayGasInERC20AndUpdateCctx", + func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ + UseFungibleMock: true, + UseObserverMock: true, + }) + + // Setup mock data + fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) + receiver := sample.EthAddress() + amount := big.NewInt(42) + senderChain := getValidEthChain() + asset := "" + + // Setup expected calls + errDeposit := fmt.Errorf("deposit failed") + keepertest.MockRevertForHandleEVMDeposit(fungibleMock, receiver, amount, senderChain.ChainId, errDeposit) + + observerMock := keepertest.GetCrosschainObserverMock(t, k) + + // Mock successful GetSupportedChainFromChainID + keepertest.MockGetSupportedChainFromChainID(observerMock, senderChain) + + // mock successful GetRevertGasLimit for ERC20 + keepertest.MockGetRevertGasLimitForERC20(fungibleMock, asset, *senderChain, 100) + + // mock unsuccessful PayGasInERC20AndUpdateCctx + observerMock.On("GetSupportedChainFromChainID", mock.Anything, senderChain.ChainId). + Return(nil).Once() + + // call InitiateOutbound + cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) + cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId + newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, PayGas: true}) + require.NoError(t, err) + require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) + require.Equal(t, types.CctxStatus_Aborted, newStatus) + require.Equal( + t, + "chain not supported", + cctx.CctxStatus.StatusMessage, + ) + }, + ) + + t.Run( + "uunable to process zevm deposit HandleEVMDeposit revert fails at PayGasInERC20AndUpdateCctx with gas limit is 0", + func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ + UseFungibleMock: true, + UseObserverMock: true, + }) + + // Setup mock data + fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) + receiver := sample.EthAddress() + amount := big.NewInt(42) + senderChain := getValidEthChain() + asset := "" + + // Setup expected calls + errDeposit := fmt.Errorf("deposit failed") + keepertest.MockRevertForHandleEVMDeposit(fungibleMock, receiver, amount, senderChain.ChainId, errDeposit) + + observerMock := keepertest.GetCrosschainObserverMock(t, k) + + // Mock successful GetSupportedChainFromChainID + keepertest.MockGetSupportedChainFromChainID(observerMock, senderChain) + + // mock successful GetRevertGasLimit for ERC20 + keepertest.MockGetRevertGasLimitForERC20(fungibleMock, asset, *senderChain, 0) + + // mock unsuccessful PayGasInERC20AndUpdateCctx + observerMock.On("GetSupportedChainFromChainID", mock.Anything, senderChain.ChainId). + Return(nil).Once() + + // call InitiateOutbound + cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) + cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId + newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, PayGas: true}) + require.NoError(t, err) + require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) + require.Equal(t, types.CctxStatus_Aborted, newStatus) + require.Equal( + t, + "chain not supported", + cctx.CctxStatus.StatusMessage, + ) + }, + ) + + t.Run("unable to process zevm deposit HandleEVMDeposit reverts fails at UpdateNonce", func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ + UseFungibleMock: true, + UseObserverMock: true, + }) + + // Setup mock data + fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) + observerMock := keepertest.GetCrosschainObserverMock(t, k) + receiver := sample.EthAddress() + amount := big.NewInt(42) + senderChain := getValidEthChain() + asset := "" + errDeposit := fmt.Errorf("deposit failed") + + // Setup expected calls + // mock unsuccessful HandleEVMDeposit which reverts , i.e returns err and isContractReverted = true + keepertest.MockRevertForHandleEVMDeposit(fungibleMock, receiver, amount, senderChain.ChainId, errDeposit) + + // Mock successful GetSupportedChainFromChainID + keepertest.MockGetSupportedChainFromChainID(observerMock, senderChain) + + // mock successful GetRevertGasLimit for ERC20 + keepertest.MockGetRevertGasLimitForERC20(fungibleMock, asset, *senderChain, 100) + + // mock successful PayGasAndUpdateCctx + keepertest.MockPayGasAndUpdateCCTX(fungibleMock, observerMock, ctx, *k, *senderChain, asset) + + // Mock unsuccessful UpdateNonce + observerMock.On("GetChainNonces", mock.Anything, senderChain.ChainName.String()). + Return(observertypes.ChainNonces{}, false) + + // call InitiateOutbound + cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) + cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId + newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, PayGas: true}) + require.NoError(t, err) + require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) + require.Equal(t, types.CctxStatus_Aborted, newStatus) + require.Contains(t, cctx.CctxStatus.StatusMessage, "cannot find receiver chain nonce") + }) + + t.Run("unable to process zevm deposit HandleEVMDeposit revert successfully", func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ + UseFungibleMock: true, + UseObserverMock: true, + }) + + // Setup mock data + fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) + observerMock := keepertest.GetCrosschainObserverMock(t, k) + receiver := sample.EthAddress() + amount := big.NewInt(42) + senderChain := getValidEthChain() + asset := "" + errDeposit := fmt.Errorf("deposit failed") + + // Setup expected calls + // mock unsuccessful HandleEVMDeposit which reverts , i.e returns err and isContractReverted = true + keepertest.MockRevertForHandleEVMDeposit(fungibleMock, receiver, amount, senderChain.ChainId, errDeposit) + + // Mock successful GetSupportedChainFromChainID + keepertest.MockGetSupportedChainFromChainID(observerMock, senderChain) + + // mock successful GetRevertGasLimit for ERC20 + keepertest.MockGetRevertGasLimitForERC20(fungibleMock, asset, *senderChain, 100) + + // mock successful PayGasAndUpdateCctx + keepertest.MockPayGasAndUpdateCCTX(fungibleMock, observerMock, ctx, *k, *senderChain, asset) + // mock successful UpdateNonce + updatedNonce := keepertest.MockUpdateNonce(observerMock, *senderChain) + + // call InitiateOutbound + cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) + cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId + newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, PayGas: true}) + require.NoError(t, err) + require.Equal(t, types.CctxStatus_PendingRevert, cctx.CctxStatus.Status) + require.Equal(t, types.CctxStatus_PendingRevert, newStatus) + require.Equal(t, errDeposit.Error(), cctx.CctxStatus.StatusMessage) + require.Equal(t, updatedNonce, cctx.GetCurrentOutboundParam().TssNonce) + }) + + t.Run("unable to process zevm deposit HandleEVMDeposit revert fails as the cctx has already been reverted", + func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ + UseFungibleMock: true, + UseObserverMock: true, + }) + + // Setup mock data + fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) + observerMock := keepertest.GetCrosschainObserverMock(t, k) + receiver := sample.EthAddress() + amount := big.NewInt(42) + senderChain := getValidEthChain() + asset := "" + errDeposit := fmt.Errorf("deposit failed") + + // Setup expected calls + // mock unsuccessful HandleEVMDeposit which reverts , i.e returns err and isContractReverted = true + keepertest.MockRevertForHandleEVMDeposit(fungibleMock, receiver, amount, senderChain.ChainId, errDeposit) + + // Mock successful GetSupportedChainFromChainID + keepertest.MockGetSupportedChainFromChainID(observerMock, senderChain) + + // mock successful GetRevertGasLimit for ERC20 + keepertest.MockGetRevertGasLimitForERC20(fungibleMock, asset, *senderChain, 100) + + // call InitiateOutbound + cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) + cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId + cctx.OutboundParams = append(cctx.OutboundParams, cctx.GetCurrentOutboundParam()) + newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, PayGas: true}) + require.NoError(t, err) + require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) + require.Equal(t, types.CctxStatus_Aborted, newStatus) + require.Contains( + t, + cctx.CctxStatus.StatusMessage, + "cannot revert a revert tx", + ) + }, + ) +} + +func TestKeeper_InitiateOutboundProcessCrosschainMsgPassing(t *testing.T) { + t.Run("process crosschain msg passing successfully", func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ + UseFungibleMock: true, + UseObserverMock: true, + }) + + // Setup mock data + fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) + observerMock := keepertest.GetCrosschainObserverMock(t, k) + receiver := sample.EthAddress() + amount := big.NewInt(42) + receiverChain := getValidEthChain() + + // mock successful PayGasAndUpdateCctx + keepertest.MockPayGasAndUpdateCCTX(fungibleMock, observerMock, ctx, *k, *receiverChain, "") + + // mock successful UpdateNonce + updatedNonce := keepertest.MockUpdateNonce(observerMock, *receiverChain) + + // call InitiateOutbound + cctx := GetERC20Cctx(t, receiver, *receiverChain, "", amount) + newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, PayGas: true}) + require.NoError(t, err) + require.Equal(t, types.CctxStatus_PendingOutbound, cctx.CctxStatus.Status) + require.Equal(t, types.CctxStatus_PendingOutbound, newStatus) + require.Equal(t, updatedNonce, cctx.GetCurrentOutboundParam().TssNonce) + }) + + t.Run("unable to process crosschain msg passing PayGasAndUpdateCctx fails", func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ + UseFungibleMock: true, + UseObserverMock: true, + }) + + // Setup mock data + observerMock := keepertest.GetCrosschainObserverMock(t, k) + receiver := sample.EthAddress() + amount := big.NewInt(42) + receiverChain := getValidEthChain() + + // mock unsuccessful PayGasAndUpdateCctx + observerMock.On("GetSupportedChainFromChainID", mock.Anything, receiverChain.ChainId). + Return(nil).Once() + + // call InitiateOutbound + cctx := GetERC20Cctx(t, receiver, *receiverChain, "", amount) + newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, PayGas: true}) + require.ErrorIs(t, err, observertypes.ErrSupportedChains) + require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) + require.Equal(t, types.CctxStatus_Aborted, newStatus) + require.Equal(t, observertypes.ErrSupportedChains.Error(), cctx.CctxStatus.StatusMessage) + }) + + t.Run("unable to process crosschain msg passing UpdateNonce fails", func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ + UseFungibleMock: true, + UseObserverMock: true, + }) + + // Setup mock data + fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) + observerMock := keepertest.GetCrosschainObserverMock(t, k) + receiver := sample.EthAddress() + amount := big.NewInt(42) + receiverChain := getValidEthChain() + + // mock successful PayGasAndUpdateCctx + keepertest.MockPayGasAndUpdateCCTX(fungibleMock, observerMock, ctx, *k, *receiverChain, "") + + // mock unsuccessful UpdateNonce + observerMock.On("GetChainNonces", mock.Anything, receiverChain.ChainName.String()). + Return(observertypes.ChainNonces{}, false) + + // call InitiateOutbound + cctx := GetERC20Cctx(t, receiver, *receiverChain, "", amount) + newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, PayGas: true}) + require.ErrorContains(t, err, "cannot find receiver chain nonce") + require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) + require.Equal(t, types.CctxStatus_Aborted, newStatus) + require.Contains(t, cctx.CctxStatus.StatusMessage, "cannot find receiver chain nonce") + }) +} + +func TestKeeper_InitiateOutboundFailures(t *testing.T) { + t.Run("should fail if chain info can not be found for receiver chain id", func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ + UseFungibleMock: true, + UseObserverMock: true, + }) + + // Setup mock data + receiver := sample.EthAddress() + amount := big.NewInt(42) + receiverChain := getValidEthChain() + receiverChain.ChainId = 123 + // call InitiateOutbound + cctx := GetERC20Cctx(t, receiver, *receiverChain, "", amount) + newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, PayGas: true}) + require.Error(t, err) + require.Equal(t, types.CctxStatus_PendingInbound, newStatus) + require.ErrorContains(t, err, "chain info not found") + }) +} From 1dbfdb9a08f7d7acdb70bba750a8839b7e634f5e Mon Sep 17 00:00:00 2001 From: skosito Date: Wed, 12 Jun 2024 18:55:34 +0200 Subject: [PATCH 31/38] cleanup --- testutil/keeper/crosschain.go | 8 -------- x/crosschain/keeper/keeper.go | 12 +++--------- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/testutil/keeper/crosschain.go b/testutil/keeper/crosschain.go index 693234ab77..d296678543 100644 --- a/testutil/keeper/crosschain.go +++ b/testutil/keeper/crosschain.go @@ -174,16 +174,8 @@ func CrosschainKeeperWithMocks( lightclientKeeper, ) - cctxGateways := map[chains.CCTXGateway]keeper.CCTXGateway{ - chains.CCTXGateway_observers: keeper.NewCCTXGatewayObservers(*k), - chains.CCTXGateway_zevm: keeper.NewCCTXGatewayZEVM(*k), - } - - k.SetCCTXGateways(cctxGateways) - // initialize ibccrosschain keeper and set it to the crosschain keeper // there is a circular dependency between the two keepers, crosschain keeper must be initialized first - var ibcCrosschainKeeperTmp types.IBCCrosschainKeeper = initIBCCrosschainKeeper( cdc, db, diff --git a/x/crosschain/keeper/keeper.go b/x/crosschain/keeper/keeper.go index fb42e9e90e..fc483689b3 100644 --- a/x/crosschain/keeper/keeper.go +++ b/x/crosschain/keeper/keeper.go @@ -8,16 +8,14 @@ import ( storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/x/crosschain/types" ) type ( Keeper struct { - cdc codec.Codec - storeKey storetypes.StoreKey - memKey storetypes.StoreKey - cctxGateways map[chains.CCTXGateway]CCTXGateway + cdc codec.Codec + storeKey storetypes.StoreKey + memKey storetypes.StoreKey stakingKeeper types.StakingKeeper authKeeper types.AccountKeeper @@ -102,10 +100,6 @@ func (k *Keeper) SetIBCCrosschainKeeper(ibcCrosschainKeeper types.IBCCrosschainK k.ibcCrosschainKeeper = ibcCrosschainKeeper } -func (k *Keeper) SetCCTXGateways(cctxGateways map[chains.CCTXGateway]CCTXGateway) { - k.cctxGateways = cctxGateways -} - func (k Keeper) GetStoreKey() storetypes.StoreKey { return k.storeKey } From 4e468f78b759e63773bf374e33100cbb44352082 Mon Sep 17 00:00:00 2001 From: skosito Date: Wed, 12 Jun 2024 19:17:38 +0200 Subject: [PATCH 32/38] cleanup --- app/app.go | 2 -- x/crosschain/keeper/cctx_gateways.go | 10 +--------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/app/app.go b/app/app.go index 83a3c5b1bf..0688ca75db 100644 --- a/app/app.go +++ b/app/app.go @@ -597,8 +597,6 @@ func New( app.LightclientKeeper, ) - crosschainkeeper.InitCCTXGateways(app.CrosschainKeeper) - // initialize ibccrosschain keeper and set it to the crosschain keeper // there is a circular dependency between the two keepers, crosschain keeper must be initialized first diff --git a/x/crosschain/keeper/cctx_gateways.go b/x/crosschain/keeper/cctx_gateways.go index 621ed7f7c5..9f8e79c0d9 100644 --- a/x/crosschain/keeper/cctx_gateways.go +++ b/x/crosschain/keeper/cctx_gateways.go @@ -17,15 +17,7 @@ type CCTXGateway interface { var cctxGateways map[chains.CCTXGateway]CCTXGateway -// initializing map of cctx gateways so crosschain module can decide which one to use -// based on chain info of destination chain -func InitCCTXGateways(keeper Keeper) { - cctxGateways = map[chains.CCTXGateway]CCTXGateway{ - chains.CCTXGateway_observers: NewCCTXGatewayObservers(keeper), - chains.CCTXGateway_zevm: NewCCTXGatewayZEVM(keeper), - } -} - +// ResolveCCTXGateway respolves cctx gateway implementation based on provided cctx gateway func ResolveCCTXGateway(c chains.CCTXGateway, keeper Keeper) (CCTXGateway, bool) { cctxGateways = map[chains.CCTXGateway]CCTXGateway{ chains.CCTXGateway_observers: NewCCTXGatewayObservers(keeper), From dd56896441f9741cec16ec004e43a64e4832e177 Mon Sep 17 00:00:00 2001 From: skosito Date: Thu, 13 Jun 2024 20:50:39 +0200 Subject: [PATCH 33/38] PR comments --- x/crosschain/keeper/cctx_gateway_observers.go | 1 - x/crosschain/keeper/cctx_gateway_zevm.go | 1 - x/crosschain/keeper/cctx_orchestrator_validate_outbound.go | 2 +- x/crosschain/keeper/initiate_outbound.go | 1 + 4 files changed, 2 insertions(+), 3 deletions(-) diff --git a/x/crosschain/keeper/cctx_gateway_observers.go b/x/crosschain/keeper/cctx_gateway_observers.go index 556d242214..e9603b3903 100644 --- a/x/crosschain/keeper/cctx_gateway_observers.go +++ b/x/crosschain/keeper/cctx_gateway_observers.go @@ -55,6 +55,5 @@ func (c CCTXGatewayObservers) InitiateOutbound( return types.CctxStatus_Aborted } commit() - cctx.SetPendingOutbound("") return types.CctxStatus_PendingOutbound } diff --git a/x/crosschain/keeper/cctx_gateway_zevm.go b/x/crosschain/keeper/cctx_gateway_zevm.go index 5b61990215..c6cadf7f8f 100644 --- a/x/crosschain/keeper/cctx_gateway_zevm.go +++ b/x/crosschain/keeper/cctx_gateway_zevm.go @@ -29,7 +29,6 @@ func (c CCTXGatewayZEVM) InitiateOutbound(ctx sdk.Context, cctx *types.CrossChai return types.CctxStatus_Aborted } - cctx.SetPendingOutbound("") newCCTXStatus = c.crosschainKeeper.ValidateOutboundZEVM(ctx, cctx, err, isContractReverted) if newCCTXStatus == types.CctxStatus_OutboundMined { commit() diff --git a/x/crosschain/keeper/cctx_orchestrator_validate_outbound.go b/x/crosschain/keeper/cctx_orchestrator_validate_outbound.go index 083eb62ccb..16b61a3a0a 100644 --- a/x/crosschain/keeper/cctx_orchestrator_validate_outbound.go +++ b/x/crosschain/keeper/cctx_orchestrator_validate_outbound.go @@ -155,7 +155,7 @@ func (k Keeper) validateFailedOutbound( cctx *types.CrossChainTx, oldStatus types.CctxStatus, revertMsg string, - inputAmount math.Uint, // TODO: find different way for this + inputAmount math.Uint, ) error { switch oldStatus { case types.CctxStatus_PendingOutbound: diff --git a/x/crosschain/keeper/initiate_outbound.go b/x/crosschain/keeper/initiate_outbound.go index 8174f6ec4e..dc3fcf4c24 100644 --- a/x/crosschain/keeper/initiate_outbound.go +++ b/x/crosschain/keeper/initiate_outbound.go @@ -35,5 +35,6 @@ func (k Keeper) InitiateOutbound(ctx sdk.Context, cctx *types.CrossChainTx) (typ ) } + cctx.SetPendingOutbound("") return cctxGateway.InitiateOutbound(ctx, cctx), nil } From b823a45f979fead027e9d3294d9b1111119dc9e7 Mon Sep 17 00:00:00 2001 From: skosito Date: Mon, 17 Jun 2024 18:06:55 +0200 Subject: [PATCH 34/38] fix changelog --- changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index e86d476be6..2bd8db1e11 100644 --- a/changelog.md +++ b/changelog.md @@ -21,7 +21,6 @@ * [2279](https://github.com/zeta-chain/node/pull/2279) - add a CCTXGateway field to chain static data * [2275](https://github.com/zeta-chain/node/pull/2275) - add ChainInfo singleton state variable in authority * [2291](https://github.com/zeta-chain/node/pull/2291) - initialize cctx gateway interface -* [2317](https://github.com/zeta-chain/node/pull/2317) - add ValidateOutbound method for cctx orchestrator * [2289](https://github.com/zeta-chain/node/pull/2289) - add an authorization list to keep track of all authorizations on the chain * [2305](https://github.com/zeta-chain/node/pull/2305) - add new messages `MsgAddAuthorization` and `MsgRemoveAuthorization` that can be used to update the authorization list * [2313](https://github.com/zeta-chain/node/pull/2313) - add `CheckAuthorization` function to replace the `IsAuthorized` function. The new function uses the authorization list to verify the signer's authorization. @@ -47,6 +46,7 @@ * [2269](https://github.com/zeta-chain/node/pull/2269) - refactor MsgUpdateCrosschainFlags into MsgEnableCCTX, MsgDisableCCTX and MsgUpdateGasPriceIncreaseFlags * [2306](https://github.com/zeta-chain/node/pull/2306) - refactor zetaclient outbound transaction signing logic * [2296](https://github.com/zeta-chain/node/pull/2296) - move `testdata` package to `testutil` to organize test-related utilities +* [2317](https://github.com/zeta-chain/node/pull/2317) - add ValidateOutbound method for cctx orchestrator ### Tests From 665b2ffb8d0e910c801467f74ab513b24f9cd95c Mon Sep 17 00:00:00 2001 From: skosito Date: Mon, 17 Jun 2024 20:52:32 +0200 Subject: [PATCH 35/38] PR comments pt1 --- x/crosschain/keeper/cctx_gateway_observers.go | 8 +++--- .../cctx_orchestrator_validate_inbound.go | 14 ++++++---- x/crosschain/keeper/evm_hooks.go | 4 +-- x/crosschain/keeper/initiate_outbound.go | 12 ++++----- x/crosschain/keeper/initiate_outbound_test.go | 26 +++++++++---------- .../keeper/msg_server_vote_inbound_tx.go | 8 +++--- .../keeper/msg_server_vote_inbound_tx_test.go | 6 ++--- 7 files changed, 42 insertions(+), 36 deletions(-) diff --git a/x/crosschain/keeper/cctx_gateway_observers.go b/x/crosschain/keeper/cctx_gateway_observers.go index faceb6ee3a..8e273b886c 100644 --- a/x/crosschain/keeper/cctx_gateway_observers.go +++ b/x/crosschain/keeper/cctx_gateway_observers.go @@ -46,7 +46,9 @@ func (c CCTXGatewayObservers) InitiateOutbound( } err = func() error { - if config.PayGas { + // If ShouldPayGas flag is set during ValidateInbound PayGasAndUpdateCctx should be called + // which will set GasPrice and Amount. Otherwise, use median gas price and InboundParams amount. + if config.ShouldPayGas { err := c.crosschainKeeper.PayGasAndUpdateCctx( tmpCtx, outboundReceiverChainID, @@ -58,11 +60,11 @@ func (c CCTXGatewayObservers) InitiateOutbound( return err } } else { - gasprice, found := c.crosschainKeeper.GetGasPrice(ctx, config.CCTX.GetCurrentOutboundParam().ReceiverChainId) + gasPrice, found := c.crosschainKeeper.GetMedianGasPriceInUint(ctx, config.CCTX.GetCurrentOutboundParam().ReceiverChainId) if !found { return fmt.Errorf("gasprice not found for %d", config.CCTX.GetCurrentOutboundParam().ReceiverChainId) } - config.CCTX.GetCurrentOutboundParam().GasPrice = fmt.Sprintf("%d", gasprice.Prices[gasprice.MedianIndex]) + config.CCTX.GetCurrentOutboundParam().GasPrice = gasPrice.String() config.CCTX.GetCurrentOutboundParam().Amount = config.CCTX.InboundParams.Amount } return c.crosschainKeeper.UpdateNonce(tmpCtx, outboundReceiverChainID, config.CCTX) diff --git a/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go b/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go index 17d2cc3a23..cca0f05395 100644 --- a/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go +++ b/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go @@ -7,10 +7,12 @@ import ( observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) -func (k Keeper) ValidateInboundObservers( +// ValidateInbound is the only entry-point to create new CCTX (eg. when observers voting is done or new inbound event is detected). +// It creates new CCTX object and calls InitiateOutbound method. +func (k Keeper) ValidateInbound( ctx sdk.Context, msg *types.MsgVoteInbound, - payGas bool, + shouldPayGas bool, ) (*types.CrossChainTx, error) { tss, tssFound := k.zetaObserverKeeper.GetTSS(ctx) if !tssFound { @@ -21,16 +23,18 @@ func (k Keeper) ValidateInboundObservers( if !k.zetaObserverKeeper.IsInboundEnabled(ctx) { return nil, observertypes.ErrInboundDisabled } - // create a new CCTX from the inbound message.The status of the new CCTX is set to PendingInbound. + + // create a new CCTX from the inbound message. The status of the new CCTX is set to PendingInbound. cctx, err := types.NewCCTX(ctx, *msg, tss.TssPubkey) if err != nil { return nil, err } + // Initiate outbound, the process function manages the state commit and cctx status change. // If the process fails, the changes to the evm state are rolled back. _, err = k.InitiateOutbound(ctx, InitiateOutboundConfig{ - CCTX: &cctx, - PayGas: payGas, + CCTX: &cctx, + ShouldPayGas: shouldPayGas, }) if err != nil { return nil, err diff --git a/x/crosschain/keeper/evm_hooks.go b/x/crosschain/keeper/evm_hooks.go index 399b12893e..2ba82b3ab9 100644 --- a/x/crosschain/keeper/evm_hooks.go +++ b/x/crosschain/keeper/evm_hooks.go @@ -176,7 +176,7 @@ func (k Keeper) ProcessZRC20WithdrawalEvent( event.Raw.Index, ) - cctx, err := k.ValidateInboundObservers(ctx, msg, false) + cctx, err := k.ValidateInbound(ctx, msg, false) if err != nil { return err } @@ -250,7 +250,7 @@ func (k Keeper) ProcessZetaSentEvent( event.Raw.Index, ) - cctx, err := k.ValidateInboundObservers(ctx, msg, true) + cctx, err := k.ValidateInbound(ctx, msg, true) if err != nil { return err } diff --git a/x/crosschain/keeper/initiate_outbound.go b/x/crosschain/keeper/initiate_outbound.go index abbe5c9361..954db18778 100644 --- a/x/crosschain/keeper/initiate_outbound.go +++ b/x/crosschain/keeper/initiate_outbound.go @@ -10,13 +10,13 @@ import ( "github.com/zeta-chain/zetacore/x/crosschain/types" ) -// TODO: this is just a tmp solution, tbd if info can be passed to CCTX constructor somehow -// and not initialize CCTX using MsgVoteInbound but for example (InboundParams, OutboundParams) -// then PayGas can be decided based on GasPrice already presend in OutboundParams -// check if msg.Digest can be replaced to calculate index +// TODO (https://github.com/zeta-chain/node/issues/2345): this is just a tmp solution because some flows require gas payment and others don't. +// TBD during implementation of issue above if info can be passed to CCTX constructor somehow. +// and not initialize CCTX using MsgVoteInbound and instead use something like (InboundParams, OutboundParams). +// Also check if msg.Digest can be replaced to calculate index type InitiateOutboundConfig struct { - CCTX *types.CrossChainTx - PayGas bool + CCTX *types.CrossChainTx + ShouldPayGas bool } // InitiateOutbound initiates the outbound for the CCTX depending on the CCTX gateway. diff --git a/x/crosschain/keeper/initiate_outbound_test.go b/x/crosschain/keeper/initiate_outbound_test.go index ffeb8d9ad2..5a1bb32592 100644 --- a/x/crosschain/keeper/initiate_outbound_test.go +++ b/x/crosschain/keeper/initiate_outbound_test.go @@ -43,7 +43,7 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) { cctx.GetInboundParams().Amount = sdkmath.NewUintFromBigInt(amount) cctx.InboundParams.CoinType = coin.CoinType_Zeta cctx.GetInboundParams().SenderChainId = 0 - newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, PayGas: true}) + newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, ShouldPayGas: true}) require.NoError(t, err) require.Equal(t, types.CctxStatus_OutboundMined, cctx.CctxStatus.Status) require.Equal(t, types.CctxStatus_OutboundMined, newStatus) @@ -72,7 +72,7 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) { cctx.GetInboundParams().Amount = sdkmath.NewUintFromBigInt(amount) cctx.InboundParams.CoinType = coin.CoinType_Zeta cctx.GetInboundParams().SenderChainId = 0 - newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, PayGas: true}) + newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, ShouldPayGas: true}) require.ErrorContains(t, err, "deposit error") require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) require.Equal(t, types.CctxStatus_Aborted, newStatus) @@ -106,7 +106,7 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) { // call InitiateOutbound cctx := GetERC20Cctx(t, receiver, *senderChain, "", amount) cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId - newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, PayGas: true}) + newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, ShouldPayGas: true}) require.NoError(t, err) require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) require.Equal(t, types.CctxStatus_Aborted, newStatus) @@ -146,7 +146,7 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) { // call InitiateOutbound cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId - newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, PayGas: true}) + newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, ShouldPayGas: true}) require.NoError(t, err) require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) require.Equal(t, types.CctxStatus_Aborted, newStatus) @@ -190,7 +190,7 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) { // call InitiateOutbound cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId - newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, PayGas: true}) + newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, ShouldPayGas: true}) require.NoError(t, err) require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) require.Equal(t, types.CctxStatus_Aborted, newStatus) @@ -236,7 +236,7 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) { // call InitiateOutbound cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId - newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, PayGas: true}) + newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, ShouldPayGas: true}) require.NoError(t, err) require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) require.Equal(t, types.CctxStatus_Aborted, newStatus) @@ -283,7 +283,7 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) { // call InitiateOutbound cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId - newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, PayGas: true}) + newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, ShouldPayGas: true}) require.NoError(t, err) require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) require.Equal(t, types.CctxStatus_Aborted, newStatus) @@ -323,7 +323,7 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) { // call InitiateOutbound cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId - newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, PayGas: true}) + newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, ShouldPayGas: true}) require.NoError(t, err) require.Equal(t, types.CctxStatus_PendingRevert, cctx.CctxStatus.Status) require.Equal(t, types.CctxStatus_PendingRevert, newStatus) @@ -361,7 +361,7 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) { cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount) cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId cctx.OutboundParams = append(cctx.OutboundParams, cctx.GetCurrentOutboundParam()) - newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, PayGas: true}) + newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, ShouldPayGas: true}) require.NoError(t, err) require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) require.Equal(t, types.CctxStatus_Aborted, newStatus) @@ -396,7 +396,7 @@ func TestKeeper_InitiateOutboundProcessCrosschainMsgPassing(t *testing.T) { // call InitiateOutbound cctx := GetERC20Cctx(t, receiver, *receiverChain, "", amount) - newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, PayGas: true}) + newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, ShouldPayGas: true}) require.NoError(t, err) require.Equal(t, types.CctxStatus_PendingOutbound, cctx.CctxStatus.Status) require.Equal(t, types.CctxStatus_PendingOutbound, newStatus) @@ -421,7 +421,7 @@ func TestKeeper_InitiateOutboundProcessCrosschainMsgPassing(t *testing.T) { // call InitiateOutbound cctx := GetERC20Cctx(t, receiver, *receiverChain, "", amount) - newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, PayGas: true}) + newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, ShouldPayGas: true}) require.ErrorIs(t, err, observertypes.ErrSupportedChains) require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) require.Equal(t, types.CctxStatus_Aborted, newStatus) @@ -450,7 +450,7 @@ func TestKeeper_InitiateOutboundProcessCrosschainMsgPassing(t *testing.T) { // call InitiateOutbound cctx := GetERC20Cctx(t, receiver, *receiverChain, "", amount) - newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, PayGas: true}) + newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, ShouldPayGas: true}) require.ErrorContains(t, err, "cannot find receiver chain nonce") require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) require.Equal(t, types.CctxStatus_Aborted, newStatus) @@ -472,7 +472,7 @@ func TestKeeper_InitiateOutboundFailures(t *testing.T) { receiverChain.ChainId = 123 // call InitiateOutbound cctx := GetERC20Cctx(t, receiver, *receiverChain, "", amount) - newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, PayGas: true}) + newStatus, err := k.InitiateOutbound(ctx, keeper.InitiateOutboundConfig{CCTX: cctx, ShouldPayGas: true}) require.Error(t, err) require.Equal(t, types.CctxStatus_PendingInbound, newStatus) require.ErrorContains(t, err, "chain info not found") diff --git a/x/crosschain/keeper/msg_server_vote_inbound_tx.go b/x/crosschain/keeper/msg_server_vote_inbound_tx.go index 06f03cdbc9..b2ddb8bf1d 100644 --- a/x/crosschain/keeper/msg_server_vote_inbound_tx.go +++ b/x/crosschain/keeper/msg_server_vote_inbound_tx.go @@ -98,17 +98,17 @@ func (k msgServer) VoteInbound( return &types.MsgVoteInboundResponse{}, nil } - cctx, err := k.ValidateInboundObservers(ctx, msg, true) + cctx, err := k.ValidateInbound(ctx, msg, true) if err != nil { return nil, err } // Save the inbound CCTX to the store. This is called irrespective of the status of the CCTX or the outcome of the process function. - k.SaveInbound(ctx, cctx, msg.EventIndex) + k.SaveObservedInboundInformation(ctx, cctx, msg.EventIndex) return &types.MsgVoteInboundResponse{}, nil } -/* SaveInbound saves the inbound CCTX to the store.It does the following: +/* SaveObservedInboundInformation saves the inbound CCTX to the store.It does the following: - Emits an event for the finalized inbound CCTX. - Adds the inbound CCTX to the finalized inbound CCTX store.This is done to prevent double spending, using the same inbound tx hash and event index. - Updates the CCTX with the finalized height and finalization status. @@ -116,7 +116,7 @@ func (k msgServer) VoteInbound( - Sets the CCTX and nonce to the CCTX and inbound transaction hash to CCTX store. */ -func (k Keeper) SaveInbound(ctx sdk.Context, cctx *types.CrossChainTx, eventIndex uint64) { +func (k Keeper) SaveObservedInboundInformation(ctx sdk.Context, cctx *types.CrossChainTx, eventIndex uint64) { EmitEventInboundFinalized(ctx, cctx) k.AddFinalizedInbound(ctx, cctx.GetInboundParams().ObservedHash, diff --git a/x/crosschain/keeper/msg_server_vote_inbound_tx_test.go b/x/crosschain/keeper/msg_server_vote_inbound_tx_test.go index 1a94c3f10d..67bba6737b 100644 --- a/x/crosschain/keeper/msg_server_vote_inbound_tx_test.go +++ b/x/crosschain/keeper/msg_server_vote_inbound_tx_test.go @@ -300,7 +300,7 @@ func TestStatus_ChangeStatus(t *testing.T) { } } -func TestKeeper_SaveInbound(t *testing.T) { +func TestKeeper_SaveObservedInboundInformation(t *testing.T) { t.Run("should save the cctx", func(t *testing.T) { k, ctx, _, zk := keepertest.CrosschainKeeper(t) zk.ObserverKeeper.SetTSS(ctx, sample.Tss()) @@ -309,7 +309,7 @@ func TestKeeper_SaveInbound(t *testing.T) { senderChain := getValidEthChain() cctx := GetERC20Cctx(t, receiver, *senderChain, "", amount) eventIndex := sample.Uint64InRange(1, 100) - k.SaveInbound(ctx, cctx, eventIndex) + k.SaveObservedInboundInformation(ctx, cctx, eventIndex) require.Equal(t, types.TxFinalizationStatus_Executed, cctx.InboundParams.TxFinalizationStatus) require.True( t, @@ -340,7 +340,7 @@ func TestKeeper_SaveInbound(t *testing.T) { eventIndex := sample.Uint64InRange(1, 100) zk.ObserverKeeper.SetTSS(ctx, sample.Tss()) - k.SaveInbound(ctx, cctx, eventIndex) + k.SaveObservedInboundInformation(ctx, cctx, eventIndex) require.Equal(t, types.TxFinalizationStatus_Executed, cctx.InboundParams.TxFinalizationStatus) require.True( t, From 5c49a9cce57bbec216206bc6ec53d689998b8c76 Mon Sep 17 00:00:00 2001 From: skosito Date: Tue, 18 Jun 2024 14:36:25 +0200 Subject: [PATCH 36/38] PR comments pt2 --- x/crosschain/keeper/cctx_gateway_observers.go | 2 +- .../keeper/cctx_orchestrator_validate_inbound.go | 6 ++++++ .../keeper/cctx_orchestrator_validate_outbound.go | 2 +- x/crosschain/keeper/cctx_utils.go | 4 ++-- x/crosschain/keeper/cctx_utils_test.go | 14 +++++++------- x/crosschain/keeper/evm_hooks.go | 13 +------------ .../keeper/msg_server_migrate_tss_funds.go | 2 +- x/crosschain/keeper/msg_server_vote_inbound_tx.go | 2 +- x/crosschain/keeper/msg_server_whitelist_erc20.go | 2 +- 9 files changed, 21 insertions(+), 26 deletions(-) diff --git a/x/crosschain/keeper/cctx_gateway_observers.go b/x/crosschain/keeper/cctx_gateway_observers.go index 8e273b886c..d95681a238 100644 --- a/x/crosschain/keeper/cctx_gateway_observers.go +++ b/x/crosschain/keeper/cctx_gateway_observers.go @@ -67,7 +67,7 @@ func (c CCTXGatewayObservers) InitiateOutbound( config.CCTX.GetCurrentOutboundParam().GasPrice = gasPrice.String() config.CCTX.GetCurrentOutboundParam().Amount = config.CCTX.InboundParams.Amount } - return c.crosschainKeeper.UpdateNonce(tmpCtx, outboundReceiverChainID, config.CCTX) + return c.crosschainKeeper.SetObserverOutboundInfo(tmpCtx, outboundReceiverChainID, config.CCTX) }() if err != nil { // do not commit anything here as the CCTX should be aborted diff --git a/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go b/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go index cca0f05395..4a5987cc54 100644 --- a/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go +++ b/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go @@ -40,5 +40,11 @@ func (k Keeper) ValidateInbound( return nil, err } + inCctxIndex, ok := ctx.Value("inCctxIndex").(string) + if ok { + cctx.InboundParams.ObservedHash = inCctxIndex + } + k.SetCctxAndNonceToCctxAndInboundHashToCctx(ctx, cctx) + return &cctx, nil } diff --git a/x/crosschain/keeper/cctx_orchestrator_validate_outbound.go b/x/crosschain/keeper/cctx_orchestrator_validate_outbound.go index 16b61a3a0a..77989b88fe 100644 --- a/x/crosschain/keeper/cctx_orchestrator_validate_outbound.go +++ b/x/crosschain/keeper/cctx_orchestrator_validate_outbound.go @@ -189,7 +189,7 @@ func (k Keeper) validateFailedOutbound( if err != nil { return err } - err = k.UpdateNonce(ctx, cctx.InboundParams.SenderChainId, cctx) + err = k.SetObserverOutboundInfo(ctx, cctx.InboundParams.SenderChainId, cctx) if err != nil { return err } diff --git a/x/crosschain/keeper/cctx_utils.go b/x/crosschain/keeper/cctx_utils.go index f85b243c1c..4f05884fcc 100644 --- a/x/crosschain/keeper/cctx_utils.go +++ b/x/crosschain/keeper/cctx_utils.go @@ -16,9 +16,9 @@ import ( zetaObserverTypes "github.com/zeta-chain/zetacore/x/observer/types" ) -// UpdateNonce sets the CCTX outbound nonce to the next nonce, and updates the nonce of blockchain state. +// SetObserverOutboundInfo sets the CCTX outbound nonce to the next nonce, and updates the nonce of blockchain state. // It also updates the PendingNonces that is used to track the unfulfilled outbound txs. -func (k Keeper) UpdateNonce(ctx sdk.Context, receiveChainID int64, cctx *types.CrossChainTx) error { +func (k Keeper) SetObserverOutboundInfo(ctx sdk.Context, receiveChainID int64, cctx *types.CrossChainTx) error { chain := k.GetObserverKeeper().GetSupportedChainFromChainID(ctx, receiveChainID) if chain == nil { return zetaObserverTypes.ErrSupportedChains diff --git a/x/crosschain/keeper/cctx_utils_test.go b/x/crosschain/keeper/cctx_utils_test.go index d466c1f1c3..0828b79fd9 100644 --- a/x/crosschain/keeper/cctx_utils_test.go +++ b/x/crosschain/keeper/cctx_utils_test.go @@ -226,7 +226,7 @@ func Test_IsPending(t *testing.T) { } } -func TestKeeper_UpdateNonce(t *testing.T) { +func TestKeeper_SetObserverOutboundInfo(t *testing.T) { t.Run("should error if supported chain is nil", func(t *testing.T) { k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ UseObserverMock: true, @@ -235,7 +235,7 @@ func TestKeeper_UpdateNonce(t *testing.T) { observerMock := keepertest.GetCrosschainObserverMock(t, k) observerMock.On("GetSupportedChainFromChainID", mock.Anything, mock.Anything).Return(nil) - err := k.UpdateNonce(ctx, 5, nil) + err := k.SetObserverOutboundInfo(ctx, 5, nil) require.Error(t, err) }) @@ -258,7 +258,7 @@ func TestKeeper_UpdateNonce(t *testing.T) { {Amount: sdkmath.NewUint(1)}, }, } - err := k.UpdateNonce(ctx, 5, &cctx) + err := k.SetObserverOutboundInfo(ctx, 5, &cctx) require.Error(t, err) }) @@ -284,7 +284,7 @@ func TestKeeper_UpdateNonce(t *testing.T) { {Amount: sdkmath.NewUint(1)}, }, } - err := k.UpdateNonce(ctx, 5, &cctx) + err := k.SetObserverOutboundInfo(ctx, 5, &cctx) require.Error(t, err) require.Equal(t, uint64(100), cctx.GetCurrentOutboundParam().TssNonce) }) @@ -314,7 +314,7 @@ func TestKeeper_UpdateNonce(t *testing.T) { {Amount: sdkmath.NewUint(1)}, }, } - err := k.UpdateNonce(ctx, 5, &cctx) + err := k.SetObserverOutboundInfo(ctx, 5, &cctx) require.Error(t, err) }) @@ -345,7 +345,7 @@ func TestKeeper_UpdateNonce(t *testing.T) { {Amount: sdkmath.NewUint(1)}, }, } - err := k.UpdateNonce(ctx, 5, &cctx) + err := k.SetObserverOutboundInfo(ctx, 5, &cctx) require.Error(t, err) }) @@ -379,7 +379,7 @@ func TestKeeper_UpdateNonce(t *testing.T) { {Amount: sdkmath.NewUint(1)}, }, } - err := k.UpdateNonce(ctx, 5, &cctx) + err := k.SetObserverOutboundInfo(ctx, 5, &cctx) require.NoError(t, err) }) } diff --git a/x/crosschain/keeper/evm_hooks.go b/x/crosschain/keeper/evm_hooks.go index 2ba82b3ab9..dbc2d2dbb1 100644 --- a/x/crosschain/keeper/evm_hooks.go +++ b/x/crosschain/keeper/evm_hooks.go @@ -187,7 +187,7 @@ func (k Keeper) ProcessZRC20WithdrawalEvent( EmitZRCWithdrawCreated(ctx, *cctx) - return k.ProcessCCTX(ctx, *cctx) + return nil } func (k Keeper) ProcessZetaSentEvent( @@ -260,17 +260,6 @@ func (k Keeper) ProcessZetaSentEvent( } EmitZetaWithdrawCreated(ctx, *cctx) - return k.ProcessCCTX(ctx, *cctx) -} - -func (k Keeper) ProcessCCTX(ctx sdk.Context, cctx types.CrossChainTx) error { - inCctxIndex, ok := ctx.Value("inCctxIndex").(string) - if ok { - cctx.InboundParams.ObservedHash = inCctxIndex - } - - k.SetCctxAndNonceToCctxAndInboundHashToCctx(ctx, cctx) - ctx.Logger().Debug("ProcessCCTX successful \n") return nil } diff --git a/x/crosschain/keeper/msg_server_migrate_tss_funds.go b/x/crosschain/keeper/msg_server_migrate_tss_funds.go index b651178312..73de1619d3 100644 --- a/x/crosschain/keeper/msg_server_migrate_tss_funds.go +++ b/x/crosschain/keeper/msg_server_migrate_tss_funds.go @@ -201,7 +201,7 @@ func (k Keeper) MigrateTSSFundsForChain( return errorsmod.Wrap(types.ErrReceiverIsEmpty, fmt.Sprintf("chain %d is not supported", chainID)) } - err := k.UpdateNonce(ctx, chainID, &cctx) + err := k.SetObserverOutboundInfo(ctx, chainID, &cctx) if err != nil { return err } diff --git a/x/crosschain/keeper/msg_server_vote_inbound_tx.go b/x/crosschain/keeper/msg_server_vote_inbound_tx.go index b2ddb8bf1d..ecf87ef9fd 100644 --- a/x/crosschain/keeper/msg_server_vote_inbound_tx.go +++ b/x/crosschain/keeper/msg_server_vote_inbound_tx.go @@ -126,5 +126,5 @@ func (k Keeper) SaveObservedInboundInformation(ctx sdk.Context, cctx *types.Cros cctx.InboundParams.FinalizedZetaHeight = uint64(ctx.BlockHeight()) cctx.InboundParams.TxFinalizationStatus = types.TxFinalizationStatus_Executed k.RemoveInboundTrackerIfExists(ctx, cctx.InboundParams.SenderChainId, cctx.InboundParams.ObservedHash) - k.SetCctxAndNonceToCctxAndInboundHashToCctx(ctx, *cctx) + k.SetCrossChainTx(ctx, *cctx) } diff --git a/x/crosschain/keeper/msg_server_whitelist_erc20.go b/x/crosschain/keeper/msg_server_whitelist_erc20.go index 47f4007eb5..838d328b6d 100644 --- a/x/crosschain/keeper/msg_server_whitelist_erc20.go +++ b/x/crosschain/keeper/msg_server_whitelist_erc20.go @@ -164,7 +164,7 @@ func (k msgServer) WhitelistERC20( }, }, } - err = k.UpdateNonce(ctx, msg.ChainId, &cctx) + err = k.SetObserverOutboundInfo(ctx, msg.ChainId, &cctx) if err != nil { return nil, err } From 2c2d7e5462f004d828371d3307959069fa1770f7 Mon Sep 17 00:00:00 2001 From: skosito Date: Tue, 18 Jun 2024 15:37:18 +0200 Subject: [PATCH 37/38] add todo --- x/crosschain/keeper/cctx_gateway_observers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/crosschain/keeper/cctx_gateway_observers.go b/x/crosschain/keeper/cctx_gateway_observers.go index d95681a238..7155e61cd0 100644 --- a/x/crosschain/keeper/cctx_gateway_observers.go +++ b/x/crosschain/keeper/cctx_gateway_observers.go @@ -39,7 +39,7 @@ func (c CCTXGatewayObservers) InitiateOutbound( ) (newCCTXStatus types.CctxStatus, err error) { tmpCtx, commit := ctx.CacheContext() outboundReceiverChainID := config.CCTX.GetCurrentOutboundParam().ReceiverChainId - // TODO: does this condition make sense? + // TODO (https://github.com/zeta-chain/node/issues/1010): workaround for this bug noEthereumTxEvent := false if chains.IsZetaChain(config.CCTX.InboundParams.SenderChainId) { noEthereumTxEvent = true From ae38f3b8b4e4400c0ed84c0d7d8277e7e8803cac Mon Sep 17 00:00:00 2001 From: skosito Date: Wed, 19 Jun 2024 20:50:40 +0200 Subject: [PATCH 38/38] PR comments --- x/crosschain/keeper/cctx_orchestrator_validate_inbound.go | 2 +- x/crosschain/keeper/cctx_utils.go | 2 +- x/crosschain/keeper/evm_deposit.go | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go b/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go index 4a5987cc54..4bb90ec915 100644 --- a/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go +++ b/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go @@ -40,7 +40,7 @@ func (k Keeper) ValidateInbound( return nil, err } - inCctxIndex, ok := ctx.Value("inCctxIndex").(string) + inCctxIndex, ok := ctx.Value(InCCTXIndexKey).(string) if ok { cctx.InboundParams.ObservedHash = inCctxIndex } diff --git a/x/crosschain/keeper/cctx_utils.go b/x/crosschain/keeper/cctx_utils.go index 4f05884fcc..3d3cf31cd5 100644 --- a/x/crosschain/keeper/cctx_utils.go +++ b/x/crosschain/keeper/cctx_utils.go @@ -16,7 +16,7 @@ import ( zetaObserverTypes "github.com/zeta-chain/zetacore/x/observer/types" ) -// SetObserverOutboundInfo sets the CCTX outbound nonce to the next nonce, and updates the nonce of blockchain state. +// SetObserverOutboundInfo sets the CCTX outbound nonce to the next available nonce for the TSS address, and updates the nonce of blockchain state. // It also updates the PendingNonces that is used to track the unfulfilled outbound txs. func (k Keeper) SetObserverOutboundInfo(ctx sdk.Context, receiveChainID int64, cctx *types.CrossChainTx) error { chain := k.GetObserverKeeper().GetSupportedChainFromChainID(ctx, receiveChainID) diff --git a/x/crosschain/keeper/evm_deposit.go b/x/crosschain/keeper/evm_deposit.go index 927e9f5eb1..858c709f18 100644 --- a/x/crosschain/keeper/evm_deposit.go +++ b/x/crosschain/keeper/evm_deposit.go @@ -18,6 +18,8 @@ import ( fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" ) +const InCCTXIndexKey = "inCctxIndex" + // HandleEVMDeposit handles a deposit from an inbound tx // returns (isContractReverted, err) // (true, non-nil) means CallEVM() reverted @@ -102,7 +104,7 @@ func (k Keeper) HandleEVMDeposit(ctx sdk.Context, cctx *types.CrossChainTx) (boo if !evmTxResponse.Failed() && contractCall { logs := evmtypes.LogsToEthereum(evmTxResponse.Logs) if len(logs) > 0 { - ctx = ctx.WithValue("inCctxIndex", cctx.Index) + ctx = ctx.WithValue(InCCTXIndexKey, cctx.Index) txOrigin := cctx.InboundParams.TxOrigin if txOrigin == "" { txOrigin = inboundSender