From c75b5693671635dff3a4b980900dc5f320b062c2 Mon Sep 17 00:00:00 2001 From: Austin Chandra Date: Thu, 3 Nov 2022 16:01:20 -0700 Subject: [PATCH 1/8] Add EIP-712 encoding support type for any array --- ethereum/eip712/eip712.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/ethereum/eip712/eip712.go b/ethereum/eip712/eip712.go index 50860093d3..823b299886 100644 --- a/ethereum/eip712/eip712.go +++ b/ethereum/eip712/eip712.go @@ -273,6 +273,24 @@ func traverseFields( fieldType = fieldType.Elem() field = field.Index(0) isCollection = true + + // Handle array of type Any + if fieldType == cosmosAnyType { + any, ok := field.Interface().(*codectypes.Any) + if !ok { + return sdkerrors.Wrapf(sdkerrors.ErrPackAny, "%T", field.Interface()) + } + + // Unwrap directly into object + var anyUnwrapped interface{} + + if err := cdc.UnpackAny(any, &anyUnwrapped); err != nil { + return sdkerrors.Wrap(err, "failed to unpack Any in msg struct") + } + + fieldType = reflect.TypeOf(anyUnwrapped) + field = reflect.ValueOf(anyUnwrapped) + } } for { From 207f96751316038f949909b28efd7f1f76ee9994 Mon Sep 17 00:00:00 2001 From: Austin Chandra Date: Thu, 10 Nov 2022 21:16:31 -0800 Subject: [PATCH 2/8] Refactor implementation + add tests --- app/ante/ante_test.go | 13 +++++- app/ante/utils_test.go | 30 +++++++++++++ ethereum/eip712/eip712.go | 93 ++++++++++++++++++++++++--------------- 3 files changed, 100 insertions(+), 36 deletions(-) diff --git a/app/ante/ante_test.go b/app/ante/ante_test.go index 689f56cef4..fafc1e25a3 100644 --- a/app/ante/ante_test.go +++ b/app/ante/ante_test.go @@ -409,7 +409,18 @@ func (suite AnteTestSuite) TestAnteHandler() { coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(20)) amount := sdk.NewCoins(coinAmount) gas := uint64(200000) - txBuilder := suite.CreateTestEIP712MsgEditValidator(from, privKey, "ethermint_9000-1", gas, amount) + txBuilder := suite.CreateTestEIP712MsgSubmitEvidence(from, privKey, "ethermint_9000-1", gas, amount) + return txBuilder.GetTx() + }, false, false, true, + }, + { + "success- DeliverTx EIP712 submit proposal v1", + func() sdk.Tx { + from := acc.GetAddress() + coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(20)) + amount := sdk.NewCoins(coinAmount) + gas := uint64(200000) + txBuilder := suite.CreateTestEIP712SubmitProposalV1(from, privKey, "ethermint_9000-1", gas, amount) return txBuilder.GetTx() }, false, false, true, }, diff --git a/app/ante/utils_test.go b/app/ante/utils_test.go index 14b79b581c..63a5a23aea 100644 --- a/app/ante/utils_test.go +++ b/app/ante/utils_test.go @@ -43,6 +43,7 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" evtypes "github.com/cosmos/cosmos-sdk/x/evidence/types" "github.com/cosmos/cosmos-sdk/x/feegrant" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types/v1" types5 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" "github.com/evmos/ethermint/app" ante "github.com/evmos/ethermint/app/ante" @@ -343,6 +344,35 @@ func (suite *AnteTestSuite) CreateTestEIP712MsgSubmitEvidence(from sdk.AccAddres return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, msgEvidence) } +func (suite *AnteTestSuite) CreateTestEIP712SubmitProposalV1(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder { + // Build V1 proposal messages. Must all be same-type, since EIP-712 + // does not support arrays of variable type. + recipient1 := sdk.AccAddress(common.Address{}.Bytes()) + msgSend1 := types2.NewMsgSend(from, recipient1, sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(1)))) + + recipient2 := sdk.AccAddress(common.HexToAddress( + "0x4242424242424242424242424242424242424242", + ).Bytes()) + msgSend2 := types2.NewMsgSend(from, recipient2, sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(1)))) + + proposalMsgs := []sdk.Msg{ + msgSend1, + msgSend2, + } + + // Build V1 proposal + msgProposal, err := govtypes.NewMsgSubmitProposal( + proposalMsgs, + sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(100))), + sdk.MustBech32ifyAddressBytes(sdk.GetConfig().GetBech32AccountAddrPrefix(), from.Bytes()), + "Metadata", + ) + + suite.Require().NoError(err) + + return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, msgProposal) +} + // StdSignBytes returns the bytes to sign for a transaction. func StdSignBytes(cdc *codec.LegacyAmino, chainID string, accnum uint64, sequence uint64, timeout uint64, fee legacytx.StdFee, msgs []sdk.Msg, memo string, tip *txtypes.Tip) []byte { msgsBytes := make([]json.RawMessage, 0, len(msgs)) diff --git a/ethereum/eip712/eip712.go b/ethereum/eip712/eip712.go index 823b299886..5140b54af3 100644 --- a/ethereum/eip712/eip712.go +++ b/ethereum/eip712/eip712.go @@ -206,7 +206,11 @@ func traverseFields( } for i := 0; i < n; i++ { - var field reflect.Value + var ( + field reflect.Value + err error + ) + if v.IsValid() { field = v.Field(i) } @@ -214,24 +218,8 @@ func traverseFields( fieldType := t.Field(i).Type fieldName := jsonNameFromTag(t.Field(i).Tag) - if fieldType == cosmosAnyType { - any, ok := field.Interface().(*codectypes.Any) - if !ok { - return sdkerrors.Wrapf(sdkerrors.ErrPackAny, "%T", field.Interface()) - } - - anyWrapper := &cosmosAnyWrapper{ - Type: any.TypeUrl, - } - - if err := cdc.UnpackAny(any, &anyWrapper.Value); err != nil { - return sdkerrors.Wrap(err, "failed to unpack Any in msg struct") - } - - fieldType = reflect.TypeOf(anyWrapper) - field = reflect.ValueOf(anyWrapper) - - // then continue as normal + if fieldType, field, err = unpackIfAny(cdc, fieldType, field, true); err != nil { + return err } // If its a nil pointer, do not include in types @@ -274,22 +262,8 @@ func traverseFields( field = field.Index(0) isCollection = true - // Handle array of type Any - if fieldType == cosmosAnyType { - any, ok := field.Interface().(*codectypes.Any) - if !ok { - return sdkerrors.Wrapf(sdkerrors.ErrPackAny, "%T", field.Interface()) - } - - // Unwrap directly into object - var anyUnwrapped interface{} - - if err := cdc.UnpackAny(any, &anyUnwrapped); err != nil { - return sdkerrors.Wrap(err, "failed to unpack Any in msg struct") - } - - fieldType = reflect.TypeOf(anyUnwrapped) - field = reflect.ValueOf(anyUnwrapped) + if fieldType, field, err = unpackIfAny(cdc, fieldType, field, false); err != nil { + return err } } @@ -381,6 +355,55 @@ func jsonNameFromTag(tag reflect.StructTag) string { return parts[0] } +func unpackIfAny( + cdc codectypes.AnyUnpacker, + _fieldType reflect.Type, + _field reflect.Value, + assumeMsgStructure bool, +) (reflect.Type, reflect.Value, error) { + // Verify type is Any, else return as-is + if _fieldType != cosmosAnyType { + return _fieldType, _field, nil + } + + // Unwrap as Any + any, ok := _field.Interface().(*codectypes.Any) + if !ok { + return nil, reflect.Value{}, sdkerrors.Wrapf(sdkerrors.ErrPackAny, "%T", _field.Interface()) + } + + var ( + fieldType reflect.Type + field reflect.Value + ) + + if assumeMsgStructure { + // Unpack Any as Msg (for Amino messages) + anyWrapper := &cosmosAnyWrapper{ + Type: any.TypeUrl, + } + + if err := cdc.UnpackAny(any, &anyWrapper.Value); err != nil { + return nil, reflect.Value{}, sdkerrors.Wrap(err, "failed to unpack Any in msg struct") + } + + fieldType = reflect.TypeOf(anyWrapper) + field = reflect.ValueOf(anyWrapper) + } else { + // Unpack using default (direct) + var anyUnwrapped interface{} + + if err := cdc.UnpackAny(any, &anyUnwrapped); err != nil { + return nil, reflect.Value{}, sdkerrors.Wrap(err, "failed to unpack Any in msg struct") + } + + fieldType = reflect.TypeOf(anyUnwrapped) + field = reflect.ValueOf(anyUnwrapped) + } + + return fieldType, field, nil +} + // _.foo_bar.baz -> TypeFooBarBaz // // this is needed for Geth's own signing code which doesn't From 29dc4d61e7421905854e25cb0a2ddb3dd22f8adc Mon Sep 17 00:00:00 2001 From: Austin Chandra Date: Fri, 11 Nov 2022 17:00:37 -0800 Subject: [PATCH 3/8] Refactor unpacking implementation; refactor test case --- app/ante/utils_test.go | 26 +++++++++++----- ethereum/eip712/eip712.go | 65 +++++++++++++-------------------------- 2 files changed, 39 insertions(+), 52 deletions(-) diff --git a/app/ante/utils_test.go b/app/ante/utils_test.go index 63a5a23aea..0d4714c1cf 100644 --- a/app/ante/utils_test.go +++ b/app/ante/utils_test.go @@ -347,17 +347,27 @@ func (suite *AnteTestSuite) CreateTestEIP712MsgSubmitEvidence(from sdk.AccAddres func (suite *AnteTestSuite) CreateTestEIP712SubmitProposalV1(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder { // Build V1 proposal messages. Must all be same-type, since EIP-712 // does not support arrays of variable type. - recipient1 := sdk.AccAddress(common.Address{}.Bytes()) - msgSend1 := types2.NewMsgSend(from, recipient1, sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(1)))) + authAcc := suite.app.GovKeeper.GetGovernanceAccount(suite.ctx) - recipient2 := sdk.AccAddress(common.HexToAddress( - "0x4242424242424242424242424242424242424242", - ).Bytes()) - msgSend2 := types2.NewMsgSend(from, recipient2, sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(1)))) + proposal1, ok := types5.ContentFromProposalType("My proposal 1", "My description 1", types5.ProposalTypeText) + suite.Require().True(ok) + content1, err := govtypes.NewLegacyContent( + proposal1, + sdk.MustBech32ifyAddressBytes(sdk.GetConfig().GetBech32AccountAddrPrefix(), authAcc.GetAddress().Bytes()), + ) + suite.Require().NoError(err) + + proposal2, ok := types5.ContentFromProposalType("My proposal 2", "My description 2", types5.ProposalTypeText) + suite.Require().True(ok) + content2, err := govtypes.NewLegacyContent( + proposal2, + sdk.MustBech32ifyAddressBytes(sdk.GetConfig().GetBech32AccountAddrPrefix(), authAcc.GetAddress().Bytes()), + ) + suite.Require().NoError(err) proposalMsgs := []sdk.Msg{ - msgSend1, - msgSend2, + content1, + content2, } // Build V1 proposal diff --git a/ethereum/eip712/eip712.go b/ethereum/eip712/eip712.go index 5140b54af3..efa2c19fc9 100644 --- a/ethereum/eip712/eip712.go +++ b/ethereum/eip712/eip712.go @@ -218,8 +218,11 @@ func traverseFields( fieldType := t.Field(i).Type fieldName := jsonNameFromTag(t.Field(i).Tag) - if fieldType, field, err = unpackIfAny(cdc, fieldType, field, true); err != nil { - return err + if fieldType == cosmosAnyType { + // Unpack as Any, assuming message structure + if fieldType, field, err = unpackAny(cdc, fieldType, field); err != nil { + return err + } } // If its a nil pointer, do not include in types @@ -262,8 +265,10 @@ func traverseFields( field = field.Index(0) isCollection = true - if fieldType, field, err = unpackIfAny(cdc, fieldType, field, false); err != nil { - return err + if fieldType == cosmosAnyType { + if fieldType, field, err = unpackAny(cdc, fieldType, field); err != nil { + return err + } } } @@ -355,52 +360,24 @@ func jsonNameFromTag(tag reflect.StructTag) string { return parts[0] } -func unpackIfAny( - cdc codectypes.AnyUnpacker, - _fieldType reflect.Type, - _field reflect.Value, - assumeMsgStructure bool, -) (reflect.Type, reflect.Value, error) { - // Verify type is Any, else return as-is - if _fieldType != cosmosAnyType { - return _fieldType, _field, nil - } - - // Unwrap as Any - any, ok := _field.Interface().(*codectypes.Any) +// Unpack the given Any value with Type/Value deconstruction +func unpackAny(cdc codectypes.AnyUnpacker, fieldType reflect.Type, field reflect.Value) (reflect.Type, reflect.Value, error) { + any, ok := field.Interface().(*codectypes.Any) if !ok { - return nil, reflect.Value{}, sdkerrors.Wrapf(sdkerrors.ErrPackAny, "%T", _field.Interface()) + return nil, reflect.Value{}, sdkerrors.Wrapf(sdkerrors.ErrPackAny, "%T", field.Interface()) } - var ( - fieldType reflect.Type - field reflect.Value - ) - - if assumeMsgStructure { - // Unpack Any as Msg (for Amino messages) - anyWrapper := &cosmosAnyWrapper{ - Type: any.TypeUrl, - } - - if err := cdc.UnpackAny(any, &anyWrapper.Value); err != nil { - return nil, reflect.Value{}, sdkerrors.Wrap(err, "failed to unpack Any in msg struct") - } - - fieldType = reflect.TypeOf(anyWrapper) - field = reflect.ValueOf(anyWrapper) - } else { - // Unpack using default (direct) - var anyUnwrapped interface{} - - if err := cdc.UnpackAny(any, &anyUnwrapped); err != nil { - return nil, reflect.Value{}, sdkerrors.Wrap(err, "failed to unpack Any in msg struct") - } + anyWrapper := &cosmosAnyWrapper{ + Type: any.TypeUrl, + } - fieldType = reflect.TypeOf(anyUnwrapped) - field = reflect.ValueOf(anyUnwrapped) + if err := cdc.UnpackAny(any, &anyWrapper.Value); err != nil { + return nil, reflect.Value{}, sdkerrors.Wrap(err, "failed to unpack Any in msg struct") } + fieldType = reflect.TypeOf(anyWrapper) + field = reflect.ValueOf(anyWrapper) + return fieldType, field, nil } From 3c8f839c2fa0edd5d25361649ad6643f396c58b9 Mon Sep 17 00:00:00 2001 From: Austin Chandra Date: Fri, 11 Nov 2022 18:34:47 -0800 Subject: [PATCH 4/8] Fix lint issue --- ethereum/eip712/eip712.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ethereum/eip712/eip712.go b/ethereum/eip712/eip712.go index efa2c19fc9..6f1609e010 100644 --- a/ethereum/eip712/eip712.go +++ b/ethereum/eip712/eip712.go @@ -220,7 +220,7 @@ func traverseFields( if fieldType == cosmosAnyType { // Unpack as Any, assuming message structure - if fieldType, field, err = unpackAny(cdc, fieldType, field); err != nil { + if fieldType, field, err = unpackAny(cdc, field); err != nil { return err } } @@ -266,7 +266,7 @@ func traverseFields( isCollection = true if fieldType == cosmosAnyType { - if fieldType, field, err = unpackAny(cdc, fieldType, field); err != nil { + if fieldType, field, err = unpackAny(cdc, field); err != nil { return err } } @@ -361,7 +361,7 @@ func jsonNameFromTag(tag reflect.StructTag) string { } // Unpack the given Any value with Type/Value deconstruction -func unpackAny(cdc codectypes.AnyUnpacker, fieldType reflect.Type, field reflect.Value) (reflect.Type, reflect.Value, error) { +func unpackAny(cdc codectypes.AnyUnpacker, field reflect.Value) (reflect.Type, reflect.Value, error) { any, ok := field.Interface().(*codectypes.Any) if !ok { return nil, reflect.Value{}, sdkerrors.Wrapf(sdkerrors.ErrPackAny, "%T", field.Interface()) @@ -375,7 +375,7 @@ func unpackAny(cdc codectypes.AnyUnpacker, fieldType reflect.Type, field reflect return nil, reflect.Value{}, sdkerrors.Wrap(err, "failed to unpack Any in msg struct") } - fieldType = reflect.TypeOf(anyWrapper) + fieldType := reflect.TypeOf(anyWrapper) field = reflect.ValueOf(anyWrapper) return fieldType, field, nil From 28e164d5761db035e10ab8e9f216c2ff34705610 Mon Sep 17 00:00:00 2001 From: Austin Chandra Date: Fri, 11 Nov 2022 19:37:00 -0800 Subject: [PATCH 5/8] Add MsgExec test case --- app/ante/ante_test.go | 11 +++++++++++ app/ante/utils_test.go | 8 ++++++++ 2 files changed, 19 insertions(+) diff --git a/app/ante/ante_test.go b/app/ante/ante_test.go index fafc1e25a3..34be0e0c9b 100644 --- a/app/ante/ante_test.go +++ b/app/ante/ante_test.go @@ -424,6 +424,17 @@ func (suite AnteTestSuite) TestAnteHandler() { return txBuilder.GetTx() }, false, false, true, }, + { + "success- DeliverTx EIP712 MsgExec", + func() sdk.Tx { + from := acc.GetAddress() + coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(20)) + amount := sdk.NewCoins(coinAmount) + gas := uint64(200000) + txBuilder := suite.CreateTestEIP712MsgExec(from, privKey, "ethermint_9000-1", gas, amount) + return txBuilder.GetTx() + }, false, false, true, + }, { "fails - DeliverTx EIP712 signed Cosmos Tx with wrong Chain ID", func() sdk.Tx { diff --git a/app/ante/utils_test.go b/app/ante/utils_test.go index 0d4714c1cf..6f9eb80137 100644 --- a/app/ante/utils_test.go +++ b/app/ante/utils_test.go @@ -37,6 +37,7 @@ import ( authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + authz "github.com/cosmos/cosmos-sdk/x/authz" cryptocodec "github.com/evmos/ethermint/crypto/codec" "github.com/evmos/ethermint/crypto/ethsecp256k1" @@ -383,6 +384,13 @@ func (suite *AnteTestSuite) CreateTestEIP712SubmitProposalV1(from sdk.AccAddress return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, msgProposal) } +func (suite *AnteTestSuite) CreateTestEIP712MsgExec(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder { + recipient := sdk.AccAddress(common.Address{}.Bytes()) + msgSend := types2.NewMsgSend(from, recipient, sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(1)))) + msgExec := authz.NewMsgExec(from, []sdk.Msg{msgSend}) + return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, &msgExec) +} + // StdSignBytes returns the bytes to sign for a transaction. func StdSignBytes(cdc *codec.LegacyAmino, chainID string, accnum uint64, sequence uint64, timeout uint64, fee legacytx.StdFee, msgs []sdk.Msg, memo string, tip *txtypes.Tip) []byte { msgsBytes := make([]json.RawMessage, 0, len(msgs)) From 833255a95d67e90fc4bf1cb497eb2583323fc6fb Mon Sep 17 00:00:00 2001 From: Austin Chandra Date: Mon, 14 Nov 2022 08:45:08 -0800 Subject: [PATCH 6/8] Update comment for clarity --- ethereum/eip712/eip712.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereum/eip712/eip712.go b/ethereum/eip712/eip712.go index 6f1609e010..2045755e76 100644 --- a/ethereum/eip712/eip712.go +++ b/ethereum/eip712/eip712.go @@ -219,7 +219,7 @@ func traverseFields( fieldName := jsonNameFromTag(t.Field(i).Tag) if fieldType == cosmosAnyType { - // Unpack as Any, assuming message structure + // Unpack field, value as Any if fieldType, field, err = unpackAny(cdc, field); err != nil { return err } From 8724220b36fa9d3b1362d51d05644a18503f6816 Mon Sep 17 00:00:00 2001 From: Austin Chandra Date: Mon, 14 Nov 2022 16:00:40 -0800 Subject: [PATCH 7/8] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c87967620c..42b7890820 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (deps) [#1168](https://github.com/evmos/ethermint/pull/1168) Upgrade Cosmos SDK to `v0.46`. * (feemarket) [#1194](https://github.com/evmos/ethermint/pull/1194) Apply feemarket to native cosmos tx. * (eth) [#1346](https://github.com/evmos/ethermint/pull/1346) Added support for `sdk.Dec` and `ed25519` type on eip712. +* (eth) [#1430](https://github.com/evmos/ethermint/pull/1430) Added support for array of type `Any` on eip712.  ### API Breaking From e1cc31362bfa07923146b2cd853dc43f5e6f5292 Mon Sep 17 00:00:00 2001 From: Austin Chandra Date: Tue, 15 Nov 2022 13:46:37 -0800 Subject: [PATCH 8/8] Refactor `sdkerrors` to `errorsmod` --- ethereum/eip712/eip712.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ethereum/eip712/eip712.go b/ethereum/eip712/eip712.go index 94204d7d2c..f2549096f7 100644 --- a/ethereum/eip712/eip712.go +++ b/ethereum/eip712/eip712.go @@ -365,7 +365,7 @@ func jsonNameFromTag(tag reflect.StructTag) string { func unpackAny(cdc codectypes.AnyUnpacker, field reflect.Value) (reflect.Type, reflect.Value, error) { any, ok := field.Interface().(*codectypes.Any) if !ok { - return nil, reflect.Value{}, sdkerrors.Wrapf(sdkerrors.ErrPackAny, "%T", field.Interface()) + return nil, reflect.Value{}, errorsmod.Wrapf(errortypes.ErrPackAny, "%T", field.Interface()) } anyWrapper := &cosmosAnyWrapper{ @@ -373,7 +373,7 @@ func unpackAny(cdc codectypes.AnyUnpacker, field reflect.Value) (reflect.Type, r } if err := cdc.UnpackAny(any, &anyWrapper.Value); err != nil { - return nil, reflect.Value{}, sdkerrors.Wrap(err, "failed to unpack Any in msg struct") + return nil, reflect.Value{}, errorsmod.Wrap(err, "failed to unpack Any in msg struct") } fieldType := reflect.TypeOf(anyWrapper)