From 17e084ed92f68d02bcf76095b4b2f0c6b0585e79 Mon Sep 17 00:00:00 2001 From: lumtis Date: Sat, 10 Aug 2024 16:12:56 +0200 Subject: [PATCH] simple call implementation --- cmd/zetae2e/local/local.go | 8 +- docs/openapi/openapi.swagger.yaml | 4 +- e2e/e2etests/test_v2_evm_to_zevm_call.go | 12 +- e2e/e2etests/test_v2_zevm_to_evm_call.go | 13 +- e2e/runner/v2_setup_zeta.go | 1 + pkg/contracts/testdappv2/TestDAppV2.sol | 1 + .../zetachain/zetacore/pkg/coin/coin_pb.d.ts | 9 +- x/fungible/keeper/deposits.go | 8 +- x/fungible/keeper/v2_deposits.go | 14 +- x/fungible/keeper/v2_deposits_test.go | 3 + x/fungible/keeper/v2_evm.go | 50 +++++ zetaclient/chains/evm/cctx.go | 4 + zetaclient/chains/evm/observer/inbound.go | 17 +- zetaclient/chains/evm/observer/v2_inbound.go | 199 +++++++++++++++--- zetaclient/chains/evm/observer/v2_outbound.go | 7 +- zetaclient/chains/evm/signer/sign.go | 117 ---------- zetaclient/chains/evm/signer/signer.go | 20 +- zetaclient/chains/evm/signer/v2_sign.go | 126 +++++++++++ zetaclient/chains/evm/signer/v2_signer.go | 10 +- 19 files changed, 437 insertions(+), 186 deletions(-) create mode 100644 zetaclient/chains/evm/signer/v2_sign.go diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index d318642ba9..74ebdffcf2 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -346,12 +346,12 @@ func localE2ETest(cmd *cobra.Command, _ []string) { //e2etests.TestV2ETHDepositAndCallName, //e2etests.TestV2ETHWithdrawName, //e2etests.TestV2ETHWithdrawAndCallName, - e2etests.TestV2ERC20DepositName, + //e2etests.TestV2ERC20DepositName, //e2etests.TestV2ERC20DepositAndCallName, //e2etests.TestV2ERC20WithdrawName, - e2etests.TestV2ERC20WithdrawAndCallName, - //e2etests.TestV2ZEVMToEVMCallName, - //e2etests.TestV2EVMToZEVMCallName, + //e2etests.TestV2ERC20WithdrawAndCallName, + e2etests.TestV2ZEVMToEVMCallName, + e2etests.TestV2EVMToZEVMCallName, )) } diff --git a/docs/openapi/openapi.swagger.yaml b/docs/openapi/openapi.swagger.yaml index 9e8598e15e..21cf25f5f0 100644 --- a/docs/openapi/openapi.swagger.yaml +++ b/docs/openapi/openapi.swagger.yaml @@ -57071,11 +57071,13 @@ definitions: - Gas - ERC20 - Cmd + - NoAssetCall default: Zeta title: |- - Gas: Ether, BNB, Matic, Klay, BTC, etc - ERC20: ERC20 token - - Cmd: not a real coin, rather a command + - Cmd: not asset, used for admin command + - NoAssetCall: no asset, used for contract call crosschainCctxStatus: type: string enum: diff --git a/e2e/e2etests/test_v2_evm_to_zevm_call.go b/e2e/e2etests/test_v2_evm_to_zevm_call.go index 47c7bdf7e8..24e980eaab 100644 --- a/e2e/e2etests/test_v2_evm_to_zevm_call.go +++ b/e2e/e2etests/test_v2_evm_to_zevm_call.go @@ -1,6 +1,8 @@ package e2etests import ( + "math/big" + "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/e2e/runner" @@ -8,17 +10,19 @@ import ( crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" ) +const payloadMessageZEVMCall = "this is a test ZEVM call payload" + func TestV2EVMToZEVMCall(r *runner.E2ERunner, args []string) { require.Len(r, args, 0) - // TODO: set payload - payload := []byte("") - // perform the withdraw - tx := r.V2EVMToZEMVCall(r.EVMAddress(), payload) + tx := r.V2EVMToZEMVCall(r.TestDAppV2ZEVMAddr, []byte(payloadMessageZEVMCall)) // wait for the cctx to be mined cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) r.Logger.CCTX(*cctx, "call") require.Equal(r, crosschaintypes.CctxStatus_OutboundMined, cctx.CctxStatus.Status) + + // check the payload was received on the contract + r.AssertTestDAppZEVMValues(true, payloadMessageZEVMCall, big.NewInt(0)) } diff --git a/e2e/e2etests/test_v2_zevm_to_evm_call.go b/e2e/e2etests/test_v2_zevm_to_evm_call.go index cc01f0ebd1..91a1098be4 100644 --- a/e2e/e2etests/test_v2_zevm_to_evm_call.go +++ b/e2e/e2etests/test_v2_zevm_to_evm_call.go @@ -1,6 +1,8 @@ package e2etests import ( + "math/big" + "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/e2e/runner" @@ -8,17 +10,22 @@ import ( crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" ) +const payloadMessageEVMCall = "this is a test EVM call payload" + func TestV2ZEVMToEVMCall(r *runner.E2ERunner, args []string) { require.Len(r, args, 0) - // TODO: set payload - payload := []byte("") + // Necessary approval for fee payment + r.ApproveETHZRC20(r.GatewayZEVMAddr) // perform the withdraw - tx := r.V2ZEVMToEMVCall(r.EVMAddress(), payload) + tx := r.V2ZEVMToEMVCall(r.TestDAppV2EVMAddr, r.EncodeSimpleCall(payloadMessageEVMCall)) // wait for the cctx to be mined cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) r.Logger.CCTX(*cctx, "call") require.Equal(r, crosschaintypes.CctxStatus_OutboundMined, cctx.CctxStatus.Status) + + // check the payload was received on the contract + r.AssertTestDAppEVMValues(true, payloadMessageEVMCall, big.NewInt(0)) } diff --git a/e2e/runner/v2_setup_zeta.go b/e2e/runner/v2_setup_zeta.go index 38254ae4ea..c82e8fa741 100644 --- a/e2e/runner/v2_setup_zeta.go +++ b/e2e/runner/v2_setup_zeta.go @@ -82,6 +82,7 @@ func (r *E2ERunner) UpdateChainParamsERC20CustodyContract() { require.NoError(r, err) evmChainID, err := r.EVMClient.ChainID(r.Ctx) + require.NoError(r, err) // find old chain params var ( diff --git a/pkg/contracts/testdappv2/TestDAppV2.sol b/pkg/contracts/testdappv2/TestDAppV2.sol index cd957ba975..3a26ad258e 100644 --- a/pkg/contracts/testdappv2/TestDAppV2.sol +++ b/pkg/contracts/testdappv2/TestDAppV2.sol @@ -54,6 +54,7 @@ contract TestDAppV2 { require(!isRevertMessage(bytes(message))); lastMessage = message; + lastAmount = 0; } function isRevertMessage(bytes memory message) internal pure returns (bool) { diff --git a/typescript/zetachain/zetacore/pkg/coin/coin_pb.d.ts b/typescript/zetachain/zetacore/pkg/coin/coin_pb.d.ts index 2ad47ca416..90147498eb 100644 --- a/typescript/zetachain/zetacore/pkg/coin/coin_pb.d.ts +++ b/typescript/zetachain/zetacore/pkg/coin/coin_pb.d.ts @@ -27,10 +27,17 @@ export declare enum CoinType { ERC20 = 2, /** - * not a real coin, rather a command + * not asset, used for admin command * * @generated from enum value: Cmd = 3; */ Cmd = 3, + + /** + * no asset, used for contract call + * + * @generated from enum value: NoAssetCall = 4; + */ + NoAssetCall = 4, } diff --git a/x/fungible/keeper/deposits.go b/x/fungible/keeper/deposits.go index 5287763346..18fab24525 100644 --- a/x/fungible/keeper/deposits.go +++ b/x/fungible/keeper/deposits.go @@ -41,7 +41,11 @@ func (k Keeper) ZRC20DepositAndCallContract( var found bool // get foreign coin - if coinType == coin.CoinType_Gas { + // retrieve the gas token of the chain for no asset call + // this simplify the current workflow and allow to pause calls by pausing the gas token + // TODO: refactor this logic and create specific workflow for no asset call + // https://github.com/zeta-chain/node/issues/2627 + if coinType == coin.CoinType_Gas || coinType == coin.CoinType_NoAssetCall { foreignCoin, found = k.GetGasCoinForForeignCoin(ctx, senderChainID) if !found { return nil, false, crosschaintypes.ErrGasCoinNotFound @@ -74,7 +78,7 @@ func (k Keeper) ZRC20DepositAndCallContract( // handle the deposit for protocol contract version 2 if protocolContractVersion == crosschaintypes.ProtocolContractVersion_V2 { - return k.ProcessV2Deposit(ctx, from, senderChainID, zrc20Contract, to, amount, message) + return k.ProcessV2Deposit(ctx, from, senderChainID, zrc20Contract, to, amount, message, coinType) } // check if the receiver is a contract diff --git a/x/fungible/keeper/v2_deposits.go b/x/fungible/keeper/v2_deposits.go index 99bc860c17..da54f27cd6 100644 --- a/x/fungible/keeper/v2_deposits.go +++ b/x/fungible/keeper/v2_deposits.go @@ -7,6 +7,8 @@ import ( ethcommon "github.com/ethereum/go-ethereum/common" evmtypes "github.com/evmos/ethermint/x/evm/types" "github.com/zeta-chain/protocol-contracts/v2/pkg/systemcontract.sol" + + "github.com/zeta-chain/zetacore/pkg/coin" ) // ProcessV2Deposit handles a deposit from an inbound tx with protocol version 2 @@ -20,14 +22,22 @@ func (k Keeper) ProcessV2Deposit( to ethcommon.Address, amount *big.Int, message []byte, + coinType coin.CoinType, ) (*evmtypes.MsgEthereumTxResponse, bool, error) { - // simple deposit if len(message) == 0 { // simple deposit res, err := k.DepositZRC20(ctx, zrc20Addr, to, amount) return res, false, err + } else if coinType == coin.CoinType_NoAssetCall { + // simple call + context := systemcontract.ZContext{ + Origin: from, + Sender: ethcommon.Address{}, + ChainID: big.NewInt(senderChainID), + } + res, err := k.CallExecute(ctx, context, zrc20Addr, amount, to, message) + return res, true, err } - // deposit and call context := systemcontract.ZContext{ Origin: from, diff --git a/x/fungible/keeper/v2_deposits_test.go b/x/fungible/keeper/v2_deposits_test.go index cee5c2f2b1..98aeeb71b9 100644 --- a/x/fungible/keeper/v2_deposits_test.go +++ b/x/fungible/keeper/v2_deposits_test.go @@ -5,6 +5,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/pkg/chains" + "github.com/zeta-chain/zetacore/pkg/coin" "github.com/zeta-chain/zetacore/pkg/contracts/testdappv2" keepertest "github.com/zeta-chain/zetacore/testutil/keeper" "github.com/zeta-chain/zetacore/testutil/sample" @@ -101,6 +102,7 @@ func TestKeeper_ProcessV2Deposit(t *testing.T) { receiver, big.NewInt(42), []byte{}, + coin.CoinType_Gas, ) // ASSERT @@ -135,6 +137,7 @@ func TestKeeper_ProcessV2Deposit(t *testing.T) { testDapp, big.NewInt(82), []byte("foo"), + coin.CoinType_Gas, ) // ASSERT diff --git a/x/fungible/keeper/v2_evm.go b/x/fungible/keeper/v2_evm.go index add7acca32..2989672597 100644 --- a/x/fungible/keeper/v2_evm.go +++ b/x/fungible/keeper/v2_evm.go @@ -65,3 +65,53 @@ func (k Keeper) CallDepositAndCallZRC20( message, ) } + +// CallExecute calls the execute function on the gateway contract +// function execute( +// +// zContext calldata context, +// address zrc20, +// uint256 amount, +// address target, +// bytes calldata message +// +// ) +func (k Keeper) CallExecute( + ctx sdk.Context, + context systemcontract.ZContext, + zrc20 common.Address, + amount *big.Int, + target common.Address, + message []byte, +) (*evmtypes.MsgEthereumTxResponse, error) { + gatewayABI, err := gatewayzevm.GatewayZEVMMetaData.GetAbi() + if err != nil { + return nil, err + } + + systemContract, found := k.GetSystemContract(ctx) + if !found { + return nil, types.ErrSystemContractNotFound + } + gatewayAddr := common.HexToAddress(systemContract.Gateway) + if gatewayAddr == (common.Address{}) { + return nil, types.ErrGatewayContractNotSet + } + + return k.CallEVM( + ctx, + *gatewayABI, + types.ModuleAddressEVM, + gatewayAddr, + BigIntZero, + nil, + true, + false, + "execute", + context, + zrc20, + amount, + target, + message, + ) +} diff --git a/zetaclient/chains/evm/cctx.go b/zetaclient/chains/evm/cctx.go index fce715cd8c..c5b5ab5c43 100644 --- a/zetaclient/chains/evm/cctx.go +++ b/zetaclient/chains/evm/cctx.go @@ -57,6 +57,10 @@ func ParseOutboundTypeFromCCTX(cctx types.CrossChainTx) OutboundTypes { return OutboundTypeERC20WithdrawAndCall } } + case coin.CoinType_NoAssetCall: + if cctx.CctxStatus.Status == types.CctxStatus_PendingOutbound { + return OutboundTypeCall + } } return OutboundTypeUnknown diff --git a/zetaclient/chains/evm/observer/inbound.go b/zetaclient/chains/evm/observer/inbound.go index b9cf02a993..f933abe565 100644 --- a/zetaclient/chains/evm/observer/inbound.go +++ b/zetaclient/chains/evm/observer/inbound.go @@ -220,11 +220,17 @@ func (ob *Observer) ObserveInbound(ctx context.Context, sampledLogger zerolog.Lo // query the gateway logs // TODO: refactor in a more declarative design. Example: storing the list of contract and events to listen in an array // https://github.com/zeta-chain/node/issues/2493 - lastScannedGateway, err := ob.ObserveGateway(ctx, startBlock, toBlock) + lastScannedGatewayDeposit, err := ob.ObserveGatewayDeposit(ctx, startBlock, toBlock) if err != nil { ob.Logger().Inbound.Error(). Err(err). - Msgf("ObserveInbound: error observing events from Gateway contract") + Msgf("ObserveInbound: error observing deposit events from Gateway contract") + } + lastScannedGatewayCall, err := ob.ObserveGatewayCall(ctx, startBlock, toBlock) + if err != nil { + ob.Logger().Inbound.Error(). + Err(err). + Msgf("ObserveInbound: error observing call events from Gateway contract") } // note: using lowest height for all 3 events is not perfect, but it's simple and good enough @@ -235,8 +241,11 @@ func (ob *Observer) ObserveInbound(ctx context.Context, sampledLogger zerolog.Lo if lastScannedTssRecvd < lastScannedLowest { lastScannedLowest = lastScannedTssRecvd } - if lastScannedGateway < lastScannedLowest { - lastScannedLowest = lastScannedGateway + if lastScannedGatewayDeposit < lastScannedLowest { + lastScannedLowest = lastScannedGatewayDeposit + } + if lastScannedGatewayCall < lastScannedLowest { + lastScannedLowest = lastScannedGatewayCall } // update last scanned block height for all 3 events (ZetaSent, Deposited, TssRecvd), ignore db error diff --git a/zetaclient/chains/evm/observer/v2_inbound.go b/zetaclient/chains/evm/observer/v2_inbound.go index 8f3a50efab..caf084e0b0 100644 --- a/zetaclient/chains/evm/observer/v2_inbound.go +++ b/zetaclient/chains/evm/observer/v2_inbound.go @@ -23,9 +23,40 @@ import ( "github.com/zeta-chain/zetacore/zetaclient/zetacore" ) -// ObserveGateway queries the gateway contract for deposit/call events +// checkEventProcessability checks if the event is processable +func (ob *Observer) checkEventProcessability( + sender, receiver ethcommon.Address, + txHash ethcommon.Hash, + payload []byte, +) bool { + // compliance check + if config.ContainRestrictedAddress(sender.Hex(), receiver.Hex()) { + compliance.PrintComplianceLog( + ob.Logger().Inbound, + ob.Logger().Compliance, + false, + ob.Chain().ChainId, + txHash.Hex(), + sender.Hex(), + receiver.Hex(), + "Deposit", + ) + return false + } + + // donation check + if bytes.Equal(payload, []byte(constant.DonationMessage)) { + ob.Logger().Inbound.Info(). + Msgf("thank you rich folk for your donation! tx %s chain %d", txHash.Hex(), ob.Chain().ChainId) + return false + } + + return true +} + +// ObserveGatewayDeposit queries the gateway contract for deposit events // returns the last block successfully scanned -func (ob *Observer) ObserveGateway(ctx context.Context, startBlock, toBlock uint64) (uint64, error) { +func (ob *Observer) ObserveGatewayDeposit(ctx context.Context, startBlock, toBlock uint64) (uint64, error) { // filter ERC20CustodyDeposited logs gatewayAddr, gatewayContract, err := ob.GetGatewayContract() if err != nil { @@ -64,7 +95,7 @@ func (ob *Observer) ObserveGateway(ctx context.Context, startBlock, toBlock uint } // check if the event is processable - if !ob.checkEventProcessability(event) { + if !ob.checkEventProcessability(event.Sender, event.Receiver, event.Raw.TxHash, event.Payload) { continue } @@ -86,7 +117,7 @@ func (ob *Observer) ObserveGateway(ctx context.Context, startBlock, toBlock uint return toBlock, nil } -// parseAndValidateEvents collects and sorts events by block number, tx index, and log index +// parseAndValidateDepositEvents collects and sorts events by block number, tx index, and log index func (ob *Observer) parseAndValidateDepositEvents( iterator *gatewayevm.GatewayEVMDepositIterator, gatewayAddr ethcommon.Address, @@ -132,33 +163,6 @@ func (ob *Observer) parseAndValidateDepositEvents( return filtered } -// checkEventProcessability checks if the event is processable -func (ob *Observer) checkEventProcessability(event *gatewayevm.GatewayEVMDeposit) bool { - // compliance check - if config.ContainRestrictedAddress(event.Sender.Hex(), event.Receiver.Hex()) { - compliance.PrintComplianceLog( - ob.Logger().Inbound, - ob.Logger().Compliance, - false, - ob.Chain().ChainId, - event.Raw.TxHash.Hex(), - event.Sender.Hex(), - event.Receiver.Hex(), - "Deposit", - ) - return false - } - - // donation check - if bytes.Equal(event.Payload, []byte(constant.DonationMessage)) { - ob.Logger().Inbound.Info(). - Msgf("thank you rich folk for your donation! tx %s chain %d", event.Raw.TxHash.Hex(), ob.Chain().ChainId) - return false - } - - return true -} - // newDepositInboundVote creates a MsgVoteInbound message for a Deposit event func (ob *Observer) newDepositInboundVote(event *gatewayevm.GatewayEVMDeposit) types.MsgVoteInbound { // if event.Asset is zero, it's a native token @@ -185,3 +189,136 @@ func (ob *Observer) newDepositInboundVote(event *gatewayevm.GatewayEVMDeposit) t types.ProtocolContractVersion_V2, ) } + +// ObserveGatewayCall queries the gateway contract for call events +// returns the last block successfully scanned +// TODO: there are lot of similarities between this function and ObserveGatewayDeposit +// logic should be factorized using interfaces and generics +// https://github.com/zeta-chain/node/issues/2493 +func (ob *Observer) ObserveGatewayCall(ctx context.Context, startBlock, toBlock uint64) (uint64, error) { + // filter ERC20CustodyDeposited logs + gatewayAddr, gatewayContract, err := ob.GetGatewayContract() + if err != nil { + // lastScanned is startBlock - 1 + return startBlock - 1, errors.Wrap(err, "can't get gateway contract") + } + + // get iterator for the events for the block range + eventIterator, err := gatewayContract.FilterCall(&bind.FilterOpts{ + Start: startBlock, + End: &toBlock, + Context: ctx, + }, []ethcommon.Address{}, []ethcommon.Address{}) + if err != nil { + return startBlock - 1, errors.Wrapf( + err, + "error filtering calls from block %d to %d for chain %d", + startBlock, + toBlock, + ob.Chain().ChainId, + ) + } + + // parse and validate events + events := ob.parseAndValidateCallEvents(eventIterator, gatewayAddr) + + // increment prom counter + metrics.GetFilterLogsPerChain.WithLabelValues(ob.Chain().Name).Inc() + + // post to zetacore + lastScanned := uint64(0) + for _, event := range events { + // remember which block we are scanning (there could be multiple events in the same block) + if event.Raw.BlockNumber > lastScanned { + lastScanned = event.Raw.BlockNumber + } + + // check if the event is processable + if !ob.checkEventProcessability(event.Sender, event.Receiver, event.Raw.TxHash, event.Payload) { + continue + } + + msg := ob.newCallInboundVote(event) + + ob.Logger().Inbound.Info(). + Msgf("ObserveGateway: Call inbound detected on chain %d tx %s block %d from %s value message %s", + ob.Chain(). + ChainId, event.Raw.TxHash.Hex(), event.Raw.BlockNumber, event.Sender.Hex(), hex.EncodeToString(event.Payload)) + + _, err = ob.PostVoteInbound(ctx, &msg, zetacore.PostVoteInboundExecutionGasLimit) + if err != nil { + // decrement the last scanned block so we have to re-scan from this block next time + return lastScanned - 1, errors.Wrap(err, "error posting vote inbound") + } + } + + // successfully processed all events in [startBlock, toBlock] + return toBlock, nil +} + +// parseAndValidateCallEvents collects and sorts events by block number, tx index, and log index +func (ob *Observer) parseAndValidateCallEvents( + iterator *gatewayevm.GatewayEVMCallIterator, + gatewayAddr ethcommon.Address, +) []*gatewayevm.GatewayEVMCall { + // collect and sort events by block number, then tx index, then log index (ascending) + events := make([]*gatewayevm.GatewayEVMCall, 0) + for iterator.Next() { + events = append(events, iterator.Event) + err := evm.ValidateEvmTxLog(&iterator.Event.Raw, gatewayAddr, "", evm.TopicsGatewayCall) + if err == nil { + events = append(events, iterator.Event) + continue + } + ob.Logger().Inbound.Warn(). + Err(err). + Msgf("ObserveGateway: invalid Call event in tx %s on chain %d at height %d", + iterator.Event.Raw.TxHash.Hex(), ob.Chain().ChainId, iterator.Event.Raw.BlockNumber) + } + sort.SliceStable(events, func(i, j int) bool { + if events[i].Raw.BlockNumber == events[j].Raw.BlockNumber { + if events[i].Raw.TxIndex == events[j].Raw.TxIndex { + return events[i].Raw.Index < events[j].Raw.Index + } + return events[i].Raw.TxIndex < events[j].Raw.TxIndex + } + return events[i].Raw.BlockNumber < events[j].Raw.BlockNumber + }) + + // filter events from same tx + filtered := make([]*gatewayevm.GatewayEVMCall, 0) + guard := make(map[string]bool) + for _, event := range events { + // guard against multiple events in the same tx + if guard[event.Raw.TxHash.Hex()] { + ob.Logger().Inbound.Warn(). + Msgf("ObserveGateway: multiple remote call events detected in same tx %s", event.Raw.TxHash) + continue + } + guard[event.Raw.TxHash.Hex()] = true + filtered = append(filtered, event) + } + + return filtered +} + +// newCallInboundVote creates a MsgVoteInbound message for a Call event +func (ob *Observer) newCallInboundVote(event *gatewayevm.GatewayEVMCall) types.MsgVoteInbound { + return *types.NewMsgVoteInbound( + ob.ZetacoreClient().GetKeys().GetOperatorAddress().String(), + event.Sender.Hex(), + ob.Chain().ChainId, + "", + event.Receiver.Hex(), + ob.ZetacoreClient().Chain().ChainId, + sdkmath.ZeroUint(), + hex.EncodeToString(event.Payload), + event.Raw.TxHash.Hex(), + event.Raw.BlockNumber, + 1_500_000, + coin.CoinType_NoAssetCall, + "", + event.Raw.Index, + types.ProtocolContractVersion_V2, + ) +} diff --git a/zetaclient/chains/evm/observer/v2_outbound.go b/zetaclient/chains/evm/observer/v2_outbound.go index 57722780da..f452f52c4b 100644 --- a/zetaclient/chains/evm/observer/v2_outbound.go +++ b/zetaclient/chains/evm/observer/v2_outbound.go @@ -40,10 +40,13 @@ func ParseOutboundEventV2( return transaction.Value(), chains.ReceiveStatus_success, nil case evm.OutboundTypeERC20Withdraw: return ParseAndCheckERC20CustodyWithdraw(cctx, receipt, custodyAddr, custody) - case evm.OutboundTypeGasWithdrawAndCall: - return ParseAndCheckGatewayExecuted(cctx, receipt, gatewayAddr, gateway) case evm.OutboundTypeERC20WithdrawAndCall: return ParseAndCheckERC20CustodyWithdrawAndCall(cctx, receipt, custodyAddr, custody) + case evm.OutboundTypeGasWithdrawAndCall: + case evm.OutboundTypeCall: + // both gas withdraw and call and no-asset call uses gateway execute + // no-asset call simply hash msg.value == 0 + return ParseAndCheckGatewayExecuted(cctx, receipt, gatewayAddr, gateway) } return big.NewInt(0), chains.ReceiveStatus_failed, fmt.Errorf("unsupported outbound type %d", outboundType) } diff --git a/zetaclient/chains/evm/signer/sign.go b/zetaclient/chains/evm/signer/sign.go index e643c4d685..2943092677 100644 --- a/zetaclient/chains/evm/signer/sign.go +++ b/zetaclient/chains/evm/signer/sign.go @@ -9,8 +9,6 @@ import ( "github.com/pkg/errors" "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/erc20custody.sol" connectorevm "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/zetaconnector.base.sol" - erc20custodyv2 "github.com/zeta-chain/protocol-contracts/v2/pkg/erc20custody.sol" - "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayevm.sol" "github.com/zeta-chain/zetacore/zetaclient/chains/evm" ) @@ -236,118 +234,3 @@ func (signer *Signer) SignMigrateTssFundsCmd(ctx context.Context, txData *Outbou } return tx, nil } - -// SignGatewayExecute signs a gateway execute -// used for gas withdrawal and call transaction -// function execute -// address destination, -// bytes calldata data -func (signer *Signer) SignGatewayExecute(ctx context.Context, txData *OutboundData) (*ethtypes.Transaction, error) { - var data []byte - var err error - - gatewayABI, err := gatewayevm.GatewayEVMMetaData.GetAbi() - if err != nil { - return nil, errors.Wrap(err, "unable to get GatewayEVMMetaData ABI") - } - - data, err = gatewayABI.Pack("execute", txData.to, txData.message) - if err != nil { - return nil, fmt.Errorf("execute pack error: %w", err) - } - - tx, _, _, err := signer.Sign( - ctx, - data, - signer.gatewayAddress, - txData.amount, - txData.gasLimit, - txData.gasPrice, - txData.nonce, - txData.height, - ) - if err != nil { - return nil, fmt.Errorf("sign execute error: %w", err) - } - - return tx, nil -} - -// SignERC20CustodyWithdraw signs a erc20 withdrawal transaction -// function withdrawAndCall -// address token, -// address to, -// uint256 amount, -func (signer *Signer) SignERC20CustodyWithdraw( - ctx context.Context, - txData *OutboundData, -) (*ethtypes.Transaction, error) { - var data []byte - var err error - - erc20CustodyV2ABI, err := erc20custodyv2.ERC20CustodyMetaData.GetAbi() - if err != nil { - return nil, errors.Wrap(err, "unable to get ERC20CustodyMetaData ABI") - } - - data, err = erc20CustodyV2ABI.Pack("withdraw", txData.asset, txData.to, txData.amount) - if err != nil { - return nil, fmt.Errorf("withdraw pack error: %w", err) - } - - tx, _, _, err := signer.Sign( - ctx, - data, - signer.er20CustodyAddress, - zeroValue, - txData.gasLimit, - txData.gasPrice, - txData.nonce, - txData.height, - ) - if err != nil { - return nil, fmt.Errorf("sign withdraw error: %w", err) - } - - return tx, nil -} - -// SignERC20CustodyWithdrawAndCall signs a erc20 withdrawal and call transaction -// function withdrawAndCall -// address token, -// address to, -// uint256 amount, -// bytes calldata data -func (signer *Signer) SignERC20CustodyWithdrawAndCall( - ctx context.Context, - txData *OutboundData, -) (*ethtypes.Transaction, error) { - var data []byte - var err error - - erc20CustodyV2ABI, err := erc20custodyv2.ERC20CustodyMetaData.GetAbi() - if err != nil { - return nil, errors.Wrap(err, "unable to get ERC20CustodyMetaData ABI") - } - - data, err = erc20CustodyV2ABI.Pack("withdrawAndCall", txData.asset, txData.to, txData.amount, txData.message) - if err != nil { - return nil, fmt.Errorf("withdraw pack error: %w", err) - } - - tx, _, _, err := signer.Sign( - ctx, - data, - signer.er20CustodyAddress, - zeroValue, - txData.gasLimit, - txData.gasPrice, - txData.nonce, - txData.height, - ) - if err != nil { - return nil, fmt.Errorf("sign withdrawAndCall error: %w", err) - } - - return tx, nil -} diff --git a/zetaclient/chains/evm/signer/signer.go b/zetaclient/chains/evm/signer/signer.go index dfb685eedf..37b4773907 100644 --- a/zetaclient/chains/evm/signer/signer.go +++ b/zetaclient/chains/evm/signer/signer.go @@ -23,7 +23,6 @@ import ( "github.com/zeta-chain/zetacore/pkg/coin" "github.com/zeta-chain/zetacore/pkg/constant" crosschainkeeper "github.com/zeta-chain/zetacore/x/crosschain/keeper" - "github.com/zeta-chain/zetacore/x/crosschain/types" crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" "github.com/zeta-chain/zetacore/zetaclient/chains/base" "github.com/zeta-chain/zetacore/zetaclient/chains/evm" @@ -197,7 +196,7 @@ func (signer *Signer) Broadcast(tx *ethtypes.Transaction) error { // TODO(revamp): simplify function func (signer *Signer) TryProcessOutbound( ctx context.Context, - cctx *types.CrossChainTx, + cctx *crosschaintypes.CrossChainTx, outboundProc *outboundprocessor.Processor, outboundID string, chainObserver interfaces.ChainObserver, @@ -289,7 +288,7 @@ func (signer *Signer) TryProcessOutbound( func (signer *Signer) SignOutboundFromCCTX( ctx context.Context, logger zerolog.Logger, - cctx *types.CrossChainTx, + cctx *crosschaintypes.CrossChainTx, outboundData *OutboundData, zetacoreClient interfaces.ZetacoreClient, toChain zctx.Chain, @@ -326,7 +325,6 @@ func (signer *Signer) SignOutboundFromCCTX( params := msg[1] return signer.SignCommandTx(ctx, outboundData, cmd, params) } else if cctx.ProtocolContractVersion == crosschaintypes.ProtocolContractVersion_V2 { - // call sign outbound from cctx for v2 protocol contracts return signer.SignOutboundFromCCTXV2(ctx, cctx, outboundData) } else if IsSenderZetaChain(cctx, zetacoreClient) { @@ -359,7 +357,7 @@ func (signer *Signer) SignOutboundFromCCTX( ) return signer.SignConnectorOnReceive(ctx, outboundData) } - } else if cctx.CctxStatus.Status == types.CctxStatus_PendingRevert && cctx.OutboundParams[0].ReceiverChainId == zetacoreClient.Chain().ChainId { + } else if cctx.CctxStatus.Status == crosschaintypes.CctxStatus_PendingRevert && cctx.OutboundParams[0].ReceiverChainId == zetacoreClient.Chain().ChainId { switch cctx.InboundParams.CoinType { case coin.CoinType_Zeta: logger.Info().Msgf( @@ -389,7 +387,7 @@ func (signer *Signer) SignOutboundFromCCTX( ) return signer.SignERC20Withdraw(ctx, outboundData) } - } else if cctx.CctxStatus.Status == types.CctxStatus_PendingRevert { + } else if cctx.CctxStatus.Status == crosschaintypes.CctxStatus_PendingRevert { logger.Info().Msgf( "SignConnectorOnRevert: %d => %d, nonce %d, gasPrice %d", cctx.InboundParams.SenderChainId, @@ -400,7 +398,7 @@ func (signer *Signer) SignOutboundFromCCTX( outboundData.srcChainID = big.NewInt(cctx.OutboundParams[0].ReceiverChainId) outboundData.toChainID = big.NewInt(cctx.GetCurrentOutboundParam().ReceiverChainId) return signer.SignConnectorOnRevert(ctx, outboundData) - } else if cctx.CctxStatus.Status == types.CctxStatus_PendingOutbound { + } else if cctx.CctxStatus.Status == crosschaintypes.CctxStatus_PendingOutbound { logger.Info().Msgf( "SignConnectorOnReceive: %d => %d, nonce %d, gasPrice %d", cctx.InboundParams.SenderChainId, @@ -437,7 +435,7 @@ func (signer *Signer) SignCommandTx( func (signer *Signer) BroadcastOutbound( ctx context.Context, tx *ethtypes.Transaction, - cctx *types.CrossChainTx, + cctx *crosschaintypes.CrossChainTx, logger zerolog.Logger, myID sdk.AccAddress, zetacoreClient interfaces.ZetacoreClient, @@ -512,15 +510,15 @@ func (signer *Signer) EvmSigner() ethtypes.Signer { // IsSenderZetaChain checks if the sender chain is ZetaChain // TODO(revamp): move to another package more general for cctx functions func IsSenderZetaChain( - cctx *types.CrossChainTx, + cctx *crosschaintypes.CrossChainTx, zetacoreClient interfaces.ZetacoreClient, ) bool { return cctx.InboundParams.SenderChainId == zetacoreClient.Chain().ChainId && - cctx.CctxStatus.Status == types.CctxStatus_PendingOutbound + cctx.CctxStatus.Status == crosschaintypes.CctxStatus_PendingOutbound } // ErrorMsg returns a error message for SignConnectorOnReceive failure with cctx data -func ErrorMsg(cctx *types.CrossChainTx) string { +func ErrorMsg(cctx *crosschaintypes.CrossChainTx) string { return fmt.Sprintf( "signer SignConnectorOnReceive error: nonce %d chain %d", cctx.GetCurrentOutboundParam().TssNonce, diff --git a/zetaclient/chains/evm/signer/v2_sign.go b/zetaclient/chains/evm/signer/v2_sign.go new file mode 100644 index 0000000000..53ab65ecf6 --- /dev/null +++ b/zetaclient/chains/evm/signer/v2_sign.go @@ -0,0 +1,126 @@ +package signer + +import ( + "context" + "fmt" + + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/pkg/errors" + erc20custodyv2 "github.com/zeta-chain/protocol-contracts/v2/pkg/erc20custody.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayevm.sol" +) + +// SignGatewayExecute signs a gateway execute +// used for gas withdrawal and call transaction +// function execute +// address destination, +// bytes calldata data +func (signer *Signer) SignGatewayExecute(ctx context.Context, txData *OutboundData) (*ethtypes.Transaction, error) { + var data []byte + var err error + + gatewayABI, err := gatewayevm.GatewayEVMMetaData.GetAbi() + if err != nil { + return nil, errors.Wrap(err, "unable to get GatewayEVMMetaData ABI") + } + + data, err = gatewayABI.Pack("execute", txData.to, txData.message) + if err != nil { + return nil, fmt.Errorf("execute pack error: %w", err) + } + + tx, _, _, err := signer.Sign( + ctx, + data, + signer.gatewayAddress, + txData.amount, + txData.gasLimit, + txData.gasPrice, + txData.nonce, + txData.height, + ) + if err != nil { + return nil, fmt.Errorf("sign execute error: %w", err) + } + + return tx, nil +} + +// SignERC20CustodyWithdraw signs a erc20 withdrawal transaction +// function withdrawAndCall +// address token, +// address to, +// uint256 amount, +func (signer *Signer) SignERC20CustodyWithdraw( + ctx context.Context, + txData *OutboundData, +) (*ethtypes.Transaction, error) { + var data []byte + var err error + + erc20CustodyV2ABI, err := erc20custodyv2.ERC20CustodyMetaData.GetAbi() + if err != nil { + return nil, errors.Wrap(err, "unable to get ERC20CustodyMetaData ABI") + } + + data, err = erc20CustodyV2ABI.Pack("withdraw", txData.asset, txData.to, txData.amount) + if err != nil { + return nil, fmt.Errorf("withdraw pack error: %w", err) + } + + tx, _, _, err := signer.Sign( + ctx, + data, + signer.er20CustodyAddress, + zeroValue, + txData.gasLimit, + txData.gasPrice, + txData.nonce, + txData.height, + ) + if err != nil { + return nil, fmt.Errorf("sign withdraw error: %w", err) + } + + return tx, nil +} + +// SignERC20CustodyWithdrawAndCall signs a erc20 withdrawal and call transaction +// function withdrawAndCall +// address token, +// address to, +// uint256 amount, +// bytes calldata data +func (signer *Signer) SignERC20CustodyWithdrawAndCall( + ctx context.Context, + txData *OutboundData, +) (*ethtypes.Transaction, error) { + var data []byte + var err error + + erc20CustodyV2ABI, err := erc20custodyv2.ERC20CustodyMetaData.GetAbi() + if err != nil { + return nil, errors.Wrap(err, "unable to get ERC20CustodyMetaData ABI") + } + + data, err = erc20CustodyV2ABI.Pack("withdrawAndCall", txData.asset, txData.to, txData.amount, txData.message) + if err != nil { + return nil, fmt.Errorf("withdraw pack error: %w", err) + } + + tx, _, _, err := signer.Sign( + ctx, + data, + signer.er20CustodyAddress, + zeroValue, + txData.gasLimit, + txData.gasPrice, + txData.nonce, + txData.height, + ) + if err != nil { + return nil, fmt.Errorf("sign withdrawAndCall error: %w", err) + } + + return tx, nil +} diff --git a/zetaclient/chains/evm/signer/v2_signer.go b/zetaclient/chains/evm/signer/v2_signer.go index d25b2e30a1..033b72db73 100644 --- a/zetaclient/chains/evm/signer/v2_signer.go +++ b/zetaclient/chains/evm/signer/v2_signer.go @@ -22,11 +22,13 @@ func (signer *Signer) SignOutboundFromCCTXV2( return signer.SignGasWithdraw(ctx, outboundData) case evm.OutboundTypeERC20Withdraw: return signer.SignERC20CustodyWithdraw(ctx, outboundData) - case evm.OutboundTypeGasWithdrawAndCall: - return signer.SignGatewayExecute(ctx, outboundData) case evm.OutboundTypeERC20WithdrawAndCall: return signer.SignERC20CustodyWithdrawAndCall(ctx, outboundData) - default: - return nil, fmt.Errorf("unsupported outbound type %d", outboundType) + case evm.OutboundTypeGasWithdrawAndCall: + case evm.OutboundTypeCall: + // both gas withdraw and call and no-asset call uses gateway execute + // no-asset call simply hash msg.value == 0 + return signer.SignGatewayExecute(ctx, outboundData) } + return nil, fmt.Errorf("unsupported outbound type %d", outboundType) }