diff --git a/testutil/keeper/crosschain.go b/testutil/keeper/crosschain.go index 6f350aa5ed..40ce858497 100644 --- a/testutil/keeper/crosschain.go +++ b/testutil/keeper/crosschain.go @@ -359,6 +359,7 @@ func MockRevertForHandleEVMDeposit( mock.Anything, coin.CoinType_ERC20, mock.Anything, + mock.Anything, ).Return(&evmtypes.MsgEthereumTxResponse{VmError: "reverted"}, false, errDeposit) } diff --git a/testutil/keeper/mocks/crosschain/fungible.go b/testutil/keeper/mocks/crosschain/fungible.go index 9eab868481..5612e1470f 100644 --- a/testutil/keeper/mocks/crosschain/fungible.go +++ b/testutil/keeper/mocks/crosschain/fungible.go @@ -8,6 +8,8 @@ import ( common "github.com/ethereum/go-ethereum/common" coin "github.com/zeta-chain/zetacore/pkg/coin" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" + evmtypes "github.com/evmos/ethermint/x/evm/types" fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" @@ -677,9 +679,9 @@ func (_m *CrosschainFungibleKeeper) ZETARevertAndCallContract(ctx types.Context, return r0, r1 } -// ZRC20DepositAndCallContract provides a mock function with given fields: ctx, from, to, amount, senderChainID, data, coinType, asset -func (_m *CrosschainFungibleKeeper) ZRC20DepositAndCallContract(ctx types.Context, from []byte, to common.Address, amount *big.Int, senderChainID int64, data []byte, coinType coin.CoinType, asset string) (*evmtypes.MsgEthereumTxResponse, bool, error) { - ret := _m.Called(ctx, from, to, amount, senderChainID, data, coinType, asset) +// ZRC20DepositAndCallContract provides a mock function with given fields: ctx, from, to, amount, senderChainID, data, coinType, asset, protocolContractVersion +func (_m *CrosschainFungibleKeeper) ZRC20DepositAndCallContract(ctx types.Context, from []byte, to common.Address, amount *big.Int, senderChainID int64, data []byte, coinType coin.CoinType, asset string, protocolContractVersion crosschaintypes.ProtocolContractVersion) (*evmtypes.MsgEthereumTxResponse, bool, error) { + ret := _m.Called(ctx, from, to, amount, senderChainID, data, coinType, asset, protocolContractVersion) if len(ret) == 0 { panic("no return value specified for ZRC20DepositAndCallContract") @@ -688,25 +690,25 @@ func (_m *CrosschainFungibleKeeper) ZRC20DepositAndCallContract(ctx types.Contex var r0 *evmtypes.MsgEthereumTxResponse var r1 bool var r2 error - if rf, ok := ret.Get(0).(func(types.Context, []byte, common.Address, *big.Int, int64, []byte, coin.CoinType, string) (*evmtypes.MsgEthereumTxResponse, bool, error)); ok { - return rf(ctx, from, to, amount, senderChainID, data, coinType, asset) + if rf, ok := ret.Get(0).(func(types.Context, []byte, common.Address, *big.Int, int64, []byte, coin.CoinType, string, crosschaintypes.ProtocolContractVersion) (*evmtypes.MsgEthereumTxResponse, bool, error)); ok { + return rf(ctx, from, to, amount, senderChainID, data, coinType, asset, protocolContractVersion) } - if rf, ok := ret.Get(0).(func(types.Context, []byte, common.Address, *big.Int, int64, []byte, coin.CoinType, string) *evmtypes.MsgEthereumTxResponse); ok { - r0 = rf(ctx, from, to, amount, senderChainID, data, coinType, asset) + if rf, ok := ret.Get(0).(func(types.Context, []byte, common.Address, *big.Int, int64, []byte, coin.CoinType, string, crosschaintypes.ProtocolContractVersion) *evmtypes.MsgEthereumTxResponse); ok { + r0 = rf(ctx, from, to, amount, senderChainID, data, coinType, asset, protocolContractVersion) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*evmtypes.MsgEthereumTxResponse) } } - if rf, ok := ret.Get(1).(func(types.Context, []byte, common.Address, *big.Int, int64, []byte, coin.CoinType, string) bool); ok { - r1 = rf(ctx, from, to, amount, senderChainID, data, coinType, asset) + if rf, ok := ret.Get(1).(func(types.Context, []byte, common.Address, *big.Int, int64, []byte, coin.CoinType, string, crosschaintypes.ProtocolContractVersion) bool); ok { + r1 = rf(ctx, from, to, amount, senderChainID, data, coinType, asset, protocolContractVersion) } else { r1 = ret.Get(1).(bool) } - if rf, ok := ret.Get(2).(func(types.Context, []byte, common.Address, *big.Int, int64, []byte, coin.CoinType, string) error); ok { - r2 = rf(ctx, from, to, amount, senderChainID, data, coinType, asset) + if rf, ok := ret.Get(2).(func(types.Context, []byte, common.Address, *big.Int, int64, []byte, coin.CoinType, string, crosschaintypes.ProtocolContractVersion) error); ok { + r2 = rf(ctx, from, to, amount, senderChainID, data, coinType, asset, protocolContractVersion) } else { r2 = ret.Error(2) } diff --git a/x/crosschain/keeper/evm_deposit.go b/x/crosschain/keeper/evm_deposit.go index b1e55e3f6f..c82dd1705c 100644 --- a/x/crosschain/keeper/evm_deposit.go +++ b/x/crosschain/keeper/evm_deposit.go @@ -92,6 +92,7 @@ func (k Keeper) HandleEVMDeposit(ctx sdk.Context, cctx *types.CrossChainTx) (boo data, inboundCoinType, cctx.InboundParams.Asset, + cctx.ProtocolContractVersion, ) if fungibletypes.IsContractReverted(evmTxResponse, err) || errShouldRevertCctx(err) { return true, err diff --git a/x/crosschain/keeper/evm_deposit_test.go b/x/crosschain/keeper/evm_deposit_test.go index 7541c21aa6..45e8a5547e 100644 --- a/x/crosschain/keeper/evm_deposit_test.go +++ b/x/crosschain/keeper/evm_deposit_test.go @@ -67,7 +67,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { fungibleMock.On("ZETADepositAndCallContract", ctx, ethcommon.HexToAddress(sender.String()), receiver, senderChainId, amount, mock.Anything, mock.Anything). Return(nil, errDeposit) - // call HandleEVMDeposit + // call HandleEVMDeposit cctx.InboundParams.Sender = sender.String() cctx.GetCurrentOutboundParam().Receiver = receiver.String() @@ -106,6 +106,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { mock.Anything, coin.CoinType_ERC20, mock.Anything, + mock.Anything, ).Return(&evmtypes.MsgEthereumTxResponse{}, false, nil) // call HandleEVMDeposit @@ -151,6 +152,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { mock.Anything, coin.CoinType_ERC20, mock.Anything, + mock.Anything, ).Return(&evmtypes.MsgEthereumTxResponse{ Logs: []*evmtypes.Log{ { @@ -213,6 +215,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { mock.Anything, coin.CoinType_ERC20, mock.Anything, + mock.Anything, ).Return(&evmtypes.MsgEthereumTxResponse{ Logs: []*evmtypes.Log{ { @@ -302,6 +305,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { mock.Anything, coin.CoinType_ERC20, mock.Anything, + mock.Anything, ).Return(&evmtypes.MsgEthereumTxResponse{}, false, errDeposit) // call HandleEVMDeposit @@ -346,6 +350,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { mock.Anything, coin.CoinType_ERC20, mock.Anything, + mock.Anything, ).Return(&evmtypes.MsgEthereumTxResponse{VmError: "reverted"}, false, errDeposit) // call HandleEVMDeposit @@ -389,6 +394,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { mock.Anything, coin.CoinType_ERC20, mock.Anything, + mock.Anything, ).Return(&evmtypes.MsgEthereumTxResponse{}, false, fungibletypes.ErrForeignCoinCapReached) // call HandleEVMDeposit @@ -432,6 +438,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { mock.Anything, coin.CoinType_ERC20, mock.Anything, + mock.Anything, ).Return(&evmtypes.MsgEthereumTxResponse{}, false, fungibletypes.ErrPausedZRC20) // call HandleEVMDeposit @@ -475,6 +482,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { mock.Anything, coin.CoinType_ERC20, mock.Anything, + mock.Anything, ).Return(&evmtypes.MsgEthereumTxResponse{}, false, fungibletypes.ErrCallNonContract) // call HandleEVMDeposit @@ -544,6 +552,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { data, coin.CoinType_ERC20, mock.Anything, + mock.Anything, ).Return(&evmtypes.MsgEthereumTxResponse{}, false, nil) cctx.GetCurrentOutboundParam().Receiver = sample.EthAddress().String() @@ -586,6 +595,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { data, coin.CoinType_ERC20, mock.Anything, + mock.Anything, ).Return(&evmtypes.MsgEthereumTxResponse{}, false, nil) cctx := sample.CrossChainTx(t, "foo") diff --git a/x/crosschain/types/expected_keepers.go b/x/crosschain/types/expected_keepers.go index 1422538b2f..79b9809a10 100644 --- a/x/crosschain/types/expected_keepers.go +++ b/x/crosschain/types/expected_keepers.go @@ -145,6 +145,7 @@ type FungibleKeeper interface { data []byte, coinType coin.CoinType, asset string, + protocolContractVersion ProtocolContractVersion, ) (*evmtypes.MsgEthereumTxResponse, bool, error) CallUniswapV2RouterSwapExactTokensForTokens( ctx sdk.Context, diff --git a/x/fungible/keeper/deposits.go b/x/fungible/keeper/deposits.go index cc3a55f856..20dfbad327 100644 --- a/x/fungible/keeper/deposits.go +++ b/x/fungible/keeper/deposits.go @@ -25,18 +25,18 @@ func (k Keeper) DepositCoinsToFungibleModule(ctx sdk.Context, amount *big.Int) e // ZRC20DepositAndCallContract deposits ZRC20 to the EVM account and calls the contract // returns [txResponse, isContractCall, error] -// isContractCall is true if the receiver is a contract and a contract call was made func (k Keeper) ZRC20DepositAndCallContract( ctx sdk.Context, from []byte, to eth.Address, amount *big.Int, senderChainID int64, - data []byte, + message []byte, coinType coin.CoinType, asset string, + protocolContractVersion crosschaintypes.ProtocolContractVersion, ) (*evmtypes.MsgEthereumTxResponse, bool, error) { - var ZRC20Contract eth.Address + var zrc20Contract eth.Address var foreignCoin types.ForeignCoins var found bool @@ -52,7 +52,7 @@ func (k Keeper) ZRC20DepositAndCallContract( return nil, false, crosschaintypes.ErrForeignCoinNotFound } } - ZRC20Contract = eth.HexToAddress(foreignCoin.Zrc20ContractAddress) + zrc20Contract = eth.HexToAddress(foreignCoin.Zrc20ContractAddress) // check if foreign coin is paused if foreignCoin.Paused { @@ -62,7 +62,7 @@ func (k Keeper) ZRC20DepositAndCallContract( // check foreign coins cap if it has a cap if !foreignCoin.LiquidityCap.IsNil() && !foreignCoin.LiquidityCap.IsZero() { liquidityCap := foreignCoin.LiquidityCap.BigInt() - totalSupply, err := k.TotalSupplyZRC4(ctx, ZRC20Contract) + totalSupply, err := k.TotalSupplyZRC4(ctx, zrc20Contract) if err != nil { return nil, false, err } @@ -72,6 +72,11 @@ func (k Keeper) ZRC20DepositAndCallContract( } } + // handle the deposit for protocol contract version 2 + if protocolContractVersion == crosschaintypes.ProtocolContractVersion_V2 { + return k.ProcessV2Deposit(ctx, zrc20Contract, to, amount, message) + } + // check if the receiver is a contract // if it is, then the hook onCrossChainCall() will be called // if not, the zrc20 are simply transferred to the receiver @@ -82,15 +87,15 @@ func (k Keeper) ZRC20DepositAndCallContract( Sender: eth.Address{}, ChainID: big.NewInt(senderChainID), } - res, err := k.DepositZRC20AndCallContract(ctx, context, ZRC20Contract, to, amount, data) + res, err := k.DepositZRC20AndCallContract(ctx, context, zrc20Contract, to, amount, message) return res, true, err } // if the account is a EOC, no contract call can be made with the data - if len(data) > 0 { + if len(message) > 0 { return nil, false, types.ErrCallNonContract } - res, err := k.DepositZRC20(ctx, ZRC20Contract, to, amount) + res, err := k.DepositZRC20(ctx, zrc20Contract, to, amount) return res, false, err } diff --git a/x/fungible/keeper/deposits_test.go b/x/fungible/keeper/deposits_test.go index e394053edd..a0babf82ac 100644 --- a/x/fungible/keeper/deposits_test.go +++ b/x/fungible/keeper/deposits_test.go @@ -44,6 +44,7 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { []byte{}, coin.CoinType_Gas, sample.EthAddress().String(), + crosschaintypes.ProtocolContractVersion_V1, ) require.NoError(t, err) require.False(t, contractCall) @@ -76,6 +77,7 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { []byte{}, coin.CoinType_ERC20, assetAddress, + crosschaintypes.ProtocolContractVersion_V1, ) require.NoError(t, err) require.False(t, contractCall) @@ -108,6 +110,7 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { []byte("DEADBEEF"), coin.CoinType_ERC20, assetAddress, + crosschaintypes.ProtocolContractVersion_V1, ) require.ErrorIs(t, err, types.ErrCallNonContract) }) @@ -149,6 +152,7 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { []byte{}, coin.CoinType_Gas, sample.EthAddress().String(), + crosschaintypes.ProtocolContractVersion_V1, ) require.NoError(t, err) require.False(t, contractCall) @@ -186,6 +190,7 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { []byte{}, coin.CoinType_Gas, sample.EthAddress().String(), + crosschaintypes.ProtocolContractVersion_V1, ) require.ErrorIs(t, err, types.ErrPausedZRC20) }) @@ -227,6 +232,7 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { []byte{}, coin.CoinType_Gas, sample.EthAddress().String(), + crosschaintypes.ProtocolContractVersion_V1, ) require.ErrorIs(t, err, types.ErrForeignCoinCapReached) }) @@ -253,6 +259,7 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { []byte{}, coin.CoinType_Gas, sample.EthAddress().String(), + crosschaintypes.ProtocolContractVersion_V1, ) require.ErrorIs(t, err, crosschaintypes.ErrGasCoinNotFound) }) @@ -279,6 +286,7 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { []byte{}, coin.CoinType_ERC20, assetAddress, + crosschaintypes.ProtocolContractVersion_V1, ) require.ErrorIs(t, err, crosschaintypes.ErrForeignCoinNotFound) }) @@ -309,6 +317,7 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { []byte{}, coin.CoinType_Gas, sample.EthAddress().String(), + crosschaintypes.ProtocolContractVersion_V1, ) require.NoError(t, err) require.True(t, contractCall) @@ -347,6 +356,7 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { []byte{}, coin.CoinType_Gas, sample.EthAddress().String(), + crosschaintypes.ProtocolContractVersion_V1, ) require.Error(t, err) require.True(t, contractCall) diff --git a/x/fungible/keeper/v2_deposits.go b/x/fungible/keeper/v2_deposits.go new file mode 100644 index 0000000000..09c720a320 --- /dev/null +++ b/x/fungible/keeper/v2_deposits.go @@ -0,0 +1,29 @@ +package keeper + +import ( + "errors" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + ethcommon "github.com/ethereum/go-ethereum/common" + evmtypes "github.com/evmos/ethermint/x/evm/types" +) + +// ProcessV2Deposit handles a deposit from an inbound tx with protocol version 2 +// returns [txResponse, isContractCall, error] +// isContractCall is true if the message is non empty +func (k Keeper) ProcessV2Deposit( + ctx sdk.Context, + zrc20Addr ethcommon.Address, + to ethcommon.Address, + amount *big.Int, + message []byte, +) (*evmtypes.MsgEthereumTxResponse, bool, error) { + if len(message) == 0 { + // simple deposit + res, err := k.DepositZRC20(ctx, zrc20Addr, to, amount) + return res, false, err + } else { + return nil, true, errors.New("not implemented") + } +}