diff --git a/Makefile b/Makefile index 51931ea89b..cdf25024e0 100644 --- a/Makefile +++ b/Makefile @@ -273,7 +273,7 @@ start-solana-test: zetanode solana start-v2-test: zetanode @echo "--> Starting e2e smart contracts v2 test" - export E2E_ARGS="--skip-regular --test-v2" && \ + export E2E_ARGS="--skip-regular --test-v2 --verbose" && \ cd contrib/localnet/ && $(DOCKER_COMPOSE) -f docker-compose.yml up -d ############################################################################### diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index c7efcc116b..cff0972e99 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -340,11 +340,11 @@ func localE2ETest(cmd *cobra.Command, _ []string) { if testV2 { eg.Go(v2TestRoutine(conf, deployerRunner, verbose, e2etests.TestV2ETHDepositName, - e2etests.TestV2ETHDepositAndCallName, - //e2etests.TestV2ETHWithdrawName, + //e2etests.TestV2ETHDepositAndCallName, + e2etests.TestV2ETHWithdrawName, //e2etests.TestV2ETHWithdrawAndCallName, - e2etests.TestV2ERC20DepositName, - e2etests.TestV2ERC20DepositAndCallName, + //e2etests.TestV2ERC20DepositName, + //e2etests.TestV2ERC20DepositAndCallName, //e2etests.TestV2ERC20WithdrawName, //e2etests.TestV2ERC20WithdrawAndCallName, //e2etests.TestV2ZEVMToEVMCallName, diff --git a/e2e/e2etests/test_v2_erc20_deposit.go b/e2e/e2etests/test_v2_erc20_deposit.go index 43fe914ba7..e39a447ac6 100644 --- a/e2e/e2etests/test_v2_erc20_deposit.go +++ b/e2e/e2etests/test_v2_erc20_deposit.go @@ -16,7 +16,7 @@ func TestV2ERC20Deposit(r *runner.E2ERunner, args []string) { amount, ok := big.NewInt(0).SetString(args[0], 10) require.True(r, ok, "Invalid amount specified for TestV2ERC20Deposit") - r.ApproveERC20(r.GatewayEVMAddr) + r.ApproveERC20OnEVM(r.GatewayEVMAddr) // perform the deposit tx := r.V2ERC20Deposit(r.EVMAddress(), amount) diff --git a/e2e/e2etests/test_v2_erc20_deposit_and_call.go b/e2e/e2etests/test_v2_erc20_deposit_and_call.go index 3ac823c38e..ce0d342d8a 100644 --- a/e2e/e2etests/test_v2_erc20_deposit_and_call.go +++ b/e2e/e2etests/test_v2_erc20_deposit_and_call.go @@ -18,7 +18,7 @@ func TestV2ERC20DepositAndCall(r *runner.E2ERunner, args []string) { amount, ok := big.NewInt(0).SetString(args[0], 10) require.True(r, ok, "Invalid amount specified for TestV2ERC20DepositAndCall") - r.ApproveERC20(r.GatewayEVMAddr) + r.ApproveERC20OnEVM(r.GatewayEVMAddr) r.AssertTestDAppValues(false, payloadMessageERC20, amount) diff --git a/e2e/e2etests/test_v2_eth_withdraw.go b/e2e/e2etests/test_v2_eth_withdraw.go index 257d13df7c..963944125e 100644 --- a/e2e/e2etests/test_v2_eth_withdraw.go +++ b/e2e/e2etests/test_v2_eth_withdraw.go @@ -1,6 +1,7 @@ package e2etests import ( + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" "math/big" "github.com/stretchr/testify/require" @@ -15,10 +16,13 @@ func TestV2ETHWithdraw(r *runner.E2ERunner, args []string) { amount, ok := big.NewInt(0).SetString(args[0], 10) require.True(r, ok, "Invalid amount specified for TestV2ETHWithdraw") + r.ApproveETHZRC20(r.GatewayZEVMAddr) + // perform the withdraw tx := r.V2ETHWithdraw(r.EVMAddress(), amount) // 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, "withdraw") + require.Equal(r, crosschaintypes.CctxStatus_OutboundMined, cctx.CctxStatus.Status) } diff --git a/e2e/e2etests/test_v2_eth_withdraw_and_call.go b/e2e/e2etests/test_v2_eth_withdraw_and_call.go index a68f8b8e56..4f70815206 100644 --- a/e2e/e2etests/test_v2_eth_withdraw_and_call.go +++ b/e2e/e2etests/test_v2_eth_withdraw_and_call.go @@ -1,6 +1,7 @@ package e2etests import ( + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" "math/big" "github.com/stretchr/testify/require" @@ -24,4 +25,5 @@ func TestV2ETHWithdrawAndCall(r *runner.E2ERunner, args []string) { // 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, "withdraw") + require.Equal(r, crosschaintypes.CctxStatus_OutboundMined, cctx.CctxStatus.Status) } diff --git a/e2e/e2etests/test_v2_evm_to_zevm_call.go b/e2e/e2etests/test_v2_evm_to_zevm_call.go index 52933ea8f6..b5324e1cd7 100644 --- a/e2e/e2etests/test_v2_evm_to_zevm_call.go +++ b/e2e/e2etests/test_v2_evm_to_zevm_call.go @@ -2,6 +2,7 @@ package e2etests import ( "github.com/stretchr/testify/require" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" "github.com/zeta-chain/zetacore/e2e/runner" "github.com/zeta-chain/zetacore/e2e/utils" @@ -19,4 +20,5 @@ func TestV2EVMToZEVMCall(r *runner.E2ERunner, args []string) { // 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) } diff --git a/e2e/e2etests/test_v2_zevm_to_evm_call.go b/e2e/e2etests/test_v2_zevm_to_evm_call.go index 7b552d321f..4f829270d5 100644 --- a/e2e/e2etests/test_v2_zevm_to_evm_call.go +++ b/e2e/e2etests/test_v2_zevm_to_evm_call.go @@ -2,6 +2,7 @@ package e2etests import ( "github.com/stretchr/testify/require" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" "github.com/zeta-chain/zetacore/e2e/runner" "github.com/zeta-chain/zetacore/e2e/utils" @@ -19,4 +20,5 @@ func TestV2ZEVMToEVMCall(r *runner.E2ERunner, args []string) { // 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) } diff --git a/e2e/runner/evm.go b/e2e/runner/evm.go index 9feb35a378..5e82722329 100644 --- a/e2e/runner/evm.go +++ b/e2e/runner/evm.go @@ -165,10 +165,10 @@ func (r *E2ERunner) SendEther(_ ethcommon.Address, value *big.Int, data []byte) return signedTx, nil } -// ApproveERC20 approves ERC20 on EVM to a specific address +// ApproveERC20OnEVM approves ERC20 on EVM to a specific address // check if allowance is zero before calling this method // allow a high amount to avoid multiple approvals -func (r *E2ERunner) ApproveERC20(allowed ethcommon.Address) { +func (r *E2ERunner) ApproveERC20OnEVM(allowed ethcommon.Address) { allowance, err := r.ERC20.Allowance(&bind.CallOpts{}, r.Account.EVMAddress(), r.GatewayEVMAddr) require.NoError(r, err) @@ -181,6 +181,22 @@ func (r *E2ERunner) ApproveERC20(allowed ethcommon.Address) { } } +// ApproveETHZRC20 approves ETH ZRC20 on EVM to a specific address +// check if allowance is zero before calling this method +// allow a high amount to avoid multiple approvals +func (r *E2ERunner) ApproveETHZRC20(allowed ethcommon.Address) { + allowance, err := r.ETHZRC20.Allowance(&bind.CallOpts{}, r.Account.EVMAddress(), r.GatewayEVMAddr) + require.NoError(r, err) + + // approve 1M*1e18 if allowance is zero + if allowance.Cmp(big.NewInt(0)) == 0 { + tx, err := r.ETHZRC20.Approve(r.ZEVMAuth, allowed, big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(1000000))) + require.NoError(r, err) + receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + require.True(r, receipt.Status == 1, "approval failed") + } +} + // AnvilMineBlocks mines blocks on Anvil localnet // the block time is provided in seconds // the method returns a function to stop the mining diff --git a/e2e/runner/v2_setup_zeta.go b/e2e/runner/v2_setup_zeta.go index 1af1d42730..86893e29cc 100644 --- a/e2e/runner/v2_setup_zeta.go +++ b/e2e/runner/v2_setup_zeta.go @@ -39,6 +39,7 @@ func (r *E2ERunner) SetZEVMContractsV2() { require.NoError(r, err) // Deploy the proxy contract + r.Logger.Info("Deploying proxy with %s and %s, address: %s", r.WZetaAddr.Hex(), r.Account.EVMAddress().Hex(), gatewayZEVMAddr.Hex()) proxyAddress, txProxy, _, err := erc1967proxy.DeployERC1967Proxy( r.ZEVMAuth, r.ZEVMClient, diff --git a/x/crosschain/keeper/v2_zevm_inbound.go b/x/crosschain/keeper/v2_zevm_inbound.go index 4d04751680..8c613e7b6b 100644 --- a/x/crosschain/keeper/v2_zevm_inbound.go +++ b/x/crosschain/keeper/v2_zevm_inbound.go @@ -1,6 +1,7 @@ package keeper import ( + "encoding/hex" "fmt" errorsmod "cosmossdk.io/errors" @@ -133,7 +134,7 @@ func (k Keeper) newWithdrawalInbound( toAddr, foreignCoin.ForeignChainId, math.NewUintFromBigInt(event.Value), - "", + hex.EncodeToString(event.Message), event.Raw.TxHash.String(), event.Raw.BlockNumber, gasLimit.Uint64(), diff --git a/zetaclient/chains/bitcoin/signer/signer.go b/zetaclient/chains/bitcoin/signer/signer.go index 7e701e523d..e9621a13a7 100644 --- a/zetaclient/chains/bitcoin/signer/signer.go +++ b/zetaclient/chains/bitcoin/signer/signer.go @@ -205,7 +205,7 @@ func (signer *Signer) SignWithdrawTx( signer.Logger(). Std.Error(). Err(err). - Msgf("SignWithdrawTx: FetchUTXOs error: nonce %d chain %d", nonce, chain.ChainId) + Msgf("SignGasWithdraw: FetchUTXOs error: nonce %d chain %d", nonce, chain.ChainId) } // select N UTXOs to cover the total expense @@ -416,7 +416,7 @@ func (signer *Signer) TryProcessOutbound( true, chain.ChainId, cctx.Index, cctx.InboundParams.Sender, params.Receiver, "BTC") amount = 0.0 // zero out the amount to cancel the tx } - logger.Info().Msgf("SignWithdrawTx: to %s, value %d sats", to.EncodeAddress(), params.Amount.Uint64()) + logger.Info().Msgf("SignGasWithdraw: to %s, value %d sats", to.EncodeAddress(), params.Amount.Uint64()) // sign withdraw tx tx, err := signer.SignWithdrawTx( @@ -432,7 +432,7 @@ func (signer *Signer) TryProcessOutbound( cancelTx, ) if err != nil { - logger.Warn().Err(err).Msgf("SignOutbound error: nonce %d chain %d", outboundTssNonce, params.ReceiverChainId) + logger.Warn().Err(err).Msgf("SignConnectorOnReceive error: nonce %d chain %d", outboundTssNonce, params.ReceiverChainId) return } logger.Info(). diff --git a/zetaclient/chains/evm/cctx.go b/zetaclient/chains/evm/cctx.go new file mode 100644 index 0000000000..fce715cd8c --- /dev/null +++ b/zetaclient/chains/evm/cctx.go @@ -0,0 +1,63 @@ +package evm + +import ( + "github.com/zeta-chain/zetacore/pkg/coin" + "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +// OutboundTypes enumerate the different types of outbound transactions +// NOTE: only used for v2 protocol contracts and currently excludes ZETA withdraws +type OutboundTypes int + +const ( + // OutboundTypeUnknown is an unknown outbound transaction + OutboundTypeUnknown OutboundTypes = iota + + // OutboundTypeGasWithdraw is a gas withdraw transaction + OutboundTypeGasWithdraw + + // OutboundTypeERC20Withdraw is an ERC20 withdraw transaction + OutboundTypeERC20Withdraw + + // OutboundTypeGasWithdrawAndCall is a gas withdraw and call transaction + OutboundTypeGasWithdrawAndCall + + // OutboundTypeERC20WithdrawAndCall is an ERC20 withdraw and call transaction + OutboundTypeERC20WithdrawAndCall + + // OutboundTypeCall is a no-asset call transaction + OutboundTypeCall + + // OutboundTypeGasWithdrawAndRevert is a gas withdraw and revert call + OutboundTypeGasWithdrawAndRevert + + // OutboundTypeERC20WithdrawAndRevert is an ERC20 withdraw and revert call + OutboundTypeERC20WithdrawAndRevert +) + +// ParseOutboundTypeFromCCTX returns the outbound type from the CCTX +// TODO: address revert +func ParseOutboundTypeFromCCTX(cctx types.CrossChainTx) OutboundTypes { + switch cctx.InboundParams.CoinType { + case coin.CoinType_Gas: + switch cctx.CctxStatus.Status { + case types.CctxStatus_PendingOutbound: + if len(cctx.RelayedMessage) == 0 { + return OutboundTypeGasWithdraw + } else { + return OutboundTypeGasWithdrawAndCall + } + } + case coin.CoinType_ERC20: + switch cctx.CctxStatus.Status { + case types.CctxStatus_PendingOutbound: + if len(cctx.RelayedMessage) == 0 { + return OutboundTypeERC20Withdraw + } else { + return OutboundTypeERC20WithdrawAndCall + } + } + } + + return OutboundTypeUnknown +} diff --git a/zetaclient/chains/evm/observer/outbound.go b/zetaclient/chains/evm/observer/outbound.go index 8bedaf965a..65aa04438c 100644 --- a/zetaclient/chains/evm/observer/outbound.go +++ b/zetaclient/chains/evm/observer/outbound.go @@ -222,6 +222,63 @@ func (ob *Observer) VoteOutboundIfConfirmed( return false, nil } +// ParseOutboundReceivedValue parses the received value and status from the outbound receipt +// The receivd value is the amount of Zeta/ERC20/Gas token (released from connector/custody/TSS) sent to the receiver +func ParseOutboundReceivedValue( + cctx *crosschaintypes.CrossChainTx, + receipt *ethtypes.Receipt, + transaction *ethtypes.Transaction, + cointype coin.CoinType, + connectorAddress ethcommon.Address, + connector *zetaconnector.ZetaConnectorNonEth, + custodyAddress ethcommon.Address, + custody *erc20custody.ERC20Custody, +) (*big.Int, chains.ReceiveStatus, error) { + // determine the receive status and value + // https://docs.nethereum.com/en/latest/nethereum-receipt-status/ + receiveValue := big.NewInt(0) + receiveStatus := chains.ReceiveStatus_failed + if receipt.Status == ethtypes.ReceiptStatusSuccessful { + receiveValue = transaction.Value() + receiveStatus = chains.ReceiveStatus_success + } + + if cctx.ProtocolContractVersion == crosschaintypes.ProtocolContractVersion_V2 { + return ParseOutboundEventV2(cctx, receipt, transaction) + } + + // parse receive value from the outbound receipt for Zeta and ERC20 + switch cointype { + case coin.CoinType_Zeta: + if receipt.Status == ethtypes.ReceiptStatusSuccessful { + receivedLog, revertedLog, err := ParseAndCheckZetaEvent(cctx, receipt, connectorAddress, connector) + if err != nil { + return nil, chains.ReceiveStatus_failed, err + } + // use the value in ZetaReceived/ZetaReverted event for vote message + if receivedLog != nil { + receiveValue = receivedLog.ZetaValue + } else if revertedLog != nil { + receiveValue = revertedLog.RemainingZetaValue + } + } + case coin.CoinType_ERC20: + if receipt.Status == ethtypes.ReceiptStatusSuccessful { + withdrawn, err := ParseAndCheckWithdrawnEvent(cctx, receipt, custodyAddress, custody) + if err != nil { + return nil, chains.ReceiveStatus_failed, err + } + // use the value in Withdrawn event for vote message + receiveValue = withdrawn.Amount + } + case coin.CoinType_Gas, coin.CoinType_Cmd: + // nothing to do for CoinType_Gas/CoinType_Cmd, no need to parse event + default: + return nil, chains.ReceiveStatus_failed, fmt.Errorf("unknown coin type %s", cointype) + } + return receiveValue, receiveStatus, nil +} + // ParseAndCheckZetaEvent parses and checks ZetaReceived/ZetaReverted event from the outbound receipt // It either returns an ZetaReceived or an ZetaReverted event, or an error if no event found func ParseAndCheckZetaEvent( @@ -314,59 +371,6 @@ func ParseAndCheckWithdrawnEvent( return nil, errors.New("no ERC20 Withdrawn event found") } -// ParseOutboundReceivedValue parses the received value and status from the outbound receipt -// The receivd value is the amount of Zeta/ERC20/Gas token (released from connector/custody/TSS) sent to the receiver -func ParseOutboundReceivedValue( - cctx *crosschaintypes.CrossChainTx, - receipt *ethtypes.Receipt, - transaction *ethtypes.Transaction, - cointype coin.CoinType, - connectorAddress ethcommon.Address, - connector *zetaconnector.ZetaConnectorNonEth, - custodyAddress ethcommon.Address, - custody *erc20custody.ERC20Custody, -) (*big.Int, chains.ReceiveStatus, error) { - // determine the receive status and value - // https://docs.nethereum.com/en/latest/nethereum-receipt-status/ - receiveValue := big.NewInt(0) - receiveStatus := chains.ReceiveStatus_failed - if receipt.Status == ethtypes.ReceiptStatusSuccessful { - receiveValue = transaction.Value() - receiveStatus = chains.ReceiveStatus_success - } - - // parse receive value from the outbound receipt for Zeta and ERC20 - switch cointype { - case coin.CoinType_Zeta: - if receipt.Status == ethtypes.ReceiptStatusSuccessful { - receivedLog, revertedLog, err := ParseAndCheckZetaEvent(cctx, receipt, connectorAddress, connector) - if err != nil { - return nil, chains.ReceiveStatus_failed, err - } - // use the value in ZetaReceived/ZetaReverted event for vote message - if receivedLog != nil { - receiveValue = receivedLog.ZetaValue - } else if revertedLog != nil { - receiveValue = revertedLog.RemainingZetaValue - } - } - case coin.CoinType_ERC20: - if receipt.Status == ethtypes.ReceiptStatusSuccessful { - withdrawn, err := ParseAndCheckWithdrawnEvent(cctx, receipt, custodyAddress, custody) - if err != nil { - return nil, chains.ReceiveStatus_failed, err - } - // use the value in Withdrawn event for vote message - receiveValue = withdrawn.Amount - } - case coin.CoinType_Gas, coin.CoinType_Cmd: - // nothing to do for CoinType_Gas/CoinType_Cmd, no need to parse event - default: - return nil, chains.ReceiveStatus_failed, fmt.Errorf("unknown coin type %s", cointype) - } - return receiveValue, receiveStatus, nil -} - // checkConfirmedTx checks if a txHash is confirmed // returns (receipt, transaction, true) if confirmed or (nil, nil, false) otherwise func (ob *Observer) checkConfirmedTx( diff --git a/zetaclient/chains/evm/observer/v2_outbound.go b/zetaclient/chains/evm/observer/v2_outbound.go new file mode 100644 index 0000000000..9cf2985477 --- /dev/null +++ b/zetaclient/chains/evm/observer/v2_outbound.go @@ -0,0 +1,37 @@ +package observer + +import ( + "fmt" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/zeta-chain/zetacore/pkg/chains" + "github.com/zeta-chain/zetacore/x/crosschain/types" + "github.com/zeta-chain/zetacore/zetaclient/chains/evm" + "math/big" +) + +// ParseOutboundEventV2 parses an event from an outbound with protocol contract v2 +func ParseOutboundEventV2( + cctx *types.CrossChainTx, + receipt *ethtypes.Receipt, + transaction *ethtypes.Transaction, +) (*big.Int, chains.ReceiveStatus, error) { + receivedValue := big.NewInt(0) + status := chains.ReceiveStatus_failed + if receipt.Status == ethtypes.ReceiptStatusSuccessful { + receivedValue = transaction.Value() + status = chains.ReceiveStatus_success + } + + outboundType := evm.ParseOutboundTypeFromCCTX(*cctx) + switch outboundType { + case evm.OutboundTypeGasWithdraw: + return receivedValue, status, nil + case evm.OutboundTypeERC20Withdraw: + return receivedValue, status, nil + case evm.OutboundTypeGasWithdrawAndCall: + return receivedValue, status, nil + case evm.OutboundTypeERC20WithdrawAndCall: + return receivedValue, status, nil + } + return receivedValue, status, fmt.Errorf("unsupported outbound type %d", outboundType) +} diff --git a/zetaclient/chains/evm/signer/outbound_data.go b/zetaclient/chains/evm/signer/outbound_data.go index ebeef1c5b1..f003152bdf 100644 --- a/zetaclient/chains/evm/signer/outbound_data.go +++ b/zetaclient/chains/evm/signer/outbound_data.go @@ -182,9 +182,17 @@ func NewOutboundData( // Base64 decode message if cctx.InboundParams.CoinType != coin.CoinType_Cmd { - txData.message, err = base64.StdEncoding.DecodeString(cctx.RelayedMessage) - if err != nil { - logger.Err(err).Msgf("decode CCTX.Message %s error", cctx.RelayedMessage) + // protocol contract v2 uses hex encoding + if cctx.ProtocolContractVersion == types.ProtocolContractVersion_V2 { + txData.message, err = hex.DecodeString(cctx.RelayedMessage) + if err != nil { + logger.Err(err).Msgf("decode CCTX.Message %s error", cctx.RelayedMessage) + } + } else { + txData.message, err = base64.StdEncoding.DecodeString(cctx.RelayedMessage) + if err != nil { + logger.Err(err).Msgf("decode CCTX.Message %s error", cctx.RelayedMessage) + } } } diff --git a/zetaclient/chains/evm/signer/sign.go b/zetaclient/chains/evm/signer/sign.go index d59af3459f..e7dfd1ce3c 100644 --- a/zetaclient/chains/evm/signer/sign.go +++ b/zetaclient/chains/evm/signer/sign.go @@ -8,6 +8,7 @@ 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" ) @@ -235,6 +236,9 @@ func (signer *Signer) SignMigrateTssFundsCmd(ctx context.Context, txData *Outbou } // SignGasWithdrawAndCall signs a gas withdrawal and call transaction +// function execute +// address destination, +// bytes calldata data func (signer *Signer) SignGasWithdrawAndCall(ctx context.Context, txData *OutboundData) (*ethtypes.Transaction, error) { var data []byte var err error @@ -244,7 +248,44 @@ func (signer *Signer) SignGasWithdrawAndCall(ctx context.Context, txData *Outbou return nil, errors.Wrap(err, "unable to get GatewayEVMMetaData ABI") } - data, err = gatewayABI.Pack("withdraw", txData.to, txData.asset, txData.amount) + data, err = gatewayABI.Pack("withdraw", txData.to, txData.message) + if err != nil { + return nil, fmt.Errorf("withdraw 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 withdraw error: %w", err) + } + + return tx, nil +} + +// SignERC20WithdrawAndCall signs a gas withdrawal and call transaction +// function withdrawAndCall +// address token, +// address to, +// uint256 amount, +// bytes calldata data +func (signer *Signer) SignERC20WithdrawAndCall(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) } diff --git a/zetaclient/chains/evm/signer/signer.go b/zetaclient/chains/evm/signer/signer.go index b9e4fe0d05..d26f2e635e 100644 --- a/zetaclient/chains/evm/signer/signer.go +++ b/zetaclient/chains/evm/signer/signer.go @@ -258,7 +258,14 @@ func (signer *Signer) TryProcessOutbound( } // sign outbound - tx, err := signer.SignConnectorOnReceive(ctx, txData) + tx, err := signer.SignOutboundFromCCTX( + ctx, + logger, + cctx, + txData, + zetacoreClient, + toChain, + ) if err != nil { logger.Err(err).Msg("error signing outbound") return diff --git a/zetaclient/chains/evm/signer/v2_signer.go b/zetaclient/chains/evm/signer/v2_signer.go new file mode 100644 index 0000000000..909588ac14 --- /dev/null +++ b/zetaclient/chains/evm/signer/v2_signer.go @@ -0,0 +1,30 @@ +package signer + +import ( + "context" + "fmt" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/zeta-chain/zetacore/x/crosschain/types" + "github.com/zeta-chain/zetacore/zetaclient/chains/evm" +) + +// SignOutboundFromCCTXV2 signs an outbound transaction from a CCTX with protocol contract v2 +func (signer *Signer) SignOutboundFromCCTXV2( + ctx context.Context, + cctx *types.CrossChainTx, + outboundData *OutboundData, +) (*ethtypes.Transaction, error) { + outboundType := evm.ParseOutboundTypeFromCCTX(*cctx) + switch outboundType { + case evm.OutboundTypeGasWithdraw: + return signer.SignGasWithdraw(ctx, outboundData) + case evm.OutboundTypeERC20Withdraw: + return signer.SignERC20Withdraw(ctx, outboundData) + case evm.OutboundTypeGasWithdrawAndCall: + return signer.SignGasWithdrawAndCall(ctx, outboundData) + case evm.OutboundTypeERC20WithdrawAndCall: + return signer.SignERC20WithdrawAndCall(ctx, outboundData) + default: + return nil, fmt.Errorf("unsupported outbound type %d", outboundType) + } +} diff --git a/zetaclient/chains/solana/signer/signer.go b/zetaclient/chains/solana/signer/signer.go index 3fb7512512..09a7c2c4ff 100644 --- a/zetaclient/chains/solana/signer/signer.go +++ b/zetaclient/chains/solana/signer/signer.go @@ -117,7 +117,7 @@ func (signer *Signer) TryProcessOutbound( // sign the withdraw transaction by fee payer tx, err := signer.SignWithdrawTx(ctx, *msg) if err != nil { - logger.Error().Err(err).Msgf("TryProcessOutbound: SignWithdrawTx error for chain %d nonce %d", chainID, nonce) + logger.Error().Err(err).Msgf("TryProcessOutbound: SignGasWithdraw error for chain %d nonce %d", chainID, nonce) return }