From 8be6ce1dc98c275d0fab217cca2c71e154ff4fdd Mon Sep 17 00:00:00 2001 From: Lucas Bertrand Date: Mon, 3 Jun 2024 17:02:45 +0200 Subject: [PATCH 1/3] chore: synchronize v17 changelogs (#2300) --- changelog.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/changelog.md b/changelog.md index d4595d2891..048880a062 100644 --- a/changelog.md +++ b/changelog.md @@ -67,6 +67,13 @@ * [2191](https://github.com/zeta-chain/node/pull/2191) - Fixed conditional logic for the docker build step for non release builds to not overwrite the github tag. * [2192](https://github.com/zeta-chain/node/pull/2192) - Added release status checker and updater pipeline that will update release statuses when they go live on network. +## v17.0.0 + +### Fixes + +* [2249](https://github.com/zeta-chain/node/pull/2249) - fix inbound and outbound validation for BSC chain +* [2265](https://github.com/zeta-chain/node/pull/2265) - fix rate limiter query for revert cctxs + ## v16.0.0 ### Breaking Changes From 833a2536161e5f6075d533f88dc279320f539f7f Mon Sep 17 00:00:00 2001 From: skosito Date: Mon, 3 Jun 2024 16:31:43 +0100 Subject: [PATCH 2/3] feat: initialize cctx gateway interface (#2291) --- app/ante/vesting_test.go | 3 +- app/app.go | 9 ++ changelog.md | 1 + pkg/chains/address_taproot_test.go | 14 +- pkg/crypto/pubkey_test.go | 16 +-- testutil/keeper/crosschain.go | 7 + x/authority/types/genesis_test.go | 2 +- x/crosschain/keeper/cctx_gateway_observers.go | 60 ++++++++ x/crosschain/keeper/cctx_gateway_zevm.go | 100 +++++++++++++ x/crosschain/keeper/initiate_outbound.go | 39 ++++++ ...ound_test.go => initiate_outbound_test.go} | 120 ++++++++++++---- x/crosschain/keeper/keeper.go | 20 ++- .../keeper/msg_server_vote_inbound_tx.go | 9 +- x/crosschain/keeper/process_inbound.go | 131 ------------------ x/crosschain/types/errors.go | 1 + x/fungible/keeper/evm_test.go | 2 +- .../msg_server_deploy_system_contract_test.go | 11 +- x/observer/types/chain_params_test.go | 2 +- .../types/message_disable_cctx_flags_test.go | 4 +- .../types/message_enable_cctx_flags_test.go | 4 +- ...ge_update_gas_price_increase_flags_test.go | 4 +- zetaclient/chains/bitcoin/fee_test.go | 12 +- .../chains/bitcoin/observer/outbound_test.go | 32 ++--- .../chains/bitcoin/signer/signer_test.go | 2 +- zetaclient/chains/evm/validation_test.go | 2 +- 25 files changed, 388 insertions(+), 219 deletions(-) create mode 100644 x/crosschain/keeper/cctx_gateway_observers.go create mode 100644 x/crosschain/keeper/cctx_gateway_zevm.go create mode 100644 x/crosschain/keeper/initiate_outbound.go rename x/crosschain/keeper/{process_inbound_test.go => initiate_outbound_test.go} (81%) delete mode 100644 x/crosschain/keeper/process_inbound.go diff --git a/app/ante/vesting_test.go b/app/ante/vesting_test.go index 280a128ca5..f174bb61c2 100644 --- a/app/ante/vesting_test.go +++ b/app/ante/vesting_test.go @@ -94,8 +94,7 @@ func TestVesting_AnteHandle(t *testing.T) { _, err = decorator.AnteHandle(ctx, tx, false, mmd.AnteHandle) if tt.wantHasErr { - require.Error(t, err) - require.Contains(t, err.Error(), tt.wantErr) + require.ErrorContains(t, err, tt.wantErr) } else { require.NoError(t, err) } diff --git a/app/app.go b/app/app.go index e6dfe90de1..eaad38a8a2 100644 --- a/app/app.go +++ b/app/app.go @@ -105,6 +105,7 @@ 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" @@ -597,6 +598,14 @@ 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) + // 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/changelog.md b/changelog.md index 048880a062..cc502322d4 100644 --- a/changelog.md +++ b/changelog.md @@ -20,6 +20,7 @@ * [2287](https://github.com/zeta-chain/node/pull/2287) - implement `MsgUpdateChainInfo` message * [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 * [2289](https://github.com/zeta-chain/node/pull/2289) - add an authorization list to keep track of all authorizations on the chain ### Refactor diff --git a/pkg/chains/address_taproot_test.go b/pkg/chains/address_taproot_test.go index 08de704937..c3742dcefc 100644 --- a/pkg/chains/address_taproot_test.go +++ b/pkg/chains/address_taproot_test.go @@ -13,7 +13,7 @@ func TestAddressTaproot(t *testing.T) { // should parse mainnet taproot address addrStr := "bc1p4ur084x8y63mj5hj7eydscuc4awals7ly749x8vhyquc0twcmvhquspa5c" addr, err := DecodeTaprootAddress(addrStr) - require.Nil(t, err) + require.NoError(t, err) require.Equal(t, addrStr, addr.String()) require.Equal(t, addrStr, addr.EncodeAddress()) require.True(t, addr.IsForNet(&chaincfg.MainNetParams)) @@ -22,7 +22,7 @@ func TestAddressTaproot(t *testing.T) { // should parse testnet taproot address addrStr := "tb1pzeclkt6upu8xwuksjcz36y4q56dd6jw5r543eu8j8238yaxpvcvq7t8f33" addr, err := DecodeTaprootAddress(addrStr) - require.Nil(t, err) + require.NoError(t, err) require.Equal(t, addrStr, addr.String()) require.Equal(t, addrStr, addr.EncodeAddress()) require.True(t, addr.IsForNet(&chaincfg.TestNet3Params)) @@ -31,7 +31,7 @@ func TestAddressTaproot(t *testing.T) { // should parse regtest taproot address addrStr := "bcrt1pqqqsyqcyq5rqwzqfpg9scrgwpugpzysnzs23v9ccrydpk8qarc0sj9hjuh" addr, err := DecodeTaprootAddress(addrStr) - require.Nil(t, err) + require.NoError(t, err) require.Equal(t, addrStr, addr.String()) require.Equal(t, addrStr, addr.EncodeAddress()) require.True(t, addr.IsForNet(&chaincfg.RegressionNetParams)) @@ -50,7 +50,7 @@ func TestAddressTaproot(t *testing.T) { witnessProg[i] = byte(i) } _, err := newAddressTaproot("bcrt", witnessProg[:]) - require.Nil(t, err) + require.NoError(t, err) //t.Logf("addr: %v", addr) } { @@ -58,9 +58,9 @@ func TestAddressTaproot(t *testing.T) { // these hex string comes from link // https://mempool.space/tx/41f7cbaaf9a8d378d09ee86de32eebef455225520cb71015cc9a7318fb42e326 witnessProg, err := hex.DecodeString("af06f3d4c726a3b952f2f648d86398af5ddfc3df27aa531d97203987add8db2e") - require.Nil(t, err) + require.NoError(t, err) addr, err := NewAddressTaproot(witnessProg[:], &chaincfg.MainNetParams) - require.Nil(t, err) + require.NoError(t, err) require.Equal(t, addr.EncodeAddress(), "bc1p4ur084x8y63mj5hj7eydscuc4awals7ly749x8vhyquc0twcmvhquspa5c") } { @@ -69,7 +69,7 @@ func TestAddressTaproot(t *testing.T) { // https://blockstream.info/tx/09298a2f32f5267f419aeaf8a58c4807dcf6cac3edb59815a3b129cd8f1219b0?expand addrStr := "bc1p6pls9gpm24g8ntl37pajpjtuhd3y08hs5rnf9a4n0wq595hwdh9suw7m2h" addr, err := DecodeTaprootAddress(addrStr) - require.Nil(t, err) + require.NoError(t, err) require.Equal( t, "d07f02a03b555079aff1f07b20c97cbb62479ef0a0e692f6b37b8142d2ee6dcb", diff --git a/pkg/crypto/pubkey_test.go b/pkg/crypto/pubkey_test.go index 8f1a46f393..5f16f8142e 100644 --- a/pkg/crypto/pubkey_test.go +++ b/pkg/crypto/pubkey_test.go @@ -207,15 +207,15 @@ func TestNewPubKey(t *testing.T) { t.Run("should create new pub key from string", func(t *testing.T) { _, pubKey, _ := testdata.KeyTestPubAddr() spk, err := cosmos.Bech32ifyPubKey(cosmos.Bech32PubKeyTypeAccPub, pubKey) - require.Nil(t, err) + require.NoError(t, err) pk, err := NewPubKey(spk) - require.Nil(t, err) + require.NoError(t, err) require.Equal(t, PubKey(spk), pk) }) t.Run("should return empty pub key from empty string", func(t *testing.T) { pk, err := NewPubKey("") - require.Nil(t, err) + require.NoError(t, err) require.Equal(t, EmptyPubKey, pk) }) @@ -230,9 +230,9 @@ func TestGetAddressFromPubkeyString(t *testing.T) { t.Run("should get address from pubkey string", func(t *testing.T) { _, pubKey, _ := testdata.KeyTestPubAddr() spk, err := cosmos.Bech32ifyPubKey(cosmos.Bech32PubKeyTypeAccPub, pubKey) - require.Nil(t, err) + require.NoError(t, err) _, err = GetAddressFromPubkeyString(spk) - require.Nil(t, err) + require.NoError(t, err) }) t.Run("should get address from nonbech32 string", func(t *testing.T) { @@ -349,7 +349,7 @@ func TestGetEVMAddress(t *testing.T) { t.Run("should return empty if pubkey is empty", func(t *testing.T) { pubKey := PubKey("") e, err := pubKey.GetEVMAddress() - require.Nil(t, err) + require.NoError(t, err) require.Equal(t, chains.NoAddress, e) }) @@ -359,13 +359,13 @@ func TestGetEVMAddress(t *testing.T) { pk, _ := NewPubKey(spk) _, err := pk.GetEVMAddress() - require.Nil(t, err) + require.NoError(t, err) }) t.Run("should error if non bech32", func(t *testing.T) { pk := PubKey("invalid") e, err := pk.GetEVMAddress() - require.NotNil(t, err) + require.ErrorContains(t, err, "decoding bech32 failed") require.Equal(t, chains.NoAddress, e) }) } diff --git a/testutil/keeper/crosschain.go b/testutil/keeper/crosschain.go index 0ff22a6250..5ca5acbaae 100644 --- a/testutil/keeper/crosschain.go +++ b/testutil/keeper/crosschain.go @@ -174,6 +174,13 @@ 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 diff --git a/x/authority/types/genesis_test.go b/x/authority/types/genesis_test.go index 90aee92509..3d8dcd14df 100644 --- a/x/authority/types/genesis_test.go +++ b/x/authority/types/genesis_test.go @@ -72,7 +72,7 @@ func TestGenesisState_Validate(t *testing.T) { err := tt.gs.Validate() if tt.errContains != "" { require.Error(t, err) - require.Contains(t, err.Error(), tt.errContains) + require.ErrorContains(t, err, tt.errContains) } else { require.NoError(t, err) } diff --git a/x/crosschain/keeper/cctx_gateway_observers.go b/x/crosschain/keeper/cctx_gateway_observers.go new file mode 100644 index 0000000000..556d242214 --- /dev/null +++ b/x/crosschain/keeper/cctx_gateway_observers.go @@ -0,0 +1,60 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +// CCTXGatewayObservers is implementation of CCTXGateway interface for observers +type CCTXGatewayObservers struct { + crosschainKeeper Keeper +} + +// NewCCTXGatewayObservers returns new instance of CCTXGatewayObservers +func NewCCTXGatewayObservers(crosschainKeeper Keeper) CCTXGatewayObservers { + return CCTXGatewayObservers{ + crosschainKeeper: crosschainKeeper, + } +} + +/* +InitiateOutbound updates the store so observers can use the PendingCCTX query: + + - If preprocessing of outbound is successful, the CCTX status is changed to PendingOutbound. + + - if preprocessing of outbound, such as paying the gas fee for the destination fails, the state is reverted to aborted + + 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 PendingOutbound. + New CCTX status after preprocessing is returned. +*/ +func (c CCTXGatewayObservers) InitiateOutbound( + ctx sdk.Context, + cctx *types.CrossChainTx, +) (newCCTXStatus types.CctxStatus) { + tmpCtx, commit := ctx.CacheContext() + 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 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 new file mode 100644 index 0000000000..89e7c5a152 --- /dev/null +++ b/x/crosschain/keeper/cctx_gateway_zevm.go @@ -0,0 +1,100 @@ +package keeper + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +// CCTXGatewayZEVM is implementation of CCTXGateway interface for ZEVM +type CCTXGatewayZEVM struct { + crosschainKeeper Keeper +} + +// NewCCTXGatewayZEVM returns new instance of CCTXGatewayZEVM +func NewCCTXGatewayZEVM(crosschainKeeper Keeper) CCTXGatewayZEVM { + return CCTXGatewayZEVM{ + crosschainKeeper: crosschainKeeper, + } +} + +/* +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. +*/ +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 + } + + 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 +} diff --git a/x/crosschain/keeper/initiate_outbound.go b/x/crosschain/keeper/initiate_outbound.go new file mode 100644 index 0000000000..8174f6ec4e --- /dev/null +++ b/x/crosschain/keeper/initiate_outbound.go @@ -0,0 +1,39 @@ +package keeper + +import ( + "fmt" + + cosmoserrors "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/zeta-chain/zetacore/pkg/chains" + "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +// 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 + chainInfo := chains.GetChainFromChainID(receiverChainID) + if chainInfo == nil { + return cctx.CctxStatus.Status, cosmoserrors.Wrap( + types.ErrInitiatitingOutbound, + fmt.Sprintf( + "chain info not found for %d", receiverChainID, + ), + ) + } + + cctxGateway, ok := k.cctxGateways[chainInfo.CctxGateway] + if !ok { + return cctx.CctxStatus.Status, cosmoserrors.Wrap( + types.ErrInitiatitingOutbound, + fmt.Sprintf( + "CCTXGateway not defined for receiver chain %d", receiverChainID, + ), + ) + } + + return cctxGateway.InitiateOutbound(ctx, cctx), nil +} diff --git a/x/crosschain/keeper/process_inbound_test.go b/x/crosschain/keeper/initiate_outbound_test.go similarity index 81% rename from x/crosschain/keeper/process_inbound_test.go rename to x/crosschain/keeper/initiate_outbound_test.go index 0286290cc4..59d0eaee0b 100644 --- a/x/crosschain/keeper/process_inbound_test.go +++ b/x/crosschain/keeper/initiate_outbound_test.go @@ -13,12 +13,13 @@ import ( "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_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, @@ -34,7 +35,7 @@ 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() @@ -42,8 +43,10 @@ func TestKeeper_ProcessInboundZEVMDeposit(t *testing.T) { cctx.GetInboundParams().Amount = sdkmath.NewUintFromBigInt(amount) cctx.InboundParams.CoinType = coin.CoinType_Zeta cctx.GetInboundParams().SenderChainId = 0 - k.ProcessInbound(ctx, cctx) + 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) { @@ -61,7 +64,7 @@ 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() @@ -69,8 +72,10 @@ func TestKeeper_ProcessInboundZEVMDeposit(t *testing.T) { cctx.GetInboundParams().Amount = sdkmath.NewUintFromBigInt(amount) cctx.InboundParams.CoinType = coin.CoinType_Zeta cctx.GetInboundParams().SenderChainId = 0 - k.ProcessInbound(ctx, cctx) + 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) }) @@ -98,11 +103,13 @@ 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) + 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("invalid sender chain id %d", cctx.InboundParams.SenderChainId), @@ -136,11 +143,13 @@ 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) + 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("revert gas limit error: %s", types.ErrForeignCoinNotFound), @@ -179,11 +188,13 @@ 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) + 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("deposit revert message: %s err : %s", errDeposit, observertypes.ErrSupportedChains), @@ -223,11 +234,13 @@ 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) + 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("deposit revert message: %s err : %s", errDeposit, observertypes.ErrSupportedChains), @@ -268,11 +281,13 @@ 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) + 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") }) @@ -306,11 +321,13 @@ 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) + 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) }) @@ -342,12 +359,14 @@ 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) + 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, @@ -357,7 +376,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, @@ -377,10 +396,12 @@ 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) + 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) }) @@ -400,10 +421,12 @@ 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) + 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) }) @@ -427,10 +450,55 @@ 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) + 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 fc483689b3..b32543126c 100644 --- a/x/crosschain/keeper/keeper.go +++ b/x/crosschain/keeper/keeper.go @@ -8,14 +8,24 @@ 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" ) +// 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 - storeKey storetypes.StoreKey - memKey storetypes.StoreKey + cdc codec.Codec + storeKey storetypes.StoreKey + memKey storetypes.StoreKey + cctxGateways map[chains.CCTXGateway]CCTXGateway stakingKeeper types.StakingKeeper authKeeper types.AccountKeeper @@ -100,6 +110,10 @@ 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 } diff --git a/x/crosschain/keeper/msg_server_vote_inbound_tx.go b/x/crosschain/keeper/msg_server_vote_inbound_tx.go index 9b40097635..b118d2bc43 100644 --- a/x/crosschain/keeper/msg_server_vote_inbound_tx.go +++ b/x/crosschain/keeper/msg_server_vote_inbound_tx.go @@ -106,9 +106,12 @@ func (k msgServer) VoteInbound( if err != nil { return nil, err } - // Process the inbound CCTX, the process function manages the state commit and cctx status change. - // If the process fails, the changes to the evm state are rolled back. - k.ProcessInbound(ctx, &cctx) + // 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 + } // 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) return &types.MsgVoteInboundResponse{}, nil diff --git a/x/crosschain/keeper/process_inbound.go b/x/crosschain/keeper/process_inbound.go deleted file mode 100644 index 2fe9bfb3c9..0000000000 --- a/x/crosschain/keeper/process_inbound.go +++ /dev/null @@ -1,131 +0,0 @@ -package keeper - -import ( - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/zeta-chain/zetacore/pkg/chains" - "github.com/zeta-chain/zetacore/x/crosschain/types" -) - -// ProcessInbound processes the inbound CCTX. -// It does a conditional dispatch to ProcessZEVMDeposit or ProcessCrosschainMsgPassing based on the receiver chain. -// The internal functions handle the state changes and error handling. -func (k Keeper) ProcessInbound(ctx sdk.Context, cctx *types.CrossChainTx) { - if chains.IsZetaChain(cctx.GetCurrentOutboundParam().ReceiverChainId) { - k.processZEVMDeposit(ctx, cctx) - } else { - k.processCrosschainMsgPassing(ctx, cctx) - } -} - -/* -processZEVMDeposit processes the EVM deposit CCTX. A deposit is a cctx which has Zetachain as the receiver chain.It trasnsitions state according to the following rules: - - - 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. -*/ -func (k Keeper) processZEVMDeposit(ctx sdk.Context, cctx *types.CrossChainTx) { - 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 - } else if err != nil && isContractReverted { // contract call reverted; should refund via a revert tx - revertMessage := err.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 - } - gasLimit, err := k.GetRevertGasLimit(ctx, *cctx) - if err != nil { - cctx.SetAbort(fmt.Sprintf("revert gas limit error: %s", err.Error())) - return - } - 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 - } - // 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 - } - commitRevert() - cctx.SetPendingRevert(revertMessage) - return - } - // successful HandleEVMDeposit; - commit() - cctx.SetOutBoundMined("Remote omnichain contract call completed") - return -} - -/* -processCrosschainMsgPassing processes the CCTX for crosschain message passing. A crosschain message passing is a cctx which has a non-Zetachain as the receiver chain.It trasnsitions state according to the following rules: - - - If the crosschain message passing is successful, the CCTX status is changed to PendingOutbound. - - - 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. - - 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 (k Keeper) processCrosschainMsgPassing(ctx sdk.Context, cctx *types.CrossChainTx) { - tmpCtx, commit := ctx.CacheContext() - outboundReceiverChainID := cctx.GetCurrentOutboundParam().ReceiverChainId - err := func() error { - err := k.PayGasAndUpdateCctx( - tmpCtx, - outboundReceiverChainID, - cctx, - cctx.InboundParams.Amount, - false, - ) - if err != nil { - return err - } - return k.UpdateNonce(tmpCtx, outboundReceiverChainID, cctx) - }() - if err != nil { - // do not commit anything here as the CCTX should be aborted - cctx.SetAbort(err.Error()) - return - } - commit() - cctx.SetPendingOutbound("") - return -} diff --git a/x/crosschain/types/errors.go b/x/crosschain/types/errors.go index ffeda3077c..232bf229db 100644 --- a/x/crosschain/types/errors.go +++ b/x/crosschain/types/errors.go @@ -48,4 +48,5 @@ var ( ErrUnableToDecodeMessageString = errorsmod.Register(ModuleName, 1151, "unable to decode message string") ErrInvalidRateLimiterFlags = errorsmod.Register(ModuleName, 1152, "invalid rate limiter flags") ErrMaxTxOutTrackerHashesReached = errorsmod.Register(ModuleName, 1153, "max tx out tracker hashes reached") + ErrInitiatitingOutbound = errorsmod.Register(ModuleName, 1154, "cannot initiate outbound") ) diff --git a/x/fungible/keeper/evm_test.go b/x/fungible/keeper/evm_test.go index a175943247..2ef0f4dfb9 100644 --- a/x/fungible/keeper/evm_test.go +++ b/x/fungible/keeper/evm_test.go @@ -575,7 +575,7 @@ func TestKeeper_CallEVMWithData(t *testing.T) { // check reason is included for revert error // 0xbfb4ebcf is the hash of "Foo()" - require.Contains(t, err.Error(), "reason: 0xbfb4ebcf") + require.ErrorContains(t, err, "reason: 0xbfb4ebcf") res, err = k.CallEVM( ctx, diff --git a/x/fungible/keeper/msg_server_deploy_system_contract_test.go b/x/fungible/keeper/msg_server_deploy_system_contract_test.go index 6b11c46592..117ee0cc33 100644 --- a/x/fungible/keeper/msg_server_deploy_system_contract_test.go +++ b/x/fungible/keeper/msg_server_deploy_system_contract_test.go @@ -72,8 +72,7 @@ func TestMsgServer_DeploySystemContracts(t *testing.T) { mockFailedContractDeployment(ctx, t, k) _, err := msgServer.DeploySystemContracts(ctx, types.NewMsgDeploySystemContracts(admin)) - require.Error(t, err) - require.Contains(t, err.Error(), "failed to deploy") + require.ErrorContains(t, err, "failed to deploy") }) t.Run("should fail if wzeta contract deployment fails", func(t *testing.T) { @@ -95,7 +94,7 @@ func TestMsgServer_DeploySystemContracts(t *testing.T) { _, err := msgServer.DeploySystemContracts(ctx, types.NewMsgDeploySystemContracts(admin)) require.Error(t, err) - require.Contains(t, err.Error(), "failed to deploy") + require.ErrorContains(t, err, "failed to deploy") }) t.Run("should fail if uniswapv2router deployment fails", func(t *testing.T) { @@ -118,7 +117,7 @@ func TestMsgServer_DeploySystemContracts(t *testing.T) { _, err := msgServer.DeploySystemContracts(ctx, types.NewMsgDeploySystemContracts(admin)) require.Error(t, err) - require.Contains(t, err.Error(), "failed to deploy") + require.ErrorContains(t, err, "failed to deploy") }) t.Run("should fail if connectorzevm deployment fails", func(t *testing.T) { @@ -142,7 +141,7 @@ func TestMsgServer_DeploySystemContracts(t *testing.T) { _, err := msgServer.DeploySystemContracts(ctx, types.NewMsgDeploySystemContracts(admin)) require.Error(t, err) - require.Contains(t, err.Error(), "failed to deploy") + require.ErrorContains(t, err, "failed to deploy") }) t.Run("should fail if system contract deployment fails", func(t *testing.T) { @@ -167,7 +166,7 @@ func TestMsgServer_DeploySystemContracts(t *testing.T) { _, err := msgServer.DeploySystemContracts(ctx, types.NewMsgDeploySystemContracts(admin)) require.Error(t, err) - require.Contains(t, err.Error(), "failed to deploy") + require.ErrorContains(t, err, "failed to deploy") }) } diff --git a/x/observer/types/chain_params_test.go b/x/observer/types/chain_params_test.go index 9aab9dc9ff..1703b43f54 100644 --- a/x/observer/types/chain_params_test.go +++ b/x/observer/types/chain_params_test.go @@ -30,7 +30,7 @@ func TestChainParamsList_Validate(t *testing.T) { list.ChainParams = append(list.ChainParams, list.ChainParams[0]) err := list.Validate() require.Error(t, err) - require.Contains(t, err.Error(), "duplicated chain id") + require.ErrorContains(t, err, "duplicated chain id") }) } diff --git a/x/observer/types/message_disable_cctx_flags_test.go b/x/observer/types/message_disable_cctx_flags_test.go index 40a4d023d2..8a394a2bac 100644 --- a/x/observer/types/message_disable_cctx_flags_test.go +++ b/x/observer/types/message_disable_cctx_flags_test.go @@ -20,14 +20,14 @@ func TestMsgDisableCCTX_ValidateBasic(t *testing.T) { name: "invalid creator address", msg: types.NewMsgDisableCCTX("invalid", true, true), err: func(t require.TestingT, err error, i ...interface{}) { - require.Contains(t, err.Error(), "invalid creator address") + require.ErrorContains(t, err, "invalid creator address") }, }, { name: "invalid flags", msg: types.NewMsgDisableCCTX(sample.AccAddress(), false, false), err: func(t require.TestingT, err error, i ...interface{}) { - require.Contains(t, err.Error(), "at least one of DisableInbound or DisableOutbound must be true") + require.ErrorContains(t, err, "at least one of DisableInbound or DisableOutbound must be true") }, }, { diff --git a/x/observer/types/message_enable_cctx_flags_test.go b/x/observer/types/message_enable_cctx_flags_test.go index 2033837e7d..f1121c5fce 100644 --- a/x/observer/types/message_enable_cctx_flags_test.go +++ b/x/observer/types/message_enable_cctx_flags_test.go @@ -20,14 +20,14 @@ func TestMsgEnableCCTX_ValidateBasic(t *testing.T) { name: "invalid creator address", msg: types.NewMsgEnableCCTX("invalid", true, true), err: func(t require.TestingT, err error, i ...interface{}) { - require.Contains(t, err.Error(), "invalid creator address") + require.ErrorContains(t, err, "invalid creator address") }, }, { name: "invalid flags", msg: types.NewMsgEnableCCTX(sample.AccAddress(), false, false), err: func(t require.TestingT, err error, i ...interface{}) { - require.Contains(t, err.Error(), "at least one of EnableInbound or EnableOutbound must be true") + require.ErrorContains(t, err, "at least one of EnableInbound or EnableOutbound must be true") }, }, { diff --git a/x/observer/types/message_update_gas_price_increase_flags_test.go b/x/observer/types/message_update_gas_price_increase_flags_test.go index d432835b62..17f110958c 100644 --- a/x/observer/types/message_update_gas_price_increase_flags_test.go +++ b/x/observer/types/message_update_gas_price_increase_flags_test.go @@ -20,7 +20,7 @@ func TestMsgUpdateGasPriceIncreaseFlags_ValidateBasic(t *testing.T) { name: "invalid creator address", msg: types.NewMsgUpdateGasPriceIncreaseFlags("invalid", types.DefaultGasPriceIncreaseFlags), err: func(t require.TestingT, err error, i ...interface{}) { - require.Contains(t, err.Error(), "invalid creator address") + require.ErrorContains(t, err, "invalid creator address") }, }, { @@ -34,7 +34,7 @@ func TestMsgUpdateGasPriceIncreaseFlags_ValidateBasic(t *testing.T) { }, ), err: func(t require.TestingT, err error, i ...interface{}) { - require.Contains(t, err.Error(), "invalid request") + require.ErrorContains(t, err, "invalid request") }, }, { diff --git a/zetaclient/chains/bitcoin/fee_test.go b/zetaclient/chains/bitcoin/fee_test.go index bb6757ede2..160bdf6093 100644 --- a/zetaclient/chains/bitcoin/fee_test.go +++ b/zetaclient/chains/bitcoin/fee_test.go @@ -60,13 +60,13 @@ var exampleTxids = []string{ func generateKeyPair(t *testing.T, net *chaincfg.Params) (*btcec.PrivateKey, btcutil.Address, []byte) { privateKey, err := btcec.NewPrivateKey(btcec.S256()) - require.Nil(t, err) + require.NoError(t, err) pubKeyHash := btcutil.Hash160(privateKey.PubKey().SerializeCompressed()) addr, err := btcutil.NewAddressWitnessPubKeyHash(pubKeyHash, net) - require.Nil(t, err) + require.NoError(t, err) //fmt.Printf("New address: %s\n", addr.EncodeAddress()) pkScript, err := PayToAddrScript(addr) - require.Nil(t, err) + require.NoError(t, err) return privateKey, addr, pkScript } @@ -98,7 +98,7 @@ func addTxInputs(t *testing.T, tx *wire.MsgTx, txids []string) { preTxSize := tx.SerializeSize() for _, txid := range txids { hash, err := chainhash.NewHashFromStr(txid) - require.Nil(t, err) + require.NoError(t, err) outpoint := wire.NewOutPoint(hash, uint32(rand.Intn(100))) txIn := wire.NewTxIn(outpoint, nil, nil) tx.AddTxIn(txIn) @@ -158,9 +158,9 @@ func signTx(t *testing.T, tx *wire.MsgTx, payerScript []byte, privateKey *btcec. for ix := range tx.TxIn { amount := int64(1 + rand.Intn(100000000)) witnessHash, err := txscript.CalcWitnessSigHash(payerScript, sigHashes, txscript.SigHashAll, tx, ix, amount) - require.Nil(t, err) + require.NoError(t, err) sig, err := privateKey.Sign(witnessHash) - require.Nil(t, err) + require.NoError(t, err) pkCompressed := privateKey.PubKey().SerializeCompressed() txWitness := wire.TxWitness{append(sig.Serialize(), byte(txscript.SigHashAll)), pkCompressed} diff --git a/zetaclient/chains/bitcoin/observer/outbound_test.go b/zetaclient/chains/bitcoin/observer/outbound_test.go index 2f25575284..bad0997c0c 100644 --- a/zetaclient/chains/bitcoin/observer/outbound_test.go +++ b/zetaclient/chains/bitcoin/observer/outbound_test.go @@ -33,7 +33,7 @@ func MockBTCObserverMainnet() *Observer { func createObserverWithPrivateKey(t *testing.T) *Observer { skHex := "7b8507ba117e069f4a3f456f505276084f8c92aee86ac78ae37b4d1801d35fa8" privateKey, err := crypto.HexToECDSA(skHex) - require.Nil(t, err) + require.NoError(t, err) tss := &mocks.TSS{ PrivKey: privateKey, } @@ -241,7 +241,7 @@ func TestSelectUTXOs(t *testing.T) { // input: utxoCap = 5, amount = 0.01, nonce = 0 // output: [0.01], 0.01 result, amount, _, _, err := ob.SelectUTXOs(0.01, 5, 0, math.MaxUint16, true) - require.Nil(t, err) + require.NoError(t, err) require.Equal(t, 0.01, amount) require.Equal(t, ob.utxos[0:1], result) @@ -249,7 +249,7 @@ func TestSelectUTXOs(t *testing.T) { // input: utxoCap = 5, amount = 0.5, nonce = 1 // output: error result, amount, _, _, err = ob.SelectUTXOs(0.5, 5, 1, math.MaxUint16, true) - require.NotNil(t, err) + require.Error(t, err) require.Nil(t, result) require.Zero(t, amount) require.Equal(t, "getOutboundIDByNonce: cannot find outbound txid for nonce 0", err.Error()) @@ -259,7 +259,7 @@ func TestSelectUTXOs(t *testing.T) { // input: utxoCap = 5, amount = 0.5, nonce = 1 // output: [0.00002, 0.01, 0.12, 0.18, 0.24], 0.55002 result, amount, _, _, err = ob.SelectUTXOs(0.5, 5, 1, math.MaxUint16, true) - require.Nil(t, err) + require.NoError(t, err) require.Equal(t, 0.55002, amount) require.Equal(t, ob.utxos[0:5], result) mineTxNSetNonceMark(ob, 1, dummyTxID, 0) // mine a transaction and set nonce-mark utxo for nonce 1 @@ -268,7 +268,7 @@ func TestSelectUTXOs(t *testing.T) { // input: utxoCap = 5, amount = 1.0, nonce = 2 // output: [0.00002001, 0.01, 0.12, 0.18, 0.24, 0.5], 1.05002001 result, amount, _, _, err = ob.SelectUTXOs(1.0, 5, 2, math.MaxUint16, true) - require.Nil(t, err) + require.NoError(t, err) require.InEpsilon(t, 1.05002001, amount, 1e-8) require.Equal(t, ob.utxos[0:6], result) mineTxNSetNonceMark(ob, 2, dummyTxID, 0) // mine a transaction and set nonce-mark utxo for nonce 2 @@ -277,7 +277,7 @@ func TestSelectUTXOs(t *testing.T) { // input: utxoCap = 5, amount = 8.05, nonce = 3 // output: [0.00002002, 0.24, 0.5, 1.26, 2.97, 3.28], 8.25002002 result, amount, _, _, err = ob.SelectUTXOs(8.05, 5, 3, math.MaxUint16, true) - require.Nil(t, err) + require.NoError(t, err) require.InEpsilon(t, 8.25002002, amount, 1e-8) expected := append([]btcjson.ListUnspentResult{ob.utxos[0]}, ob.utxos[4:9]...) require.Equal(t, expected, result) @@ -287,7 +287,7 @@ func TestSelectUTXOs(t *testing.T) { // input: utxoCap = 5, amount = 0.503, nonce = 24105432 // output: [0.24107432, 0.01, 0.12, 0.18, 0.24], 0.55002002 result, amount, _, _, err = ob.SelectUTXOs(0.503, 5, 24105432, math.MaxUint16, true) - require.Nil(t, err) + require.NoError(t, err) require.InEpsilon(t, 0.79107431, amount, 1e-8) expected = append([]btcjson.ListUnspentResult{ob.utxos[4]}, ob.utxos[0:4]...) require.Equal(t, expected, result) @@ -297,7 +297,7 @@ func TestSelectUTXOs(t *testing.T) { // input: utxoCap = 5, amount = 1.0, nonce = 24105433 // output: [0.24107432, 0.12, 0.18, 0.24, 0.5], 1.28107432 result, amount, _, _, err = ob.SelectUTXOs(1.0, 5, 24105433, math.MaxUint16, true) - require.Nil(t, err) + require.NoError(t, err) require.InEpsilon(t, 1.28107432, amount, 1e-8) expected = append([]btcjson.ListUnspentResult{ob.utxos[4]}, ob.utxos[1:4]...) expected = append(expected, ob.utxos[5]) @@ -307,7 +307,7 @@ func TestSelectUTXOs(t *testing.T) { // input: utxoCap = 5, amount = 16.03 // output: [0.24107432, 1.26, 2.97, 3.28, 5.16, 8.72], 21.63107432 result, amount, _, _, err = ob.SelectUTXOs(16.03, 5, 24105433, math.MaxUint16, true) - require.Nil(t, err) + require.NoError(t, err) require.InEpsilon(t, 21.63107432, amount, 1e-8) expected = append([]btcjson.ListUnspentResult{ob.utxos[4]}, ob.utxos[6:11]...) require.Equal(t, expected, result) @@ -316,7 +316,7 @@ func TestSelectUTXOs(t *testing.T) { // input: utxoCap = 5, amount = 21.64 // output: error result, amount, _, _, err = ob.SelectUTXOs(21.64, 5, 24105433, math.MaxUint16, true) - require.NotNil(t, err) + require.Error(t, err) require.Nil(t, result) require.Zero(t, amount) require.Equal( @@ -336,7 +336,7 @@ func TestUTXOConsolidation(t *testing.T) { // input: utxoCap = 10, amount = 0.01, nonce = 1, rank = 10 // output: [0.00002, 0.01], 0.01002 result, amount, clsdtUtxo, clsdtValue, err := ob.SelectUTXOs(0.01, 10, 1, 10, true) - require.Nil(t, err) + require.NoError(t, err) require.Equal(t, 0.01002, amount) require.Equal(t, ob.utxos[0:2], result) require.Equal(t, uint16(0), clsdtUtxo) @@ -350,7 +350,7 @@ func TestUTXOConsolidation(t *testing.T) { // input: utxoCap = 9, amount = 0.01, nonce = 1, rank = 9 // output: [0.00002, 0.01, 0.12], 0.13002 result, amount, clsdtUtxo, clsdtValue, err := ob.SelectUTXOs(0.01, 9, 1, 9, true) - require.Nil(t, err) + require.NoError(t, err) require.Equal(t, 0.13002, amount) require.Equal(t, ob.utxos[0:3], result) require.Equal(t, uint16(1), clsdtUtxo) @@ -364,7 +364,7 @@ func TestUTXOConsolidation(t *testing.T) { // input: utxoCap = 5, amount = 0.01, nonce = 0, rank = 5 // output: [0.00002, 0.014, 1.26, 0.5, 0.2], 2.01002 result, amount, clsdtUtxo, clsdtValue, err := ob.SelectUTXOs(0.01, 5, 1, 5, true) - require.Nil(t, err) + require.NoError(t, err) require.Equal(t, 2.01002, amount) expected := make([]btcjson.ListUnspentResult, 2) copy(expected, ob.utxos[0:2]) @@ -383,7 +383,7 @@ func TestUTXOConsolidation(t *testing.T) { // input: utxoCap = 12, amount = 0.01, nonce = 0, rank = 1 // output: [0.00002, 0.01, 8.72, 5.16, 3.28, 2.97, 1.26, 0.5, 0.24, 0.18, 0.12], 22.44002 result, amount, clsdtUtxo, clsdtValue, err := ob.SelectUTXOs(0.01, 12, 1, 1, true) - require.Nil(t, err) + require.NoError(t, err) require.Equal(t, 22.44002, amount) expected := make([]btcjson.ListUnspentResult, 2) copy(expected, ob.utxos[0:2]) @@ -407,7 +407,7 @@ func TestUTXOConsolidation(t *testing.T) { // input: utxoCap = 5, amount = 0.13, nonce = 24105432, rank = 5 // output: [0.24107431, 0.01, 0.12, 1.26, 0.5, 0.24], 2.37107431 result, amount, clsdtUtxo, clsdtValue, err := ob.SelectUTXOs(0.13, 5, 24105432, 5, true) - require.Nil(t, err) + require.NoError(t, err) require.InEpsilon(t, 2.37107431, amount, 1e-8) expected := append([]btcjson.ListUnspentResult{ob.utxos[4]}, ob.utxos[0:2]...) expected = append(expected, ob.utxos[6]) @@ -430,7 +430,7 @@ func TestUTXOConsolidation(t *testing.T) { // input: utxoCap = 12, amount = 0.13, nonce = 24105432, rank = 1 // output: [0.24107431, 0.01, 0.12, 8.72, 5.16, 3.28, 2.97, 1.26, 0.5, 0.24, 0.18], 22.68107431 result, amount, clsdtUtxo, clsdtValue, err := ob.SelectUTXOs(0.13, 12, 24105432, 1, true) - require.Nil(t, err) + require.NoError(t, err) require.InEpsilon(t, 22.68107431, amount, 1e-8) expected := append([]btcjson.ListUnspentResult{ob.utxos[4]}, ob.utxos[0:2]...) for i := 10; i >= 5; i-- { // append consolidated utxos in descending order diff --git a/zetaclient/chains/bitcoin/signer/signer_test.go b/zetaclient/chains/bitcoin/signer/signer_test.go index 76b46aef37..6d048c7bba 100644 --- a/zetaclient/chains/bitcoin/signer/signer_test.go +++ b/zetaclient/chains/bitcoin/signer/signer_test.go @@ -370,7 +370,7 @@ func TestAddWithdrawTxOutputs(t *testing.T) { if tt.fail { require.Error(t, err) if tt.message != "" { - require.Contains(t, err.Error(), tt.message) + require.ErrorContains(t, err, tt.message) } return } else { diff --git a/zetaclient/chains/evm/validation_test.go b/zetaclient/chains/evm/validation_test.go index 330d15f8f8..8077f8ab4e 100644 --- a/zetaclient/chains/evm/validation_test.go +++ b/zetaclient/chains/evm/validation_test.go @@ -296,7 +296,7 @@ func TestCheckEvmTransactionTable(t *testing.T) { err := evm.ValidateEvmTransaction(tt.tx) if tt.fail { require.Error(t, err) - require.Contains(t, err.Error(), tt.msg) + require.ErrorContains(t, err, tt.msg) return } require.NoError(t, err) From f4366ad1d3ac7ddf1dadf3eb113198d3bf43ce0e Mon Sep 17 00:00:00 2001 From: Lucas Bertrand Date: Mon, 3 Jun 2024 18:54:05 +0200 Subject: [PATCH 3/3] test(`e2e`): add a test to deploy contracts (#2299) * deploy e2e test * add deploy e2e test to list * make generate * changelog --- changelog.md | 1 + e2e/e2etests/e2etests.go | 17 ++++++ e2e/e2etests/test_deploy_contract.go | 90 ++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+) create mode 100644 e2e/e2etests/test_deploy_contract.go diff --git a/changelog.md b/changelog.md index cc502322d4..f0f94999ef 100644 --- a/changelog.md +++ b/changelog.md @@ -48,6 +48,7 @@ * [2199](https://github.com/zeta-chain/node/pull/2199) - custom priority mempool unit tests * [2240](https://github.com/zeta-chain/node/pull/2240) - removed hard-coded Bitcoin regnet chainID in E2E withdraw tests * [2266](https://github.com/zeta-chain/node/pull/2266) - try fixing E2E test `crosschain_swap` failure `btc transaction not signed` +* [2299](https://github.com/zeta-chain/node/pull/2299) - add `zetae2e` command to deploy test contracts ### Fixes diff --git a/e2e/e2etests/e2etests.go b/e2e/e2etests/e2etests.go index 83bc455502..348a2a7ef1 100644 --- a/e2e/e2etests/e2etests.go +++ b/e2e/e2etests/e2etests.go @@ -98,6 +98,12 @@ const ( TestUpdateBytecodeZRC20Name = "update_bytecode_zrc20" TestUpdateBytecodeConnectorName = "update_bytecode_connector" TestRateLimiterName = "rate_limiter" + + /* + Special tests + Not used to test functionalities but do various interactions with the netwoks + */ + TestDeploy = "deploy" ) // AllE2ETests is an ordered list of all e2e tests @@ -507,4 +513,15 @@ var AllE2ETests = []runner.E2ETest{ []runner.ArgDefinition{}, TestRateLimiter, ), + /* + Special tests + */ + runner.NewE2ETest( + TestDeploy, + "deploy a contract", + []runner.ArgDefinition{ + {Description: "contract name", DefaultValue: ""}, + }, + TestDeployContract, + ), } diff --git a/e2e/e2etests/test_deploy_contract.go b/e2e/e2etests/test_deploy_contract.go new file mode 100644 index 0000000000..860ae2e0dc --- /dev/null +++ b/e2e/e2etests/test_deploy_contract.go @@ -0,0 +1,90 @@ +package e2etests + +import ( + "fmt" + + ethcommon "github.com/ethereum/go-ethereum/common" + + "github.com/zeta-chain/zetacore/e2e/contracts/testdapp" + "github.com/zeta-chain/zetacore/e2e/runner" + "github.com/zeta-chain/zetacore/e2e/utils" +) + +// deployFunc is a function that deploys a contract +type deployFunc func(r *runner.E2ERunner) (ethcommon.Address, error) + +// deployMap maps contract names to deploy functions +var deployMap = map[string]deployFunc{ + "testdapp_zevm": deployZEVMTestDApp, + "testdapp_evm": deployEVMTestDApp, +} + +// TestDeployContract deploys the specified contract +func TestDeployContract(r *runner.E2ERunner, args []string) { + availableContractNames := make([]string, 0, len(deployMap)) + for contractName := range deployMap { + availableContractNames = append(availableContractNames, contractName) + } + availableContractNamesMessage := fmt.Sprintf("Available contract names: %v", availableContractNames) + + if len(args) != 1 { + panic( + "TestDeployContract requires exactly one argument for the contract name. " + availableContractNamesMessage, + ) + } + contractName := args[0] + + deployFunc, ok := deployMap[contractName] + if !ok { + panic(fmt.Sprintf("Unknown contract name: %s, %s", contractName, availableContractNamesMessage)) + } + + addr, err := deployFunc(r) + if err != nil { + panic(err) + } + + r.Logger.Print("%s deployed at %s", contractName, addr.Hex()) +} + +// deployZEVMTestDApp deploys the TestDApp contract on ZetaChain +func deployZEVMTestDApp(r *runner.E2ERunner) (ethcommon.Address, error) { + addr, tx, _, err := testdapp.DeployTestDApp( + r.ZEVMAuth, + r.ZEVMClient, + r.ConnectorZEVMAddr, + r.WZetaAddr, + ) + if err != nil { + return addr, err + } + + // Wait for the transaction to be mined + receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + if receipt.Status != 1 { + return addr, fmt.Errorf("contract deployment failed") + } + + return addr, nil +} + +// deployEVMTestDApp deploys the TestDApp contract on Ethereum +func deployEVMTestDApp(r *runner.E2ERunner) (ethcommon.Address, error) { + addr, tx, _, err := testdapp.DeployTestDApp( + r.EVMAuth, + r.EVMClient, + r.ConnectorEthAddr, + r.ZetaEthAddr, + ) + if err != nil { + return addr, err + } + + // Wait for the transaction to be mined + receipt := utils.MustWaitForTxReceipt(r.Ctx, r.EVMClient, tx, r.Logger, r.ReceiptTimeout) + if receipt.Status != 1 { + return addr, fmt.Errorf("contract deployment failed") + } + + return addr, nil +}