Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: initialize cctx gateway interface #2291

Merged
merged 27 commits into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@

"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"
Expand Down Expand Up @@ -597,6 +598,12 @@
app.LightclientKeeper,
)

cctxGateways := map[chains.CCTXGateway]crosschainkeeper.CCTXGateway{
skosito marked this conversation as resolved.
Show resolved Hide resolved
chains.CCTXGateway_observers: crosschainkeeper.NewCCTXGatewayObservers(app.CrosschainKeeper),
chains.CCTXGateway_zevm: crosschainkeeper.NewCCTXGatewayZEVM(app.CrosschainKeeper),

Check warning on line 603 in app/app.go

View check run for this annotation

Codecov / codecov/patch

app/app.go#L601-L603

Added lines #L601 - L603 were not covered by tests
}
app.CrosschainKeeper.SetCCTXGateways(cctxGateways)

Check warning on line 605 in app/app.go

View check run for this annotation

Codecov / codecov/patch

app/app.go#L605

Added line #L605 was not covered by tests

// 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

Expand Down
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
* [2258](https://github.com/zeta-chain/node/pull/2258) - add Optimism and Base in static chain information
* [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) - cctx gateway interface
skosito marked this conversation as resolved.
Show resolved Hide resolved

### Refactor

Expand Down
7 changes: 7 additions & 0 deletions testutil/keeper/crosschain.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,13 @@ func CrosschainKeeperWithMocks(
lightclientKeeper,
)

cctxGateways := map[chains.CCTXGateway]keeper.CCTXGateway{
skosito marked this conversation as resolved.
Show resolved Hide resolved
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

Expand Down
53 changes: 53 additions & 0 deletions x/crosschain/keeper/cctx_gateway_observers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package keeper

import (
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/zeta-chain/zetacore/x/crosschain/types"
)

type CCTXGatewayObservers struct {
skosito marked this conversation as resolved.
Show resolved Hide resolved
crosschainKeeper Keeper
}

func NewCCTXGatewayObservers(crosschainKeeper Keeper) CCTXGatewayObservers {
skosito marked this conversation as resolved.
Show resolved Hide resolved
return CCTXGatewayObservers{
crosschainKeeper: crosschainKeeper,
}
}

/*
InitiateOutbound updates the store so observers can use the PendingCCTX query:

- If the crosschain message passing is successful, the CCTX status is changed to PendingOutbound.
skosito marked this conversation as resolved.
Show resolved Hide resolved

- If the crosschain message passing returns an error, the CCTX status is changed to Aborted.
We do not return an error from this function, as all changes need to be persisted to the state.
skosito marked this conversation as resolved.
Show resolved Hide resolved

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 PendingOutbound.
*/
func (c CCTXGatewayObservers) InitiateOutbound(ctx sdk.Context, cctx *types.CrossChainTx) error {
tmpCtx, commit := ctx.CacheContext()
lumtis marked this conversation as resolved.
Show resolved Hide resolved
outboundReceiverChainID := cctx.GetCurrentOutboundParam().ReceiverChainId
err := func() error {
err := c.crosschainKeeper.PayGasAndUpdateCctx(
tmpCtx,
outboundReceiverChainID,
cctx,
cctx.InboundParams.Amount,
false,
)
if err != nil {
return err
}
return c.crosschainKeeper.UpdateNonce(tmpCtx, outboundReceiverChainID, cctx)
}()
if err != nil {
// do not commit anything here as the CCTX should be aborted
cctx.SetAbort(err.Error())
return nil
}
commit()
cctx.SetPendingOutbound("")
return nil
}
95 changes: 95 additions & 0 deletions x/crosschain/keeper/cctx_gateway_zevm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package keeper

import (
"fmt"

sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/zeta-chain/zetacore/x/crosschain/types"
)

type CCTXGatewayZEVM struct {
skosito marked this conversation as resolved.
Show resolved Hide resolved
crosschainKeeper Keeper
}

func NewCCTXGatewayZEVM(crosschainKeeper Keeper) CCTXGatewayZEVM {
return CCTXGatewayZEVM{
skosito marked this conversation as resolved.
Show resolved Hide resolved
crosschainKeeper: crosschainKeeper,
}
}

/*
InitiateOutbound handles evm deposit and then ValidateOutbound is called.
skosito marked this conversation as resolved.
Show resolved Hide resolved
TODO: move remaining of this comment to ValidateOutbound once it's added.
skosito marked this conversation as resolved.
Show resolved Hide resolved

- 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.
skosito marked this conversation as resolved.
Show resolved Hide resolved
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.
*/
func (c CCTXGatewayZEVM) InitiateOutbound(ctx sdk.Context, cctx *types.CrossChainTx) error {
skosito marked this conversation as resolved.
Show resolved Hide resolved
tmpCtx, commit := ctx.CacheContext()
isContractReverted, err := c.crosschainKeeper.HandleEVMDeposit(tmpCtx, cctx)

// 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 err
} else if err != nil && isContractReverted { // contract call reverted; should refund via a revert tx
skosito marked this conversation as resolved.
Show resolved Hide resolved
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 nil
}
gasLimit, err := c.crosschainKeeper.GetRevertGasLimit(ctx, *cctx)
if err != nil {
cctx.SetAbort(fmt.Sprintf("revert gas limit error: %s", err.Error()))
return nil
}
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 nil
}
// 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 nil
}
commitRevert()
cctx.SetPendingRevert(revertMessage)
return nil
}
// successful HandleEVMDeposit;
lumtis marked this conversation as resolved.
Show resolved Hide resolved
commit()
cctx.SetOutBoundMined("Remote omnichain contract call completed")
return nil
}
17 changes: 17 additions & 0 deletions x/crosschain/keeper/initiate_inbound.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
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"
)

// InitiateOutbound processes the inbound CCTX.
// It does a conditional dispatch to correct CCTX gateway based on the receiver chain
// which handle the state changes and error handling.
func (k Keeper) InitiateOutbound(ctx sdk.Context, cctx *types.CrossChainTx) error {
chainInfo := chains.GetChainFromChainID(cctx.GetCurrentOutboundParam().ReceiverChainId)

return k.cctxGateways[chainInfo.CctxGateway].InitiateOutbound(ctx, cctx)
skosito marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
observertypes "github.com/zeta-chain/zetacore/x/observer/types"
)

func TestKeeper_ProcessInboundZEVMDeposit(t *testing.T) {
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,
Expand All @@ -34,15 +34,15 @@ func TestKeeper_ProcessInboundZEVMDeposit(t *testing.T) {
mock.Anything,
receiver, int64(0), amount, mock.Anything, mock.Anything).Return(nil, nil)

// call ProcessInbound
// 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
k.ProcessInbound(ctx, cctx)
k.InitiateOutbound(ctx, cctx)
require.Equal(t, types.CctxStatus_OutboundMined, cctx.CctxStatus.Status)
})

Expand All @@ -61,15 +61,15 @@ func TestKeeper_ProcessInboundZEVMDeposit(t *testing.T) {
fungibleMock.On("ZETADepositAndCallContract", mock.Anything, mock.Anything, receiver, int64(0), amount, mock.Anything, mock.Anything).
Return(nil, fmt.Errorf("deposit error"))

// call ProcessInbound
// 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
k.ProcessInbound(ctx, cctx)
k.InitiateOutbound(ctx, cctx)
require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status)
require.Equal(t, "deposit error", cctx.CctxStatus.StatusMessage)
})
Expand Down Expand Up @@ -98,10 +98,10 @@ func TestKeeper_ProcessInboundZEVMDeposit(t *testing.T) {
observerMock.On("GetSupportedChainFromChainID", mock.Anything, senderChain.ChainId).
Return(nil)

// call ProcessInbound
// call InitiateOutbound
cctx := GetERC20Cctx(t, receiver, *senderChain, "", amount)
cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId
k.ProcessInbound(ctx, cctx)
k.InitiateOutbound(ctx, cctx)
require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status)
require.Equal(
t,
Expand Down Expand Up @@ -136,10 +136,10 @@ func TestKeeper_ProcessInboundZEVMDeposit(t *testing.T) {
fungibleMock.On("GetForeignCoinFromAsset", mock.Anything, asset, senderChain.ChainId).
Return(fungibletypes.ForeignCoins{}, false)

// call ProcessInbound
// call InitiateOutbound
cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount)
cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId
k.ProcessInbound(ctx, cctx)
k.InitiateOutbound(ctx, cctx)
require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status)
require.Equal(
t,
Expand Down Expand Up @@ -179,10 +179,10 @@ func TestKeeper_ProcessInboundZEVMDeposit(t *testing.T) {
observerMock.On("GetSupportedChainFromChainID", mock.Anything, senderChain.ChainId).
Return(nil).Once()

// call ProcessInbound
// call InitiateOutbound
cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount)
cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId
k.ProcessInbound(ctx, cctx)
k.InitiateOutbound(ctx, cctx)
require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status)
require.Equal(
t,
Expand Down Expand Up @@ -223,10 +223,10 @@ func TestKeeper_ProcessInboundZEVMDeposit(t *testing.T) {
observerMock.On("GetSupportedChainFromChainID", mock.Anything, senderChain.ChainId).
Return(nil).Once()

// call ProcessInbound
// call InitiateOutbound
cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount)
cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId
k.ProcessInbound(ctx, cctx)
k.InitiateOutbound(ctx, cctx)
require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status)
require.Equal(
t,
Expand Down Expand Up @@ -268,10 +268,10 @@ func TestKeeper_ProcessInboundZEVMDeposit(t *testing.T) {
observerMock.On("GetChainNonces", mock.Anything, senderChain.ChainName.String()).
Return(observertypes.ChainNonces{}, false)

// call ProcessInbound
// call InitiateOutbound
cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount)
cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId
k.ProcessInbound(ctx, cctx)
k.InitiateOutbound(ctx, cctx)
require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status)
require.Contains(t, cctx.CctxStatus.StatusMessage, "cannot find receiver chain nonce")
})
Expand Down Expand Up @@ -306,10 +306,10 @@ func TestKeeper_ProcessInboundZEVMDeposit(t *testing.T) {
// mock successful UpdateNonce
updatedNonce := keepertest.MockUpdateNonce(observerMock, *senderChain)

// call ProcessInbound
// call InitiateOutbound
cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount)
cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId
k.ProcessInbound(ctx, cctx)
k.InitiateOutbound(ctx, cctx)
require.Equal(t, types.CctxStatus_PendingRevert, cctx.CctxStatus.Status)
require.Equal(t, errDeposit.Error(), cctx.CctxStatus.StatusMessage)
require.Equal(t, updatedNonce, cctx.GetCurrentOutboundParam().TssNonce)
Expand Down Expand Up @@ -342,11 +342,11 @@ func TestKeeper_ProcessInboundZEVMDeposit(t *testing.T) {
// mock successful GetRevertGasLimit for ERC20
keepertest.MockGetRevertGasLimitForERC20(fungibleMock, asset, *senderChain, 100)

// call ProcessInbound
// call InitiateOutbound
cctx := GetERC20Cctx(t, receiver, *senderChain, asset, amount)
cctx.GetCurrentOutboundParam().ReceiverChainId = chains.ZetaChainPrivnet.ChainId
cctx.OutboundParams = append(cctx.OutboundParams, cctx.GetCurrentOutboundParam())
k.ProcessInbound(ctx, cctx)
k.InitiateOutbound(ctx, cctx)
require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status)
require.Contains(
t,
Expand All @@ -357,7 +357,7 @@ func TestKeeper_ProcessInboundZEVMDeposit(t *testing.T) {
)
}

func TestKeeper_ProcessInboundProcessCrosschainMsgPassing(t *testing.T) {
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,
Expand All @@ -377,9 +377,9 @@ func TestKeeper_ProcessInboundProcessCrosschainMsgPassing(t *testing.T) {
// mock successful UpdateNonce
updatedNonce := keepertest.MockUpdateNonce(observerMock, *receiverChain)

// call ProcessInbound
// call InitiateOutbound
cctx := GetERC20Cctx(t, receiver, *receiverChain, "", amount)
k.ProcessInbound(ctx, cctx)
k.InitiateOutbound(ctx, cctx)
require.Equal(t, types.CctxStatus_PendingOutbound, cctx.CctxStatus.Status)
require.Equal(t, updatedNonce, cctx.GetCurrentOutboundParam().TssNonce)
})
Expand All @@ -400,9 +400,9 @@ func TestKeeper_ProcessInboundProcessCrosschainMsgPassing(t *testing.T) {
observerMock.On("GetSupportedChainFromChainID", mock.Anything, receiverChain.ChainId).
Return(nil).Once()

// call ProcessInbound
// call InitiateOutbound
cctx := GetERC20Cctx(t, receiver, *receiverChain, "", amount)
k.ProcessInbound(ctx, cctx)
k.InitiateOutbound(ctx, cctx)
require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status)
require.Equal(t, observertypes.ErrSupportedChains.Error(), cctx.CctxStatus.StatusMessage)
})
Expand All @@ -427,9 +427,9 @@ func TestKeeper_ProcessInboundProcessCrosschainMsgPassing(t *testing.T) {
observerMock.On("GetChainNonces", mock.Anything, receiverChain.ChainName.String()).
Return(observertypes.ChainNonces{}, false)

// call ProcessInbound
// call InitiateOutbound
cctx := GetERC20Cctx(t, receiver, *receiverChain, "", amount)
k.ProcessInbound(ctx, cctx)
k.InitiateOutbound(ctx, cctx)
require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status)
require.Contains(t, cctx.CctxStatus.StatusMessage, "cannot find receiver chain nonce")
})
Expand Down
Loading
Loading