From cf747df9cbc9c4b3e0b7d46903ee9f610ee9c480 Mon Sep 17 00:00:00 2001 From: skosito Date: Mon, 16 Dec 2024 14:51:33 +0000 Subject: [PATCH] fix: use the ZEVM address pulled from memo as Receiver in MsgVoteInbound (#3267) * fix: use the ZEVM address pulled from memo as Receiver in MsgVoteInbound (#3242) * use the ZEVM address decoded from memo as Receiver in MsgVoteInbound * add Receiver check in unit test * add changelog entry * fix unit test * fix unit test --------- Co-authored-by: Charlie Chen <34498985+ws4charlie@users.noreply.github.com> Co-authored-by: Lucas Bertrand --- e2e/e2etests/test_solana_deposit.go | 1 + e2e/e2etests/test_solana_deposit_call.go | 1 + e2e/e2etests/test_spl_deposit.go | 1 + e2e/e2etests/test_spl_deposit_and_call.go | 1 + zetaclient/chains/solana/observer/inbound.go | 20 ++++++++++-- .../chains/solana/observer/inbound_test.go | 8 +++-- zetaclient/types/event.go | 31 +++++++++++++++++++ 7 files changed, 57 insertions(+), 6 deletions(-) diff --git a/e2e/e2etests/test_solana_deposit.go b/e2e/e2etests/test_solana_deposit.go index d46a56c015..eb238c8c58 100644 --- a/e2e/e2etests/test_solana_deposit.go +++ b/e2e/e2etests/test_solana_deposit.go @@ -29,6 +29,7 @@ func TestSolanaDeposit(r *runner.E2ERunner, args []string) { cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, sig.String(), r.CctxClient, r.Logger, r.CctxTimeout) r.Logger.CCTX(*cctx, "solana_deposit") utils.RequireCCTXStatus(r, cctx, crosschaintypes.CctxStatus_OutboundMined) + require.Equal(r, cctx.GetCurrentOutboundParam().Receiver, r.EVMAddress().Hex()) // get ERC20 SOL balance after deposit balanceAfter, err := r.SOLZRC20.BalanceOf(&bind.CallOpts{}, r.EVMAddress()) diff --git a/e2e/e2etests/test_solana_deposit_call.go b/e2e/e2etests/test_solana_deposit_call.go index 0d7ef97975..7d64c5a5d6 100644 --- a/e2e/e2etests/test_solana_deposit_call.go +++ b/e2e/e2etests/test_solana_deposit_call.go @@ -29,6 +29,7 @@ func TestSolanaDepositAndCall(r *runner.E2ERunner, args []string) { cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, sig.String(), r.CctxClient, r.Logger, r.CctxTimeout) r.Logger.CCTX(*cctx, "solana_deposit_and_call") utils.RequireCCTXStatus(r, cctx, crosschaintypes.CctxStatus_OutboundMined) + require.Equal(r, cctx.GetCurrentOutboundParam().Receiver, contractAddr.Hex()) // check if example contract has been called, bar value should be set to amount utils.MustHaveCalledExampleContractWithMsg(r, contract, depositAmount, data) diff --git a/e2e/e2etests/test_spl_deposit.go b/e2e/e2etests/test_spl_deposit.go index e20ff5879a..7bf1ffa7a9 100644 --- a/e2e/e2etests/test_spl_deposit.go +++ b/e2e/e2etests/test_spl_deposit.go @@ -42,6 +42,7 @@ func TestSPLDeposit(r *runner.E2ERunner, args []string) { cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, sig.String(), r.CctxClient, r.Logger, r.CctxTimeout) r.Logger.CCTX(*cctx, "solana_deposit_spl") utils.RequireCCTXStatus(r, cctx, crosschaintypes.CctxStatus_OutboundMined) + require.Equal(r, cctx.GetCurrentOutboundParam().Receiver, r.EVMAddress().Hex()) // verify balances are updated pdaBalanceAfter, err := r.SolanaClient.GetTokenAccountBalance(r.Ctx, pdaAta, rpc.CommitmentFinalized) diff --git a/e2e/e2etests/test_spl_deposit_and_call.go b/e2e/e2etests/test_spl_deposit_and_call.go index 9c15c8c27f..9d311d5906 100644 --- a/e2e/e2etests/test_spl_deposit_and_call.go +++ b/e2e/e2etests/test_spl_deposit_and_call.go @@ -49,6 +49,7 @@ func TestSPLDepositAndCall(r *runner.E2ERunner, args []string) { cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, sig.String(), r.CctxClient, r.Logger, r.CctxTimeout) r.Logger.CCTX(*cctx, "solana_deposit_spl_and_call") utils.RequireCCTXStatus(r, cctx, crosschaintypes.CctxStatus_OutboundMined) + require.Equal(r, cctx.GetCurrentOutboundParam().Receiver, contractAddr.Hex()) // check if example contract has been called, bar value should be set to amount utils.MustHaveCalledExampleContractWithMsg(r, contract, big.NewInt(int64(amount)), data) diff --git a/zetaclient/chains/solana/observer/inbound.go b/zetaclient/chains/solana/observer/inbound.go index fa3edde764..68de065e9c 100644 --- a/zetaclient/chains/solana/observer/inbound.go +++ b/zetaclient/chains/solana/observer/inbound.go @@ -19,6 +19,7 @@ import ( solanarpc "github.com/zeta-chain/node/zetaclient/chains/solana/rpc" "github.com/zeta-chain/node/zetaclient/compliance" zctx "github.com/zeta-chain/node/zetaclient/context" + "github.com/zeta-chain/node/zetaclient/logs" clienttypes "github.com/zeta-chain/node/zetaclient/types" "github.com/zeta-chain/node/zetaclient/zetacore" ) @@ -213,7 +214,7 @@ func (ob *Observer) FilterInboundEvents(txResult *rpc.GetTransactionResult) ([]* events = append(events, &clienttypes.InboundEvent{ SenderChainID: ob.Chain().ChainId, Sender: deposit.Sender, - Receiver: deposit.Sender, // receiver is pulled out from memo + Receiver: "", // receiver will be pulled out from memo later TxOrigin: deposit.Sender, Amount: deposit.Amount, Memo: deposit.Memo, @@ -241,7 +242,7 @@ func (ob *Observer) FilterInboundEvents(txResult *rpc.GetTransactionResult) ([]* events = append(events, &clienttypes.InboundEvent{ SenderChainID: ob.Chain().ChainId, Sender: deposit.Sender, - Receiver: deposit.Sender, // receiver is pulled out from memo + Receiver: "", // receiver will be pulled out from memo later TxOrigin: deposit.Sender, Amount: deposit.Amount, Memo: deposit.Memo, @@ -277,11 +278,24 @@ func (ob *Observer) BuildInboundVoteMsgFromEvent(event *clienttypes.InboundEvent return nil } + // prepare logger fields + lf := map[string]any{ + logs.FieldMethod: "BuildInboundVoteMsgFromEvent", + logs.FieldTx: event.TxHash, + } + + // decode event memo bytes to get the receiver + err := event.DecodeMemo() + if err != nil { + ob.Logger().Inbound.Info().Fields(lf).Msgf("invalid memo bytes: %s", hex.EncodeToString(event.Memo)) + return nil + } + return zetacore.GetInboundVoteMessage( event.Sender, event.SenderChainID, event.Sender, - event.Sender, + event.Receiver, ob.ZetacoreClient().Chain().ChainId, cosmosmath.NewUint(event.Amount), hex.EncodeToString(event.Memo), diff --git a/zetaclient/chains/solana/observer/inbound_test.go b/zetaclient/chains/solana/observer/inbound_test.go index 025d72592a..deb5d4ab51 100644 --- a/zetaclient/chains/solana/observer/inbound_test.go +++ b/zetaclient/chains/solana/observer/inbound_test.go @@ -82,7 +82,7 @@ func Test_FilterInboundEvents(t *testing.T) { eventExpected := &clienttypes.InboundEvent{ SenderChainID: chain.ChainId, Sender: sender, - Receiver: sender, + Receiver: "", TxOrigin: sender, Amount: 100000000, Memo: expectedMemo, @@ -124,11 +124,13 @@ func Test_BuildInboundVoteMsgFromEvent(t *testing.T) { t.Run("should return vote msg for valid event", func(t *testing.T) { sender := sample.SolanaAddress(t) - memo := sample.EthAddress().Bytes() - event := sample.InboundEvent(chain.ChainId, sender, sender, 1280, []byte(memo)) + receiver := sample.EthAddress() + event := sample.InboundEvent(chain.ChainId, sender, "", 1280, receiver.Bytes()) msg := ob.BuildInboundVoteMsgFromEvent(event) require.NotNil(t, msg) + require.Equal(t, sender, msg.Sender) + require.Equal(t, receiver.Hex(), msg.Receiver) }) t.Run("should return nil msg if sender is restricted", func(t *testing.T) { sender := sample.SolanaAddress(t) diff --git a/zetaclient/types/event.go b/zetaclient/types/event.go index a0313236e6..6709b063bd 100644 --- a/zetaclient/types/event.go +++ b/zetaclient/types/event.go @@ -1,7 +1,15 @@ package types import ( + "bytes" + "encoding/hex" + + "github.com/pkg/errors" + "github.com/zeta-chain/node/pkg/coin" + "github.com/zeta-chain/node/pkg/constant" + "github.com/zeta-chain/node/pkg/crypto" + "github.com/zeta-chain/node/pkg/memo" ) // InboundEvent represents an inbound event @@ -41,3 +49,26 @@ type InboundEvent struct { // Asset is the asset of the inbound Asset string } + +// DecodeMemo decodes the receiver from the memo bytes +func (event *InboundEvent) DecodeMemo() error { + // skip decoding donation tx as it won't go through zetacore + if bytes.Equal(event.Memo, []byte(constant.DonationMessage)) { + return nil + } + + // decode receiver address from memo + parsedAddress, _, err := memo.DecodeLegacyMemoHex(hex.EncodeToString(event.Memo)) + if err != nil { // unreachable code + return errors.Wrap(err, "invalid memo hex") + } + + // ensure the receiver is valid + if crypto.IsEmptyAddress(parsedAddress) { + return errors.New("got empty receiver address from memo") + } + + event.Receiver = parsedAddress.Hex() + + return nil +}