diff --git a/changelog.md b/changelog.md index 61b85985bf..41aa1b32fd 100644 --- a/changelog.md +++ b/changelog.md @@ -44,6 +44,7 @@ * [1989](https://github.com/zeta-chain/node/pull/1989) - simplify `IsSendOutTxProcessed` method and add unit tests * [2013](https://github.com/zeta-chain/node/pull/2013) - rename `GasPriceVoter` message to `VoteGasPrice` * [2059](https://github.com/zeta-chain/node/pull/2059) - Remove unused params from all functions in zetanode +* [2076](https://github.com/zeta-chain/node/pull/2076) - automatically deposit native zeta to an address if it doesn't exist on ZEVM. ### Features diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index 0fee34f8a9..62e0182ecf 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -257,6 +257,8 @@ func localE2ETest(cmd *cobra.Command, _ []string) { e2etests.TestMessagePassingEVMtoZEVMName, e2etests.TestMessagePassingEVMtoZEVMRevertName, e2etests.TestMessagePassingZEVMtoEVMRevertName, + e2etests.TestZetaDepositName, + e2etests.TestZetaDepositNewAddressName, } bitcoinTests := []string{ e2etests.TestBitcoinWithdrawSegWitName, diff --git a/e2e/e2etests/e2etests.go b/e2e/e2etests/e2etests.go index 09197c874a..583606ec50 100644 --- a/e2e/e2etests/e2etests.go +++ b/e2e/e2etests/e2etests.go @@ -44,6 +44,7 @@ const ( TestEtherWithdrawRestrictedName = "eth_withdraw_restricted" TestBitcoinDepositName = "bitcoin_deposit" TestZetaDepositName = "zeta_deposit" + TestZetaDepositNewAddressName = "zeta_deposit_new_address" TestZetaDepositRestrictedName = "zeta_deposit_restricted" TestDonationEtherName = "donation_ether" @@ -120,6 +121,14 @@ var AllE2ETests = []runner.E2ETest{ }, TestZetaDeposit, ), + runner.NewE2ETest( + TestZetaDepositNewAddressName, + "deposit ZETA from Ethereum to a new ZEVM address which does not exist yet", + []runner.ArgDefinition{ + runner.ArgDefinition{Description: "amount in azeta", DefaultValue: "1000000000000000000"}, + }, + TestZetaDepositNewAddress, + ), runner.NewE2ETest( TestZetaWithdrawBTCRevertName, "sending ZETA from ZEVM to Bitcoin with a message that should revert cctxs", diff --git a/e2e/e2etests/test_zeta_deposit.go b/e2e/e2etests/test_zeta_deposit.go index 237cc66d0d..48ac5e97f4 100644 --- a/e2e/e2etests/test_zeta_deposit.go +++ b/e2e/e2etests/test_zeta_deposit.go @@ -6,6 +6,7 @@ import ( ethcommon "github.com/ethereum/go-ethereum/common" "github.com/zeta-chain/zetacore/e2e/runner" "github.com/zeta-chain/zetacore/e2e/utils" + "github.com/zeta-chain/zetacore/testutil/sample" "github.com/zeta-chain/zetacore/zetaclient/testutils" ) @@ -26,6 +27,24 @@ func TestZetaDeposit(r *runner.E2ERunner, args []string) { r.Logger.CCTX(*cctx, "deposit") } +func TestZetaDepositNewAddress(r *runner.E2ERunner, args []string) { + if len(args) != 1 { + panic("TestZetaDepositNewAddress requires exactly one argument for the amount.") + } + + amount, ok := big.NewInt(0).SetString(args[0], 10) + if !ok { + panic("Invalid amount specified for TestZetaDepositNewAddress.") + } + + newAddress := sample.EthAddress() + hash := r.DepositZetaWithAmount(newAddress, amount) + + // wait for the cctx to be mined + cctx := utils.WaitCctxMinedByInTxHash(r.Ctx, hash.Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + r.Logger.CCTX(*cctx, "deposit") +} + func TestZetaDepositRestricted(r *runner.E2ERunner, args []string) { if len(args) != 1 { panic("TestZetaDepositRestricted requires exactly one argument for the amount.") diff --git a/x/crosschain/keeper/process_outbound_test.go b/x/crosschain/keeper/process_outbound_test.go index b4dbbef200..7ec80270f5 100644 --- a/x/crosschain/keeper/process_outbound_test.go +++ b/x/crosschain/keeper/process_outbound_test.go @@ -5,6 +5,7 @@ import ( "math/big" "testing" + "cosmossdk.io/errors" ethcommon "github.com/ethereum/go-ethereum/common" "github.com/evmos/ethermint/x/evm/statedb" "github.com/stretchr/testify/mock" @@ -77,10 +78,22 @@ func TestKeeper_ProcessFailedOutbound(t *testing.T) { }) t.Run("unable to process failed outbound if ZETARevertAndCallContract fails", func(t *testing.T) { - k, ctx, _, _ := keepertest.CrosschainKeeper(t) + k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ + UseFungibleMock: true, + }) + fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) receiver := sample.EthAddress() + errorFailedZETARevertAndCallContract := errors.New("test", 999, "failed ZETARevertAndCallContract") cctx := GetERC20Cctx(t, receiver, chains.GoerliChain(), "", big.NewInt(42)) cctx.InboundTxParams.SenderChainId = chains.ZetaChainMainnet().ChainId + fungibleMock.On("ZETARevertAndCallContract", mock.Anything, + ethcommon.HexToAddress(cctx.InboundTxParams.Sender), + ethcommon.HexToAddress(cctx.GetCurrentOutTxParam().Receiver), + cctx.InboundTxParams.SenderChainId, + cctx.GetCurrentOutTxParam().ReceiverChainId, + cctx.GetCurrentOutTxParam().Amount.BigInt(), + mock.Anything, + mock.Anything).Return(nil, errorFailedZETARevertAndCallContract).Once() err := k.ProcessFailedOutbound(ctx, cctx, sample.String()) require.ErrorContains(t, err, "failed ZETARevertAndCallContract") }) diff --git a/x/fungible/keeper/zevm_message_passing_test.go b/x/fungible/keeper/zevm_message_passing_test.go index b37621667d..fe03808e81 100644 --- a/x/fungible/keeper/zevm_message_passing_test.go +++ b/x/fungible/keeper/zevm_message_passing_test.go @@ -84,8 +84,8 @@ func TestKeeper_ZEVMDepositAndCallContract(t *testing.T) { require.Equal(t, inboundAmount.Int64(), b.Amount.Int64()) }) - t.Run("fail ZETADepositAndCallContract if account not found", func(t *testing.T) { - k, ctx, _, _ := keepertest.FungibleKeeper(t) + t.Run("automatically deposit coin if account not found", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) _ = k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) zetaTxSender := sample.EthAddress() @@ -96,11 +96,12 @@ func TestKeeper_ZEVMDepositAndCallContract(t *testing.T) { cctxIndexBytes := [32]byte{} _, err := k.ZETADepositAndCallContract(ctx, zetaTxSender, zetaTxReceiver, inboundSenderChainID, inboundAmount, data, cctxIndexBytes) - require.ErrorIs(t, err, types.ErrAccountNotFound) - require.ErrorContains(t, err, "account not found") + require.NoError(t, err) + b := sdkk.BankKeeper.GetBalance(ctx, sdk.AccAddress(zetaTxReceiver.Bytes()), config.BaseDenom) + require.Equal(t, inboundAmount.Int64(), b.Amount.Int64()) }) - t.Run("fail ZETADepositAndCallContract id Deposit Fails", func(t *testing.T) { + t.Run("fail ZETADepositAndCallContract if Deposit Fails", func(t *testing.T) { k, ctx, sdkk, _ := keepertest.FungibleKeeperWithMocks(t, keepertest.FungibleMockOptions{UseBankMock: true}) _ = k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) @@ -196,8 +197,8 @@ func TestKeeper_ZEVMRevertAndCallContract(t *testing.T) { require.Equal(t, amount.Int64(), b.Amount.Int64()) }) - t.Run("fail ZETARevertAndCallContract if account not found", func(t *testing.T) { - k, ctx, _, _ := keepertest.FungibleKeeper(t) + t.Run("automatically deposit coin if account not found", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) _ = k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) zetaTxSender := sample.EthAddress() @@ -209,8 +210,9 @@ func TestKeeper_ZEVMRevertAndCallContract(t *testing.T) { cctxIndexBytes := [32]byte{} _, err := k.ZETARevertAndCallContract(ctx, zetaTxSender, zetaTxReceiver, senderChainID.Int64(), destinationChainID.Int64(), amount, data, cctxIndexBytes) - require.ErrorIs(t, err, types.ErrAccountNotFound) - require.ErrorContains(t, err, "account not found") + require.NoError(t, err) + b := sdkk.BankKeeper.GetBalance(ctx, sdk.AccAddress(zetaTxSender.Bytes()), config.BaseDenom) + require.Equal(t, amount.Int64(), b.Amount.Int64()) }) t.Run("fail ZETARevertAndCallContract if Deposit Fails", func(t *testing.T) { diff --git a/x/fungible/keeper/zevm_msg_passing.go b/x/fungible/keeper/zevm_msg_passing.go index ab3210c002..827a937ae8 100644 --- a/x/fungible/keeper/zevm_msg_passing.go +++ b/x/fungible/keeper/zevm_msg_passing.go @@ -1,18 +1,15 @@ package keeper import ( - "fmt" "math/big" - "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" ethcommon "github.com/ethereum/go-ethereum/common" evmtypes "github.com/evmos/ethermint/x/evm/types" - "github.com/zeta-chain/zetacore/x/fungible/types" ) -// ZEVMDepositAndCallContract deposits ZETA to the to address if its an account -// If it's not an account it calls onReceive function of the connector contract and provides the address as the destinationAddress +// ZETADepositAndCallContract deposits native ZETA to the to address if its an account or if the account does not exist yet +// If it's not an account it calls onReceive function of the connector contract and provides the address as the destinationAddress .The amount of tokens is minted to the fungible module account, wrapped and sent to the contract func (k Keeper) ZETADepositAndCallContract(ctx sdk.Context, sender ethcommon.Address, to ethcommon.Address, @@ -21,10 +18,7 @@ func (k Keeper) ZETADepositAndCallContract(ctx sdk.Context, data []byte, indexBytes [32]byte) (*evmtypes.MsgEthereumTxResponse, error) { acc := k.evmKeeper.GetAccount(ctx, to) - if acc == nil { - return nil, errors.Wrap(types.ErrAccountNotFound, fmt.Sprintf("address: %s", to.String())) - } - if !acc.IsContract() { + if acc == nil || !acc.IsContract() { err := k.DepositCoinZeta(ctx, to, inboundAmount) if err != nil { return nil, err @@ -35,8 +29,8 @@ func (k Keeper) ZETADepositAndCallContract(ctx sdk.Context, return k.CallOnReceiveZevmConnector(ctx, sender.Bytes(), big.NewInt(inboundSenderChainID), to, inboundAmount, data, indexBytes) } -// ZEVMRevertAndCallContract deposits ZETA to the sender address if its an account -// If it's not an account it calls onRevert function of the connector contract and provides the sender address as the zetaTxSenderAddress +// ZETARevertAndCallContract deposits native ZETA to the sender address if its account or if the account does not exist yet +// If it's not an account it calls onRevert function of the connector contract and provides the sender address as the zetaTxSenderAddress.The amount of tokens is minted to the fungible module account, wrapped and sent to the contract func (k Keeper) ZETARevertAndCallContract(ctx sdk.Context, sender ethcommon.Address, to ethcommon.Address, @@ -46,10 +40,7 @@ func (k Keeper) ZETARevertAndCallContract(ctx sdk.Context, data []byte, indexBytes [32]byte) (*evmtypes.MsgEthereumTxResponse, error) { acc := k.evmKeeper.GetAccount(ctx, sender) - if acc == nil { - return nil, errors.Wrap(types.ErrAccountNotFound, fmt.Sprintf("address: %s", to.String())) - } - if !acc.IsContract() { + if acc == nil || !acc.IsContract() { err := k.DepositCoinZeta(ctx, sender, remainingAmount) if err != nil { return nil, err