Skip to content

Commit

Permalink
chore(evm): Augment the Wasm msg handler so that wasm contracts canno…
Browse files Browse the repository at this point in the history
…t send MsgEthereumTx
  • Loading branch information
Unique-Divine committed Jan 9, 2025
1 parent f7a7007 commit 5ce2608
Show file tree
Hide file tree
Showing 7 changed files with 218 additions and 11 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ JSON encoding for the `EIP55Addr` struct was not following the Go conventions an
needed to include double quotes around the hexadecimal string.
- [#2156](https://github.com/NibiruChain/nibiru/pull/2156) - test(evm-e2e): add E2E test using the Nibiru Oracle's ChainLink impl
- [#2157](https://github.com/NibiruChain/nibiru/pull/2157) - fix(evm): Fix unit inconsistency related to AuthInfo.Fee and txData.Fee using effective fee
- [#2159](https://github.com/NibiruChain/nibiru/pull/2159) - chore(evm): Augment the Wasm msg handler so that wasm contracts cannot send MsgEthereumTx

#### Nibiru EVM | Before Audit 2 - 2024-12-06

Expand Down
7 changes: 6 additions & 1 deletion app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,11 @@ func init() {
}

// GetWasmOpts build wasm options
func GetWasmOpts(nibiru NibiruApp, appOpts servertypes.AppOptions) []wasmkeeper.Option {
func GetWasmOpts(
nibiru NibiruApp,
appOpts servertypes.AppOptions,
wasmMsgHandlerArgs wasmext.MsgHandlerArgs,
) []wasmkeeper.Option {
var wasmOpts []wasmkeeper.Option
if cast.ToBool(appOpts.Get("telemetry.enabled")) {
wasmOpts = append(wasmOpts, wasmkeeper.WithVMCacheMetrics(prometheus.DefaultRegisterer))
Expand All @@ -129,6 +133,7 @@ func GetWasmOpts(nibiru NibiruApp, appOpts servertypes.AppOptions) []wasmkeeper.
return append(wasmOpts, wasmext.NibiruWasmOptions(
nibiru.GRPCQueryRouter(),
nibiru.appCodec,
wasmMsgHandlerArgs,
)...)
}

Expand Down
23 changes: 17 additions & 6 deletions app/keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ import (
// Nibiru Custom Modules

"github.com/NibiruChain/nibiru/v2/app/keepers"
"github.com/NibiruChain/nibiru/v2/app/wasmext"
"github.com/NibiruChain/nibiru/v2/eth"
"github.com/NibiruChain/nibiru/v2/x/common"
"github.com/NibiruChain/nibiru/v2/x/devgas/v1"
Expand Down Expand Up @@ -465,25 +466,35 @@ func (app *NibiruApp) InitKeepers(
panic(err)
}

wmha := wasmext.MsgHandlerArgs{
Router: app.MsgServiceRouter(),
Ics4Wrapper: app.ibcFeeKeeper,
ChannelKeeper: app.ibcKeeper.ChannelKeeper,
CapabilityKeeper: app.ScopedWasmKeeper,
BankKeeper: app.BankKeeper,
Unpacker: appCodec,
PortSource: app.ibcTransferKeeper,
}
app.WasmMsgHandlerArgs = wmha
app.WasmKeeper = wasmkeeper.NewKeeper(
appCodec,
keys[wasmtypes.StoreKey],
app.AccountKeeper,
app.BankKeeper,
app.StakingKeeper,
distrkeeper.NewQuerier(app.DistrKeeper),
app.ibcFeeKeeper, // ISC4 Wrapper: fee IBC middleware
app.ibcKeeper.ChannelKeeper,
wmha.Ics4Wrapper, // ISC4 Wrapper: fee IBC middleware
wmha.ChannelKeeper,
&app.ibcKeeper.PortKeeper,
app.ScopedWasmKeeper,
app.ibcTransferKeeper,
app.MsgServiceRouter(),
wmha.CapabilityKeeper,
wmha.PortSource,
wmha.Router,
app.GRPCQueryRouter(),
wasmDir,
wasmConfig,
supportedFeatures,
govModuleAddr,
append(GetWasmOpts(*app, appOpts), wasmkeeper.WithWasmEngine(wasmVM))...,
append(GetWasmOpts(*app, appOpts, wmha), wasmkeeper.WithWasmEngine(wasmVM))...,
)

app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithVM(
Expand Down
5 changes: 4 additions & 1 deletion app/keepers/all_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
// ---------------------------------------------------------------
// Nibiru Custom Modules

"github.com/NibiruChain/nibiru/v2/app/wasmext"
devgaskeeper "github.com/NibiruChain/nibiru/v2/x/devgas/v1/keeper"
epochskeeper "github.com/NibiruChain/nibiru/v2/x/epochs/keeper"
evmkeeper "github.com/NibiruChain/nibiru/v2/x/evm/keeper"
Expand Down Expand Up @@ -67,7 +68,9 @@ type PublicKeepers struct {
EvmKeeper *evmkeeper.Keeper

// WASM keepers
WasmKeeper wasmkeeper.Keeper
WasmKeeper wasmkeeper.Keeper
WasmMsgHandlerArgs wasmext.MsgHandlerArgs

ScopedWasmKeeper capabilitykeeper.ScopedKeeper
WasmClientKeeper ibcwasmkeeper.Keeper
}
4 changes: 2 additions & 2 deletions app/wasmext/stargate_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package wasmext_test
import (
"fmt"
"strings"
"testing"

"github.com/cosmos/gogoproto/proto"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -36,7 +35,8 @@ Given only the `PB_MSG.PACKAGE` and the `PB_MSG.NAME` of either the query
request or response, we should know the `QueryRequest::Stargate.path`
deterministically.
*/
func TestWasmAcceptedStargateQueries(t *testing.T) {
func (s *Suite) TestWasmAcceptedStargateQueries() {
t := s.T()
t.Log("stargateQueryPaths: Add nibiru query paths from GRPC service descriptions")
queryServiceDescriptions := []grpc.ServiceDesc{
epochs.GrpcQueryServiceDesc(),
Expand Down
116 changes: 115 additions & 1 deletion app/wasmext/wasm.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
package wasmext

import (
"github.com/NibiruChain/nibiru/v2/x/evm"

"cosmossdk.io/errors"
wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
wasm "github.com/CosmWasm/wasmd/x/wasm/types"
wasmvmtypes "github.com/CosmWasm/wasmvm/types"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/codec"
sdkcodec "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)

// NibiruWasmOptions: Wasm Options are extension points to instantiate the Wasm
// keeper with non-default values
func NibiruWasmOptions(
grpcQueryRouter *baseapp.GRPCQueryRouter,
appCodec codec.Codec,
msgHandlerArgs MsgHandlerArgs,
) []wasmkeeper.Option {
wasmQueryOption := wasmkeeper.WithQueryPlugins(&wasmkeeper.QueryPlugins{
Stargate: wasmkeeper.AcceptListStargateQuerier(
Expand All @@ -20,5 +29,110 @@ func NibiruWasmOptions(
),
})

return []wasmkeeper.Option{wasmQueryOption}
wasmMsgHandlerOption := wasmkeeper.WithMessageHandler(WasmMessageHandler(msgHandlerArgs))

return []wasmkeeper.Option{
wasmQueryOption,
wasmMsgHandlerOption,
}
}

func (h SDKMessageHandler) handleSdkMessage(ctx sdk.Context, contractAddr sdk.Address, msg sdk.Msg) (*sdk.Result, error) {
if err := msg.ValidateBasic(); err != nil {
return nil, err
}

Check warning on line 43 in app/wasmext/wasm.go

View check run for this annotation

Codecov / codecov/patch

app/wasmext/wasm.go#L42-L43

Added lines #L42 - L43 were not covered by tests

// make sure this account can send it
for _, acct := range msg.GetSigners() {
if !acct.Equals(contractAddr) {
return nil, errors.Wrap(sdkerrors.ErrUnauthorized, "contract doesn't have permission")
}

Check warning on line 49 in app/wasmext/wasm.go

View check run for this annotation

Codecov / codecov/patch

app/wasmext/wasm.go#L48-L49

Added lines #L48 - L49 were not covered by tests
}

msgTypeUrl := sdk.MsgTypeURL(msg)
if msgTypeUrl == sdk.MsgTypeURL(new(evm.MsgEthereumTx)) {
return nil, errors.Wrap(sdkerrors.ErrUnauthorized, "Wasm VM to EVM call pattern is not yet supported")
}

// find the handler and execute it
if handler := h.router.Handler(msg); handler != nil {
// ADR 031 request type routing
msgResult, err := handler(ctx, msg)
return msgResult, err
}

Check warning on line 62 in app/wasmext/wasm.go

View check run for this annotation

Codecov / codecov/patch

app/wasmext/wasm.go#L58-L62

Added lines #L58 - L62 were not covered by tests
// legacy sdk.Msg routing
// Assuming that the app developer has migrated all their Msgs to
// proto messages and has registered all `Msg services`, then this
// path should never be called, because all those Msgs should be
// registered within the `msgServiceRouter` already.
return nil, errors.Wrapf(sdkerrors.ErrUnknownRequest, "can't route message %+v", msg)

Check warning on line 68 in app/wasmext/wasm.go

View check run for this annotation

Codecov / codecov/patch

app/wasmext/wasm.go#L68

Added line #L68 was not covered by tests
}

type MsgHandlerArgs struct {
Router MessageRouter
Ics4Wrapper wasm.ICS4Wrapper
ChannelKeeper wasm.ChannelKeeper
CapabilityKeeper wasm.CapabilityKeeper
BankKeeper wasm.Burner
Unpacker sdkcodec.AnyUnpacker
PortSource wasm.ICS20TransferPortSource
}

// SDKMessageHandler can handles messages that can be encoded into sdk.Message types and routed.
type SDKMessageHandler struct {
router MessageRouter
encoders msgEncoder
}

// MessageRouter ADR 031 request type routing
type MessageRouter interface {
Handler(msg sdk.Msg) baseapp.MsgServiceHandler
}

// msgEncoder is an extension point to customize encodings
type msgEncoder interface {
// Encode converts wasmvm message to n cosmos message types
Encode(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) ([]sdk.Msg, error)
}

// WasmMessageHandler is a replacement constructor for
// [wasmkeeper.NewDefaultMessageHandler] inside of [wasmkeeper.NewKeeper].
func WasmMessageHandler(
args MsgHandlerArgs,
) wasmkeeper.Messenger {
encoders := wasmkeeper.DefaultEncoders(args.Unpacker, args.PortSource)
return wasmkeeper.NewMessageHandlerChain(
NewSDKMessageHandler(args.Router, encoders),
wasmkeeper.NewIBCRawPacketHandler(args.Ics4Wrapper, args.ChannelKeeper, args.CapabilityKeeper),
wasmkeeper.NewBurnCoinMessageHandler(args.BankKeeper),
)
}

func NewSDKMessageHandler(router MessageRouter, encoders msgEncoder) SDKMessageHandler {
return SDKMessageHandler{
router: router,
encoders: encoders,
}
}

func (h SDKMessageHandler) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) {
sdkMsgs, err := h.encoders.Encode(ctx, contractAddr, contractIBCPortID, msg)
if err != nil {
return nil, nil, err
}

Check warning on line 122 in app/wasmext/wasm.go

View check run for this annotation

Codecov / codecov/patch

app/wasmext/wasm.go#L121-L122

Added lines #L121 - L122 were not covered by tests
for _, sdkMsg := range sdkMsgs {
res, err := h.handleSdkMessage(ctx, contractAddr, sdkMsg)
if err != nil {
return nil, nil, err
}
// append data
data = append(data, res.Data)
// append events
sdkEvents := make([]sdk.Event, len(res.Events))
for i := range res.Events {
sdkEvents[i] = sdk.Event(res.Events[i])
}
events = append(events, sdkEvents...)

Check warning on line 135 in app/wasmext/wasm.go

View check run for this annotation

Codecov / codecov/patch

app/wasmext/wasm.go#L129-L135

Added lines #L129 - L135 were not covered by tests
}
return

Check warning on line 137 in app/wasmext/wasm.go

View check run for this annotation

Codecov / codecov/patch

app/wasmext/wasm.go#L137

Added line #L137 was not covered by tests
}
73 changes: 73 additions & 0 deletions app/wasmext/wasmext_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package wasmext_test

import (
"math/big"
"testing"

wasmvm "github.com/CosmWasm/wasmvm/types"
sdkcodec "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/suite"

"github.com/NibiruChain/nibiru/v2/app/wasmext"
"github.com/NibiruChain/nibiru/v2/x/evm"
"github.com/NibiruChain/nibiru/v2/x/evm/evmtest"
)

type Suite struct {
suite.Suite
}

func TestWasmExtSuite(t *testing.T) {
suite.Run(t, new(Suite))
}

// WasmVM to EVM call pattern is not yet supported. This test verifies the
// Nibiru's [wasmkeeper.Option] function as expected.
func (s *Suite) TestEvmFilter() {
deps := evmtest.NewTestDeps()
// wk := wasmkeeper.NewDefaultPermissionKeeper(deps.App.WasmKeeper)
wasmMsgHandler := wasmext.WasmMessageHandler(deps.App.WasmMsgHandlerArgs)

s.T().Log("Create a valid Ethereum tx msg")

to := evmtest.NewEthPrivAcc()
ethTxMsg, err := evmtest.TxTransferWei{
Deps: &deps,
To: to.EthAddr,
AmountWei: evm.NativeToWei(big.NewInt(420)),
}.Build()
s.NoError(err)

s.T().Log("Validate Eth tx msg proto encoding as wasmvm.StargateMsg")
wasmContractAddr := deps.Sender.NibiruAddr
protoValueBz, err := deps.EncCfg.Codec.Marshal(ethTxMsg)
s.Require().NoError(err, "expect ethTxMsg to proto marshal", protoValueBz)

_, ok := deps.EncCfg.Codec.(sdkcodec.AnyUnpacker)
s.Require().True(ok, "codec must be an AnyUnpacker")

pbAny, err := sdkcodec.NewAnyWithValue(ethTxMsg)
s.NoError(err)
pbAnyBz, err := pbAny.Marshal()
s.NoError(err, pbAnyBz)

var sdkMsg sdk.Msg
err = deps.EncCfg.Codec.UnpackAny(pbAny, &sdkMsg)
s.Require().NoError(err)
s.Equal("/eth.evm.v1.MsgEthereumTx", sdk.MsgTypeURL(sdkMsg))

s.T().Log("Dispatch the Eth tx msg from Wasm (unsuccessfully)")
_, _, err = wasmMsgHandler.DispatchMsg(
deps.Ctx,
wasmContractAddr,
"ibcport-unused",
wasmvm.CosmosMsg{
Stargate: &wasmvm.StargateMsg{
TypeURL: sdk.MsgTypeURL(ethTxMsg),
Value: protoValueBz,
},
},
)
s.Require().ErrorContains(err, "Wasm VM to EVM call pattern is not yet supported")
}

0 comments on commit 5ce2608

Please sign in to comment.