From 6dc7fa9accd20668939540c21e9b118529d89ec6 Mon Sep 17 00:00:00 2001 From: DimitrisJim Date: Thu, 4 Apr 2024 01:17:28 +0300 Subject: [PATCH 01/24] Move functionality from client state methods to light client module methods. - Yank all statements and delete functions as needed. - Make any required functions from types/ public. - Add the vm in the 08-wasm keeper. - Update documentation to include documentation of deleted functions. --- docs/architecture/adr-027-ibc-wasm.md | 2 +- .../light-clients/08-wasm/keeper/keeper.go | 8 +- .../light-clients/08-wasm/keeper/keeper_vm.go | 1 + .../08-wasm/light_client_module.go | 254 ++++++++++++++++-- .../08-wasm/types/client_state.go | 170 ------------ .../08-wasm/types/export_test.go | 17 +- .../08-wasm/types/misbehaviour_handle.go | 30 --- .../08-wasm/types/proposal_handle.go | 42 --- .../08-wasm/types/querier_test.go | 13 +- modules/light-clients/08-wasm/types/store.go | 2 +- modules/light-clients/08-wasm/types/update.go | 75 ------ .../light-clients/08-wasm/types/upgrade.go | 48 ---- modules/light-clients/08-wasm/types/vm.go | 16 +- 13 files changed, 258 insertions(+), 420 deletions(-) delete mode 100644 modules/light-clients/08-wasm/types/misbehaviour_handle.go delete mode 100644 modules/light-clients/08-wasm/types/proposal_handle.go delete mode 100644 modules/light-clients/08-wasm/types/update.go delete mode 100644 modules/light-clients/08-wasm/types/upgrade.go diff --git a/docs/architecture/adr-027-ibc-wasm.md b/docs/architecture/adr-027-ibc-wasm.md index 1a2a1e8a8a9..3c81796d675 100644 --- a/docs/architecture/adr-027-ibc-wasm.md +++ b/docs/architecture/adr-027-ibc-wasm.md @@ -139,7 +139,7 @@ func (cs ClientState) VerifyClientMessage( payload := QueryMsg{ VerifyClientMessage: &VerifyClientMessageMsg{ClientMessage: clientMessage.Data}, } - _, err := wasmQuery[EmptyResult](ctx, clientStore, &cs, payload) + _, err := WasmQuery[EmptyResult](ctx, clientStore, &cs, payload) return err } ``` diff --git a/modules/light-clients/08-wasm/keeper/keeper.go b/modules/light-clients/08-wasm/keeper/keeper.go index 9f323cf3a8f..20c32f644a6 100644 --- a/modules/light-clients/08-wasm/keeper/keeper.go +++ b/modules/light-clients/08-wasm/keeper/keeper.go @@ -21,7 +21,8 @@ import ( type Keeper struct { // implements gRPC QueryServer interface types.QueryServer - + // vm contains an implementation of the WasmEngine that's invoked. + vm ibcwasm.WasmEngine cdc codec.BinaryCodec storeService store.KVStoreService @@ -41,6 +42,11 @@ func (k Keeper) GetAuthority() string { return k.authority } +// GetVM returns the keeper's vm engine. +func (k Keeper) GetVM() ibcwasm.WasmEngine { + return k.vm +} + func (Keeper) storeWasmCode(ctx sdk.Context, code []byte, storeFn func(code wasmvm.WasmCode, gasLimit uint64) (wasmvm.Checksum, uint64, error)) ([]byte, error) { var err error if types.IsGzip(code) { diff --git a/modules/light-clients/08-wasm/keeper/keeper_vm.go b/modules/light-clients/08-wasm/keeper/keeper_vm.go index 6173ceb06b8..fea0ab3defd 100644 --- a/modules/light-clients/08-wasm/keeper/keeper_vm.go +++ b/modules/light-clients/08-wasm/keeper/keeper_vm.go @@ -47,6 +47,7 @@ func NewKeeperWithVM( keeper := &Keeper{ cdc: cdc, + vm: vm, storeService: storeService, clientKeeper: clientKeeper, authority: authority, diff --git a/modules/light-clients/08-wasm/light_client_module.go b/modules/light-clients/08-wasm/light_client_module.go index 0b1376df4cd..4d43b86af65 100644 --- a/modules/light-clients/08-wasm/light_client_module.go +++ b/modules/light-clients/08-wasm/light_client_module.go @@ -1,10 +1,16 @@ package wasm import ( + "bytes" + "encoding/hex" + "fmt" + errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" + commitmenttypes "github.com/cosmos/ibc-go/v8/modules/core/23-commitment/types" + wasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" @@ -34,8 +40,9 @@ func (l *LightClientModule) RegisterStoreProvider(storeProvider exported.ClientS l.storeProvider = storeProvider } -// Initialize unmarshals the provided client and consensus states and performs basic validation. It calls into the -// clientState.Initialize method. +// Initialize unmarshals the provided client and consensus states and performs basic validation. It sets the client +// state and consensus state in the client store. +// It also initializes the wasm contract for the client. // // CONTRACT: clientID is validated in 02-client router, thus clientID is assumed here to have the format 08-wasm-{n}. func (l LightClientModule) Initialize(ctx sdk.Context, clientID string, clientStateBz, consensusStateBz []byte) error { @@ -60,10 +67,25 @@ func (l LightClientModule) Initialize(ctx sdk.Context, clientID string, clientSt clientStore := l.storeProvider.ClientStore(ctx, clientID) cdc := l.keeper.Codec() - return clientState.Initialize(ctx, cdc, clientStore, &consensusState) + // Do not allow initialization of a client with a checksum that hasn't been previously stored via storeWasmCode. + if !types.HasChecksum(ctx, clientState.Checksum) { + return errorsmod.Wrapf(types.ErrInvalidChecksum, "checksum (%s) has not been previously stored", hex.EncodeToString(clientState.Checksum)) + } + + payload := types.InstantiateMessage{ + ClientState: clientState.Data, + ConsensusState: consensusState.Data, + Checksum: clientState.Checksum, + } + + return types.WasmInstantiate(ctx, cdc, clientStore, &clientState, payload) } -// VerifyClientMessage obtains the client state associated with the client identifier and calls into the clientState.VerifyClientMessage method. +// VerifyClientMessage obtains the client state associated with the client identifier, it then must verify the ClientMessage. +// A ClientMessage could be a Header, Misbehaviour, or batch update. +// It must handle each type of ClientMessage appropriately. Calls to CheckForMisbehaviour, UpdateState, and UpdateStateOnMisbehaviour +// will assume that the content of the ClientMessage has been verified and can be trusted. An error should be returned +// if the ClientMessage fails to verify. // // CONTRACT: clientID is validated in 02-client router, thus clientID is assumed here to have the format 08-wasm-{n}. func (l LightClientModule) VerifyClientMessage(ctx sdk.Context, clientID string, clientMsg exported.ClientMessage) error { @@ -75,10 +97,20 @@ func (l LightClientModule) VerifyClientMessage(ctx sdk.Context, clientID string, return errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID) } - return clientState.VerifyClientMessage(ctx, l.keeper.Codec(), clientStore, clientMsg) + clientMessage, ok := clientMsg.(*types.ClientMessage) + if !ok { + return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected type: %T, got: %T", &types.ClientMessage{}, clientMsg) + } + + payload := types.QueryMsg{ + VerifyClientMessage: &types.VerifyClientMessageMsg{ClientMessage: clientMessage.Data}, + } + _, err := types.WasmQuery[types.EmptyResult](ctx, clientStore, clientState, payload) + return err } -// CheckForMisbehaviour obtains the client state associated with the client identifier and calls into the clientState.CheckForMisbehaviour method. +// CheckForMisbehaviour obtains the client state associated with the client identifier, it detects misbehaviour in a submitted Header +// message and verifies the correctness of a submitted Misbehaviour ClientMessage. // // CONTRACT: clientID is validated in 02-client router, thus clientID is assumed here to have the format 08-wasm-{n}. func (l LightClientModule) CheckForMisbehaviour(ctx sdk.Context, clientID string, clientMsg exported.ClientMessage) bool { @@ -90,10 +122,26 @@ func (l LightClientModule) CheckForMisbehaviour(ctx sdk.Context, clientID string panic(errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID)) } - return clientState.CheckForMisbehaviour(ctx, cdc, clientStore, clientMsg) + clientMessage, ok := clientMsg.(*types.ClientMessage) + if !ok { + return false + } + + payload := types.QueryMsg{ + CheckForMisbehaviour: &types.CheckForMisbehaviourMsg{ClientMessage: clientMessage.Data}, + } + + result, err := types.WasmQuery[types.CheckForMisbehaviourResult](ctx, clientStore, clientState, payload) + if err != nil { + return false + } + + return result.FoundMisbehaviour } -// UpdateStateOnMisbehaviour obtains the client state associated with the client identifier and calls into the clientState.UpdateStateOnMisbehaviour method. +// UpdateStateOnMisbehaviour obtains the client state associated with the client identifier performs appropriate state changes on +// a client state given that misbehaviour has been detected and verified. +// Client state is updated in the store by the contract. // // CONTRACT: clientID is validated in 02-client router, thus clientID is assumed here to have the format 08-wasm-{n}. func (l LightClientModule) UpdateStateOnMisbehaviour(ctx sdk.Context, clientID string, clientMsg exported.ClientMessage) { @@ -105,10 +153,23 @@ func (l LightClientModule) UpdateStateOnMisbehaviour(ctx sdk.Context, clientID s panic(errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID)) } - clientState.UpdateStateOnMisbehaviour(ctx, cdc, clientStore, clientMsg) + clientMessage, ok := clientMsg.(*types.ClientMessage) + if !ok { + panic(fmt.Errorf("expected type %T, got %T", &types.ClientMessage{}, clientMsg)) + } + + payload := types.SudoMsg{ + UpdateStateOnMisbehaviour: &types.UpdateStateOnMisbehaviourMsg{ClientMessage: clientMessage.Data}, + } + + _, err := types.WasmSudo[types.EmptyResult](ctx, cdc, clientStore, clientState, payload) + if err != nil { + panic(err) + } } -// UpdateState obtains the client state associated with the client identifier and calls into the clientState.UpdateState method. +// UpdateState obtains the client state associated with the client identifier and calls into the appropriate +// contract endpoint. Client state and new consensus states are updated in the store by the contract. // // CONTRACT: clientID is validated in 02-client router, thus clientID is assumed here to have the format 08-wasm-{n}. func (l LightClientModule) UpdateState(ctx sdk.Context, clientID string, clientMsg exported.ClientMessage) []exported.Height { @@ -120,10 +181,32 @@ func (l LightClientModule) UpdateState(ctx sdk.Context, clientID string, clientM panic(errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID)) } - return clientState.UpdateState(ctx, cdc, clientStore, clientMsg) + clientMessage, ok := clientMsg.(*types.ClientMessage) + if !ok { + panic(fmt.Errorf("expected type %T, got %T", &types.ClientMessage{}, clientMsg)) + } + + payload := types.SudoMsg{ + UpdateState: &types.UpdateStateMsg{ClientMessage: clientMessage.Data}, + } + + result, err := types.WasmSudo[types.UpdateStateResult](ctx, cdc, clientStore, clientState, payload) + if err != nil { + panic(err) + } + + heights := []exported.Height{} + for _, height := range result.Heights { + heights = append(heights, height) + } + + return heights } -// VerifyMembership obtains the client state associated with the client identifier and calls into the clientState.VerifyMembership method. +// VerifyMembership obtains the client state associated with the client identifier and calls into the appropriate contract endpoint. +// VerifyMembership is a generic proof verification method which verifies a proof of the existence of a value at a given CommitmentPath at the specified height. +// The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 24). +// If a zero proof height is passed in, it will fail to retrieve the associated consensus state. // // CONTRACT: clientID is validated in 02-client router, thus clientID is assumed here to have the format 08-wasm-{n}. func (l LightClientModule) VerifyMembership( @@ -144,10 +227,41 @@ func (l LightClientModule) VerifyMembership( return errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID) } - return clientState.VerifyMembership(ctx, clientStore, cdc, height, delayTimePeriod, delayBlockPeriod, proof, path, value) + proofHeight, ok := height.(clienttypes.Height) + if !ok { + return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected %T, got %T", clienttypes.Height{}, height) + } + + if clientState.LatestHeight.LT(height) { + return errorsmod.Wrapf( + ibcerrors.ErrInvalidHeight, + "client state height < proof height (%d < %d), please ensure the client has been updated", clientState.LatestHeight, height, + ) + } + + merklePath, ok := path.(commitmenttypes.MerklePath) + if !ok { + return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected %T, got %T", commitmenttypes.MerklePath{}, path) + } + + payload := types.SudoMsg{ + VerifyMembership: &types.VerifyMembershipMsg{ + Height: proofHeight, + DelayTimePeriod: delayTimePeriod, + DelayBlockPeriod: delayBlockPeriod, + Proof: proof, + Path: merklePath, + Value: value, + }, + } + _, err := types.WasmSudo[types.EmptyResult](ctx, cdc, clientStore, clientState, payload) + return err } -// VerifyNonMembership obtains the client state associated with the client identifier and calls into the clientState.VerifyNonMembership method. +// VerifyNonMembership obtains the client state associated with the client identifier and calls into the appropriate contract endpoint. +// VerifyNonMembership is a generic proof verification method which verifies the absence of a given CommitmentPath at a specified height. +// The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 24). +// If a zero proof height is passed in, it will fail to retrieve the associated consensus state. // // CONTRACT: clientID is validated in 02-client router, thus clientID is assumed here to have the format 08-wasm-{n}. func (l LightClientModule) VerifyNonMembership( @@ -167,10 +281,46 @@ func (l LightClientModule) VerifyNonMembership( return errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID) } - return clientState.VerifyNonMembership(ctx, clientStore, cdc, height, delayTimePeriod, delayBlockPeriod, proof, path) + proofHeight, ok := height.(clienttypes.Height) + if !ok { + return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected %T, got %T", clienttypes.Height{}, height) + } + + if clientState.LatestHeight.LT(height) { + return errorsmod.Wrapf( + ibcerrors.ErrInvalidHeight, + "client state height < proof height (%d < %d), please ensure the client has been updated", clientState.LatestHeight, height, + ) + } + + merklePath, ok := path.(commitmenttypes.MerklePath) + if !ok { + return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected %T, got %T", commitmenttypes.MerklePath{}, path) + } + + payload := types.SudoMsg{ + VerifyNonMembership: &types.VerifyNonMembershipMsg{ + Height: proofHeight, + DelayTimePeriod: delayTimePeriod, + DelayBlockPeriod: delayBlockPeriod, + Proof: proof, + Path: merklePath, + }, + } + _, err := types.WasmSudo[types.EmptyResult](ctx, cdc, clientStore, clientState, payload) + return err } -// Status obtains the client state associated with the client identifier and calls into the clientState.Status method. +// Status obtains the client state associated with the client identifier and calls into the appropriate contract endpoint. +// It returns the status of the wasm client. +// The client may be: +// - Active: frozen height is zero and client is not expired +// - Frozen: frozen height is not zero +// - Expired: the latest consensus state timestamp + trusting period <= current time +// - Unauthorized: the client type is not registered as an allowed client type +// +// A frozen client will become expired, so the Frozen status +// has higher precedence. // // CONTRACT: clientID is validated in 02-client router, thus clientID is assumed here to have the format 08-wasm-{n}. func (l LightClientModule) Status(ctx sdk.Context, clientID string) exported.Status { @@ -182,7 +332,18 @@ func (l LightClientModule) Status(ctx sdk.Context, clientID string) exported.Sta return exported.Unknown } - return clientState.Status(ctx, clientStore, cdc) + // Return unauthorized if the checksum hasn't been previously stored via storeWasmCode. + if !types.HasChecksum(ctx, clientState.Checksum) { + return exported.Unauthorized + } + + payload := types.QueryMsg{Status: &types.StatusMsg{}} + result, err := types.WasmQuery[types.StatusResult](ctx, clientStore, clientState, payload) + if err != nil { + return exported.Unknown + } + + return exported.Status(result.Status) } // LatestHeight returns the latest height for the client state for the given client identifier. @@ -201,7 +362,8 @@ func (l LightClientModule) LatestHeight(ctx sdk.Context, clientID string) export return clientState.LatestHeight } -// TimestampAtHeight obtains the client state associated with the client identifier and calls into the clientState.GetTimestampAtHeight method. +// TimestampAtHeight obtains the client state associated with the client identifier and calls into the appropriate contract endpoint. +// It returns the timestamp in nanoseconds of the consensus state at the given height. // // CONTRACT: clientID is validated in 02-client router, thus clientID is assumed here to have the format 08-wasm-{n}. func (l LightClientModule) TimestampAtHeight(ctx sdk.Context, clientID string, height exported.Height) (uint64, error) { @@ -213,11 +375,29 @@ func (l LightClientModule) TimestampAtHeight(ctx sdk.Context, clientID string, h return 0, errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID) } - return clientState.GetTimestampAtHeight(ctx, clientStore, cdc, height) + timestampHeight, ok := height.(clienttypes.Height) + if !ok { + return 0, errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected %T, got %T", clienttypes.Height{}, height) + } + + payload := types.QueryMsg{ + TimestampAtHeight: &types.TimestampAtHeightMsg{ + Height: timestampHeight, + }, + } + + result, err := types.WasmQuery[types.TimestampAtHeightResult](ctx, clientStore, clientState, payload) + if err != nil { + return 0, errorsmod.Wrapf(err, "height (%s)", height) + } + + return result.Timestamp, nil } // RecoverClient asserts that the substitute client is a wasm client. It obtains the client state associated with the -// subject client and calls into the subjectClientState.CheckSubstituteAndUpdateState method. +// subject client and calls into the appropriate contract endpoint. +// It will verify that a substitute client state is valid and update the subject client state. +// Note that this method is used only for recovery and will not allow changes to the checksum. // // CONTRACT: clientID is validated in 02-client router, thus clientID is assumed here to have the format 08-wasm-{n}. func (l LightClientModule) RecoverClient(ctx sdk.Context, clientID, substituteClientID string) error { @@ -244,12 +424,28 @@ func (l LightClientModule) RecoverClient(ctx sdk.Context, clientID, substituteCl return errorsmod.Wrap(clienttypes.ErrClientNotFound, substituteClientID) } - return clientState.CheckSubstituteAndUpdateState(ctx, cdc, clientStore, substituteClientStore, substituteClient) + substituteClientState := substituteClient + + // check that checksums of subject client state and substitute client state match + // changing the checksum is only allowed through the migrate contract RPC endpoint + if !bytes.Equal(clientState.Checksum, substituteClientState.Checksum) { + return errorsmod.Wrapf(clienttypes.ErrInvalidClient, "expected checksums to be equal: expected %s, got %s", hex.EncodeToString(clientState.Checksum), hex.EncodeToString(substituteClientState.Checksum)) + } + + store := types.NewMigrateClientWrappedStore(clientStore, substituteClientStore) + + payload := types.SudoMsg{ + MigrateClientStore: &types.MigrateClientStoreMsg{}, + } + + _, err = types.WasmSudo[types.EmptyResult](ctx, cdc, store, clientState, payload) + return err + // return clientState.CheckSubstituteAndUpdateState(ctx, cdc, clientStore, substituteClientStore, substituteClient) } -// VerifyUpgradeAndUpdateState obtains the client state associated with the client identifier and calls into the clientState.VerifyUpgradeAndUpdateState method. +// VerifyUpgradeAndUpdateState obtains the client state associated with the client identifier and calls into the appropriate contract endpoint. // The new client and consensus states will be unmarshaled and an error is returned if the new client state is not at a height greater -// than the existing client. +// than the existing client. On a successful verification, it expects the contract to update the new client state, consensus state, and any other client metadata. // // CONTRACT: clientID is validated in 02-client router, thus clientID is assumed here to have the format 08-wasm-{n}. func (l LightClientModule) VerifyUpgradeAndUpdateState( @@ -284,5 +480,15 @@ func (l LightClientModule) VerifyUpgradeAndUpdateState( return errorsmod.Wrapf(ibcerrors.ErrInvalidHeight, "upgraded client height %s must be at greater than current client height %s", newClientState.LatestHeight, lastHeight) } - return clientState.VerifyUpgradeAndUpdateState(ctx, cdc, clientStore, &newClientState, &newConsensusState, upgradeClientProof, upgradeConsensusStateProof) + payload := types.SudoMsg{ + VerifyUpgradeAndUpdateState: &types.VerifyUpgradeAndUpdateStateMsg{ + UpgradeClientState: newClientState.Data, + UpgradeConsensusState: newConsensusState.Data, + ProofUpgradeClient: upgradeClientProof, + ProofUpgradeConsensusState: upgradeConsensusStateProof, + }, + } + + _, err := types.WasmSudo[types.EmptyResult](ctx, cdc, clientStore, clientState, payload) + return err } diff --git a/modules/light-clients/08-wasm/types/client_state.go b/modules/light-clients/08-wasm/types/client_state.go index 4ce6fe3e044..1053a7dd0ac 100644 --- a/modules/light-clients/08-wasm/types/client_state.go +++ b/modules/light-clients/08-wasm/types/client_state.go @@ -1,17 +1,9 @@ package types import ( - "encoding/hex" - errorsmod "cosmossdk.io/errors" - storetypes "cosmossdk.io/store/types" - - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" - commitmenttypes "github.com/cosmos/ibc-go/v8/modules/core/23-commitment/types" - ibcerrors "github.com/cosmos/ibc-go/v8/modules/core/errors" "github.com/cosmos/ibc-go/v8/modules/core/exported" ) @@ -43,165 +35,3 @@ func (cs ClientState) Validate() error { return nil } - -// Status returns the status of the wasm client. -// The client may be: -// - Active: frozen height is zero and client is not expired -// - Frozen: frozen height is not zero -// - Expired: the latest consensus state timestamp + trusting period <= current time -// - Unauthorized: the client type is not registered as an allowed client type -// -// A frozen client will become expired, so the Frozen status -// has higher precedence. -func (cs ClientState) Status(ctx sdk.Context, clientStore storetypes.KVStore, _ codec.BinaryCodec) exported.Status { - // Return unauthorized if the checksum hasn't been previously stored via storeWasmCode. - if !HasChecksum(ctx, cs.Checksum) { - return exported.Unauthorized - } - - payload := QueryMsg{Status: &StatusMsg{}} - result, err := wasmQuery[StatusResult](ctx, clientStore, &cs, payload) - if err != nil { - return exported.Unknown - } - - return exported.Status(result.Status) -} - -// GetTimestampAtHeight returns the timestamp in nanoseconds of the consensus state at the given height. -func (cs ClientState) GetTimestampAtHeight( - ctx sdk.Context, - clientStore storetypes.KVStore, - cdc codec.BinaryCodec, - height exported.Height, -) (uint64, error) { - timestampHeight, ok := height.(clienttypes.Height) - if !ok { - return 0, errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected %T, got %T", clienttypes.Height{}, height) - } - - payload := QueryMsg{ - TimestampAtHeight: &TimestampAtHeightMsg{ - Height: timestampHeight, - }, - } - - result, err := wasmQuery[TimestampAtHeightResult](ctx, clientStore, &cs, payload) - if err != nil { - return 0, errorsmod.Wrapf(err, "height (%s)", height) - } - - return result.Timestamp, nil -} - -// Initialize checks that the initial consensus state is an 08-wasm consensus state and -// sets the client state, consensus state in the provided client store. -// It also initializes the wasm contract for the client. -func (cs ClientState) Initialize(ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, state exported.ConsensusState) error { - consensusState, ok := state.(*ConsensusState) - if !ok { - return errorsmod.Wrapf(clienttypes.ErrInvalidConsensus, "invalid initial consensus state. expected type: %T, got: %T", - &ConsensusState{}, state) - } - - // Do not allow initialization of a client with a checksum that hasn't been previously stored via storeWasmCode. - if !HasChecksum(ctx, cs.Checksum) { - return errorsmod.Wrapf(ErrInvalidChecksum, "checksum (%s) has not been previously stored", hex.EncodeToString(cs.Checksum)) - } - - payload := InstantiateMessage{ - ClientState: cs.Data, - ConsensusState: consensusState.Data, - Checksum: cs.Checksum, - } - - return wasmInstantiate(ctx, cdc, clientStore, &cs, payload) -} - -// VerifyMembership is a generic proof verification method which verifies a proof of the existence of a value at a given CommitmentPath at the specified height. -// The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 24). -// If a zero proof height is passed in, it will fail to retrieve the associated consensus state. -func (cs ClientState) VerifyMembership( - ctx sdk.Context, - clientStore storetypes.KVStore, - cdc codec.BinaryCodec, - height exported.Height, - delayTimePeriod uint64, - delayBlockPeriod uint64, - proof []byte, - path exported.Path, - value []byte, -) error { - proofHeight, ok := height.(clienttypes.Height) - if !ok { - return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected %T, got %T", clienttypes.Height{}, height) - } - - if cs.LatestHeight.LT(height) { - return errorsmod.Wrapf( - ibcerrors.ErrInvalidHeight, - "client state height < proof height (%d < %d), please ensure the client has been updated", cs.LatestHeight, height, - ) - } - - merklePath, ok := path.(commitmenttypes.MerklePath) - if !ok { - return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected %T, got %T", commitmenttypes.MerklePath{}, path) - } - - payload := SudoMsg{ - VerifyMembership: &VerifyMembershipMsg{ - Height: proofHeight, - DelayTimePeriod: delayTimePeriod, - DelayBlockPeriod: delayBlockPeriod, - Proof: proof, - Path: merklePath, - Value: value, - }, - } - _, err := wasmSudo[EmptyResult](ctx, cdc, clientStore, &cs, payload) - return err -} - -// VerifyNonMembership is a generic proof verification method which verifies the absence of a given CommitmentPath at a specified height. -// The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 24). -// If a zero proof height is passed in, it will fail to retrieve the associated consensus state. -func (cs ClientState) VerifyNonMembership( - ctx sdk.Context, - clientStore storetypes.KVStore, - cdc codec.BinaryCodec, - height exported.Height, - delayTimePeriod uint64, - delayBlockPeriod uint64, - proof []byte, - path exported.Path, -) error { - proofHeight, ok := height.(clienttypes.Height) - if !ok { - return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected %T, got %T", clienttypes.Height{}, height) - } - - if cs.LatestHeight.LT(height) { - return errorsmod.Wrapf( - ibcerrors.ErrInvalidHeight, - "client state height < proof height (%d < %d), please ensure the client has been updated", cs.LatestHeight, height, - ) - } - - merklePath, ok := path.(commitmenttypes.MerklePath) - if !ok { - return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected %T, got %T", commitmenttypes.MerklePath{}, path) - } - - payload := SudoMsg{ - VerifyNonMembership: &VerifyNonMembershipMsg{ - Height: proofHeight, - DelayTimePeriod: delayTimePeriod, - DelayBlockPeriod: delayBlockPeriod, - Proof: proof, - Path: merklePath, - }, - } - _, err := wasmSudo[EmptyResult](ctx, cdc, clientStore, &cs, payload) - return err -} diff --git a/modules/light-clients/08-wasm/types/export_test.go b/modules/light-clients/08-wasm/types/export_test.go index e8c93990a18..ad2d059293c 100644 --- a/modules/light-clients/08-wasm/types/export_test.go +++ b/modules/light-clients/08-wasm/types/export_test.go @@ -28,7 +28,7 @@ func GetClientID(clientStore storetypes.KVStore) (string, error) { // //nolint:revive // Returning unexported type for testing purposes. func NewMigrateProposalWrappedStore(subjectStore, substituteStore storetypes.KVStore) migrateClientWrappedStore { - return newMigrateClientWrappedStore(subjectStore, substituteStore) + return NewMigrateClientWrappedStore(subjectStore, substituteStore) } // GetStore is a wrapper around getStore to allow the function to be directly called in tests. @@ -41,21 +41,6 @@ func SplitPrefix(key []byte) ([]byte, []byte) { return splitPrefix(key) } -// WasmQuery wraps wasmQuery and is used solely for testing. -func WasmQuery[T ContractResult](ctx sdk.Context, clientStore storetypes.KVStore, cs *ClientState, payload QueryMsg) (T, error) { - return wasmQuery[T](ctx, clientStore, cs, payload) -} - -// WasmSudo wraps wasmCall and is used solely for testing. -func WasmSudo[T ContractResult](ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, cs *ClientState, payload SudoMsg) (T, error) { - return wasmSudo[T](ctx, cdc, clientStore, cs, payload) -} - -// WasmInstantiate wraps wasmInstantiate and is used solely for testing. -func WasmInstantiate(ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, cs *ClientState, payload InstantiateMessage) error { - return wasmInstantiate(ctx, cdc, clientStore, cs, payload) -} - // WasmMigrate wraps wasmMigrate and is used solely for testing. func WasmMigrate(ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, cs *ClientState, clientID string, payload []byte) error { return wasmMigrate(ctx, cdc, clientStore, cs, clientID, payload) diff --git a/modules/light-clients/08-wasm/types/misbehaviour_handle.go b/modules/light-clients/08-wasm/types/misbehaviour_handle.go deleted file mode 100644 index 3923287d523..00000000000 --- a/modules/light-clients/08-wasm/types/misbehaviour_handle.go +++ /dev/null @@ -1,30 +0,0 @@ -package types - -import ( - storetypes "cosmossdk.io/store/types" - - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/cosmos/ibc-go/v8/modules/core/exported" -) - -// CheckForMisbehaviour detects misbehaviour in a submitted Header message and verifies -// the correctness of a submitted Misbehaviour ClientMessage -func (cs ClientState) CheckForMisbehaviour(ctx sdk.Context, _ codec.BinaryCodec, clientStore storetypes.KVStore, clientMsg exported.ClientMessage) bool { - clientMessage, ok := clientMsg.(*ClientMessage) - if !ok { - return false - } - - payload := QueryMsg{ - CheckForMisbehaviour: &CheckForMisbehaviourMsg{ClientMessage: clientMessage.Data}, - } - - result, err := wasmQuery[CheckForMisbehaviourResult](ctx, clientStore, &cs, payload) - if err != nil { - return false - } - - return result.FoundMisbehaviour -} diff --git a/modules/light-clients/08-wasm/types/proposal_handle.go b/modules/light-clients/08-wasm/types/proposal_handle.go deleted file mode 100644 index 73fed826c1f..00000000000 --- a/modules/light-clients/08-wasm/types/proposal_handle.go +++ /dev/null @@ -1,42 +0,0 @@ -package types - -import ( - "bytes" - "encoding/hex" - - errorsmod "cosmossdk.io/errors" - storetypes "cosmossdk.io/store/types" - - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - - clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" - "github.com/cosmos/ibc-go/v8/modules/core/exported" -) - -// CheckSubstituteAndUpdateState will verify that a substitute client state is valid and update the subject client state. -// Note that this method is used only for recovery and will not allow changes to the checksum. -func (cs ClientState) CheckSubstituteAndUpdateState(ctx sdk.Context, cdc codec.BinaryCodec, subjectClientStore, substituteClientStore storetypes.KVStore, substituteClient exported.ClientState) error { - substituteClientState, ok := substituteClient.(*ClientState) - if !ok { - return errorsmod.Wrapf( - clienttypes.ErrInvalidClient, - "invalid substitute client state: expected type %T, got %T", &ClientState{}, substituteClient, - ) - } - - // check that checksums of subject client state and substitute client state match - // changing the checksum is only allowed through the migrate contract RPC endpoint - if !bytes.Equal(cs.Checksum, substituteClientState.Checksum) { - return errorsmod.Wrapf(clienttypes.ErrInvalidClient, "expected checksums to be equal: expected %s, got %s", hex.EncodeToString(cs.Checksum), hex.EncodeToString(substituteClientState.Checksum)) - } - - store := newMigrateClientWrappedStore(subjectClientStore, substituteClientStore) - - payload := SudoMsg{ - MigrateClientStore: &MigrateClientStoreMsg{}, - } - - _, err := wasmSudo[EmptyResult](ctx, cdc, store, &cs, payload) - return err -} diff --git a/modules/light-clients/08-wasm/types/querier_test.go b/modules/light-clients/08-wasm/types/querier_test.go index 7cc1b3d20a3..6912efc904d 100644 --- a/modules/light-clients/08-wasm/types/querier_test.go +++ b/modules/light-clients/08-wasm/types/querier_test.go @@ -107,11 +107,16 @@ func (suite *TypesTestSuite) TestCustomQuery() { tc.malleate() - clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpoint.ClientID) - clientState, ok := endpoint.GetClientState().(*types.ClientState) - suite.Require().True(ok) + clientModule, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(endpoint.ClientID) + suite.Require().True(found) + + // clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpoint.ClientID) + // clientState, ok := endpoint.GetClientState().(*types.ClientState) + // suite.Require().True(ok) + + clientModule.Status(suite.chainA.GetContext(), endpoint.ClientID) - clientState.Status(suite.chainA.GetContext(), clientStore, suite.chainA.App.AppCodec()) + // clientState.Status(suite.chainA.GetContext(), clientStore, suite.chainA.App.AppCodec()) // reset query plugins after each test ibcwasm.SetQueryPlugins(types.NewDefaultQueryPlugins()) diff --git a/modules/light-clients/08-wasm/types/store.go b/modules/light-clients/08-wasm/types/store.go index 0bf9c628705..b63fa6611ea 100644 --- a/modules/light-clients/08-wasm/types/store.go +++ b/modules/light-clients/08-wasm/types/store.go @@ -98,7 +98,7 @@ type migrateClientWrappedStore struct { substituteStore storetypes.KVStore } -func newMigrateClientWrappedStore(subjectStore, substituteStore storetypes.KVStore) migrateClientWrappedStore { +func NewMigrateClientWrappedStore(subjectStore, substituteStore storetypes.KVStore) migrateClientWrappedStore { if subjectStore == nil { panic(errors.New("subjectStore must not be nil")) } diff --git a/modules/light-clients/08-wasm/types/update.go b/modules/light-clients/08-wasm/types/update.go deleted file mode 100644 index 20d2bfb8e4a..00000000000 --- a/modules/light-clients/08-wasm/types/update.go +++ /dev/null @@ -1,75 +0,0 @@ -package types - -import ( - "fmt" - - errorsmod "cosmossdk.io/errors" - storetypes "cosmossdk.io/store/types" - - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - - ibcerrors "github.com/cosmos/ibc-go/v8/modules/core/errors" - "github.com/cosmos/ibc-go/v8/modules/core/exported" -) - -var _ exported.ClientState = (*ClientState)(nil) - -// VerifyClientMessage must verify a ClientMessage. A ClientMessage could be a Header, Misbehaviour, or batch update. -// It must handle each type of ClientMessage appropriately. Calls to CheckForMisbehaviour, UpdateState, and UpdateStateOnMisbehaviour -// will assume that the content of the ClientMessage has been verified and can be trusted. An error should be returned -// if the ClientMessage fails to verify. -func (cs ClientState) VerifyClientMessage(ctx sdk.Context, _ codec.BinaryCodec, clientStore storetypes.KVStore, clientMsg exported.ClientMessage) error { - clientMessage, ok := clientMsg.(*ClientMessage) - if !ok { - return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected type: %T, got: %T", &ClientMessage{}, clientMsg) - } - - payload := QueryMsg{ - VerifyClientMessage: &VerifyClientMessageMsg{ClientMessage: clientMessage.Data}, - } - _, err := wasmQuery[EmptyResult](ctx, clientStore, &cs, payload) - return err -} - -// Client state and new consensus states are updated in the store by the contract -func (cs ClientState) UpdateState(ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, clientMsg exported.ClientMessage) []exported.Height { - clientMessage, ok := clientMsg.(*ClientMessage) - if !ok { - panic(fmt.Errorf("expected type %T, got %T", &ClientMessage{}, clientMsg)) - } - - payload := SudoMsg{ - UpdateState: &UpdateStateMsg{ClientMessage: clientMessage.Data}, - } - - result, err := wasmSudo[UpdateStateResult](ctx, cdc, clientStore, &cs, payload) - if err != nil { - panic(err) - } - - heights := []exported.Height{} - for _, height := range result.Heights { - heights = append(heights, height) - } - - return heights -} - -// UpdateStateOnMisbehaviour should perform appropriate state changes on a client state given that misbehaviour has been detected and verified -// Client state is updated in the store by contract. -func (cs ClientState) UpdateStateOnMisbehaviour(ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, clientMsg exported.ClientMessage) { - clientMessage, ok := clientMsg.(*ClientMessage) - if !ok { - panic(fmt.Errorf("expected type %T, got %T", &ClientMessage{}, clientMsg)) - } - - payload := SudoMsg{ - UpdateStateOnMisbehaviour: &UpdateStateOnMisbehaviourMsg{ClientMessage: clientMessage.Data}, - } - - _, err := wasmSudo[EmptyResult](ctx, cdc, clientStore, &cs, payload) - if err != nil { - panic(err) - } -} diff --git a/modules/light-clients/08-wasm/types/upgrade.go b/modules/light-clients/08-wasm/types/upgrade.go deleted file mode 100644 index 6f4ea5ef589..00000000000 --- a/modules/light-clients/08-wasm/types/upgrade.go +++ /dev/null @@ -1,48 +0,0 @@ -package types - -import ( - errorsmod "cosmossdk.io/errors" - storetypes "cosmossdk.io/store/types" - - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - - clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" - "github.com/cosmos/ibc-go/v8/modules/core/exported" -) - -// VerifyUpgradeAndUpdateState, on a successful verification expects the contract to update -// the new client state, consensus state, and any other client metadata. -func (cs ClientState) VerifyUpgradeAndUpdateState( - ctx sdk.Context, - cdc codec.BinaryCodec, - clientStore storetypes.KVStore, - upgradedClient exported.ClientState, - upgradedConsState exported.ConsensusState, - upgradeClientProof, - upgradeConsensusStateProof []byte, -) error { - wasmUpgradeClientState, ok := upgradedClient.(*ClientState) - if !ok { - return errorsmod.Wrapf(clienttypes.ErrInvalidClient, "upgraded client state must be wasm light client state. expected %T, got: %T", - &ClientState{}, wasmUpgradeClientState) - } - - wasmUpgradeConsState, ok := upgradedConsState.(*ConsensusState) - if !ok { - return errorsmod.Wrapf(clienttypes.ErrInvalidConsensus, "upgraded consensus state must be wasm light consensus state. expected %T, got: %T", - &ConsensusState{}, wasmUpgradeConsState) - } - - payload := SudoMsg{ - VerifyUpgradeAndUpdateState: &VerifyUpgradeAndUpdateStateMsg{ - UpgradeClientState: wasmUpgradeClientState.Data, - UpgradeConsensusState: wasmUpgradeConsState.Data, - ProofUpgradeClient: upgradeClientProof, - ProofUpgradeConsensusState: upgradeConsensusStateProof, - }, - } - - _, err := wasmSudo[EmptyResult](ctx, cdc, clientStore, &cs, payload) - return err -} diff --git a/modules/light-clients/08-wasm/types/vm.go b/modules/light-clients/08-wasm/types/vm.go index 70115968cea..50ed60cb4cf 100644 --- a/modules/light-clients/08-wasm/types/vm.go +++ b/modules/light-clients/08-wasm/types/vm.go @@ -105,8 +105,8 @@ func queryContract(ctx sdk.Context, clientStore storetypes.KVStore, checksum Che return resp, err } -// wasmInstantiate accepts a message to instantiate a wasm contract, JSON encodes it and calls instantiateContract. -func wasmInstantiate(ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, cs *ClientState, payload InstantiateMessage) error { +// WasmInstantiate accepts a message to instantiate a wasm contract, JSON encodes it and calls instantiateContract. +func WasmInstantiate(ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, cs *ClientState, payload InstantiateMessage) error { encodedData, err := json.Marshal(payload) if err != nil { return errorsmod.Wrap(err, "failed to marshal payload for wasm contract instantiation") @@ -138,15 +138,15 @@ func wasmInstantiate(ctx sdk.Context, cdc codec.BinaryCodec, clientStore storety return nil } -// wasmSudo calls the contract with the given payload and returns the result. -// wasmSudo returns an error if: +// WasmSudo calls the contract with the given payload and returns the result. +// WasmSudo returns an error if: // - the payload cannot be marshaled to JSON // - the contract call returns an error // - the response of the contract call contains non-empty messages // - the response of the contract call contains non-empty events // - the response of the contract call contains non-empty attributes // - the data bytes of the response cannot be unmarshaled into the result type -func wasmSudo[T ContractResult](ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, cs *ClientState, payload SudoMsg) (T, error) { +func WasmSudo[T ContractResult](ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, cs *ClientState, payload SudoMsg) (T, error) { var result T encodedData, err := json.Marshal(payload) @@ -204,12 +204,12 @@ func wasmMigrate(ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes. return err } -// wasmQuery queries the contract with the given payload and returns the result. -// wasmQuery returns an error if: +// WasmQuery queries the contract with the given payload and returns the result. +// WasmQuery returns an error if: // - the payload cannot be marshaled to JSON // - the contract query returns an error // - the data bytes of the response cannot be unmarshal into the result type -func wasmQuery[T ContractResult](ctx sdk.Context, clientStore storetypes.KVStore, cs *ClientState, payload QueryMsg) (T, error) { +func WasmQuery[T ContractResult](ctx sdk.Context, clientStore storetypes.KVStore, cs *ClientState, payload QueryMsg) (T, error) { var result T encodedData, err := json.Marshal(payload) From 8ec8bf68b4e4ba3d20891880e50c5a8aad3132a8 Mon Sep 17 00:00:00 2001 From: DimitrisJim Date: Thu, 4 Apr 2024 02:16:13 +0300 Subject: [PATCH 02/24] Remove global VM, introspection to retrieve client identifier. - Refactor signatures to explicitly pass vm, client identifier for time being. - Refactor tests to conform with new signatures. - Replace occurences of `ibcwasm.GetVM` in other keeper functions. --- .../08-wasm/internal/ibcwasm/wasm.go | 16 ---- .../light-clients/08-wasm/keeper/genesis.go | 7 +- .../08-wasm/keeper/grpc_query.go | 4 +- .../light-clients/08-wasm/keeper/keeper.go | 11 +-- .../08-wasm/keeper/keeper_test.go | 13 ++-- .../light-clients/08-wasm/keeper/keeper_vm.go | 1 - .../08-wasm/keeper/msg_server.go | 4 +- .../08-wasm/keeper/options_test.go | 8 +- .../08-wasm/keeper/snapshotter.go | 7 +- .../08-wasm/light_client_module.go | 30 ++++---- .../08-wasm/testing/simapp/app.go | 2 +- .../08-wasm/types/export_test.go | 10 +-- .../08-wasm/types/migrate_contract.go | 6 +- .../08-wasm/types/migrate_contract_test.go | 3 +- modules/light-clients/08-wasm/types/store.go | 48 ------------ .../light-clients/08-wasm/types/store_test.go | 74 ------------------- modules/light-clients/08-wasm/types/vm.go | 44 ++++------- .../light-clients/08-wasm/types/vm_test.go | 12 ++- 18 files changed, 77 insertions(+), 223 deletions(-) diff --git a/modules/light-clients/08-wasm/internal/ibcwasm/wasm.go b/modules/light-clients/08-wasm/internal/ibcwasm/wasm.go index 0b4fa20ba5b..4bfd58bf9b3 100644 --- a/modules/light-clients/08-wasm/internal/ibcwasm/wasm.go +++ b/modules/light-clients/08-wasm/internal/ibcwasm/wasm.go @@ -8,8 +8,6 @@ import ( ) var ( - vm WasmEngine - queryRouter QueryRouter queryPlugins QueryPluginsI @@ -21,20 +19,6 @@ var ( ChecksumsKey = collections.NewPrefix(0) ) -// SetVM sets the wasm VM for the 08-wasm module. -// It panics if the wasm VM is nil. -func SetVM(wasmVM WasmEngine) { - if wasmVM == nil { - panic(errors.New("wasm VM must be not nil")) - } - vm = wasmVM -} - -// GetVM returns the wasm VM for the 08-wasm module. -func GetVM() WasmEngine { - return vm -} - // SetQueryRouter sets the custom wasm query router for the 08-wasm module. // Panics if the queryRouter is nil. func SetQueryRouter(router QueryRouter) { diff --git a/modules/light-clients/08-wasm/keeper/genesis.go b/modules/light-clients/08-wasm/keeper/genesis.go index d7c813c7086..51f61018f81 100644 --- a/modules/light-clients/08-wasm/keeper/genesis.go +++ b/modules/light-clients/08-wasm/keeper/genesis.go @@ -5,7 +5,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" ) @@ -13,7 +12,7 @@ import ( // state. func (k Keeper) InitGenesis(ctx sdk.Context, gs types.GenesisState) error { storeFn := func(code wasmvm.WasmCode, _ uint64) (wasmvm.Checksum, uint64, error) { - checksum, err := ibcwasm.GetVM().StoreCodeUnchecked(code) + checksum, err := k.GetVM().StoreCodeUnchecked(code) return checksum, 0, err } @@ -28,7 +27,7 @@ func (k Keeper) InitGenesis(ctx sdk.Context, gs types.GenesisState) error { // ExportGenesis returns the 08-wasm module's exported genesis. This includes the code // for all contracts previously stored. -func (Keeper) ExportGenesis(ctx sdk.Context) types.GenesisState { +func (k Keeper) ExportGenesis(ctx sdk.Context) types.GenesisState { checksums, err := types.GetAllChecksums(ctx) if err != nil { panic(err) @@ -37,7 +36,7 @@ func (Keeper) ExportGenesis(ctx sdk.Context) types.GenesisState { // Grab code from wasmVM and add to genesis state. var genesisState types.GenesisState for _, checksum := range checksums { - code, err := ibcwasm.GetVM().GetCode(checksum) + code, err := k.GetVM().GetCode(checksum) if err != nil { panic(err) } diff --git a/modules/light-clients/08-wasm/keeper/grpc_query.go b/modules/light-clients/08-wasm/keeper/grpc_query.go index 44216fd2454..8e18ba6e313 100644 --- a/modules/light-clients/08-wasm/keeper/grpc_query.go +++ b/modules/light-clients/08-wasm/keeper/grpc_query.go @@ -20,7 +20,7 @@ import ( var _ types.QueryServer = (*Keeper)(nil) // Code implements the Query/Code gRPC method -func (Keeper) Code(goCtx context.Context, req *types.QueryCodeRequest) (*types.QueryCodeResponse, error) { +func (k Keeper) Code(goCtx context.Context, req *types.QueryCodeRequest) (*types.QueryCodeResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") } @@ -35,7 +35,7 @@ func (Keeper) Code(goCtx context.Context, req *types.QueryCodeRequest) (*types.Q return nil, status.Error(codes.NotFound, errorsmod.Wrap(types.ErrWasmChecksumNotFound, req.Checksum).Error()) } - code, err := ibcwasm.GetVM().GetCode(checksum) + code, err := k.GetVM().GetCode(checksum) if err != nil { return nil, status.Error(codes.NotFound, errorsmod.Wrap(types.ErrWasmChecksumNotFound, req.Checksum).Error()) } diff --git a/modules/light-clients/08-wasm/keeper/keeper.go b/modules/light-clients/08-wasm/keeper/keeper.go index 20c32f644a6..168db320027 100644 --- a/modules/light-clients/08-wasm/keeper/keeper.go +++ b/modules/light-clients/08-wasm/keeper/keeper.go @@ -47,7 +47,7 @@ func (k Keeper) GetVM() ibcwasm.WasmEngine { return k.vm } -func (Keeper) storeWasmCode(ctx sdk.Context, code []byte, storeFn func(code wasmvm.WasmCode, gasLimit uint64) (wasmvm.Checksum, uint64, error)) ([]byte, error) { +func (k Keeper) storeWasmCode(ctx sdk.Context, code []byte, storeFn func(code wasmvm.WasmCode, gasLimit uint64) (wasmvm.Checksum, uint64, error)) ([]byte, error) { var err error if types.IsGzip(code) { ctx.GasMeter().ConsumeGas(types.VMGasRegister.UncompressCosts(len(code)), "Uncompress gzip bytecode") @@ -86,7 +86,7 @@ func (Keeper) storeWasmCode(ctx sdk.Context, code []byte, storeFn func(code wasm } // pin the code to the vm in-memory cache - if err := ibcwasm.GetVM().Pin(vmChecksum); err != nil { + if err := k.GetVM().Pin(vmChecksum); err != nil { return nil, errorsmod.Wrapf(err, "failed to pin contract with checksum (%s) to vm cache", hex.EncodeToString(vmChecksum)) } @@ -108,7 +108,7 @@ func (k Keeper) migrateContractCode(ctx sdk.Context, clientID string, newChecksu clientStore := k.clientKeeper.ClientStore(ctx, clientID) - err = wasmClientState.MigrateContract(ctx, k.cdc, clientStore, clientID, newChecksum, migrateMsg) + err = wasmClientState.MigrateContract(ctx, k.GetVM(), k.cdc, clientStore, clientID, newChecksum, migrateMsg) if err != nil { return errorsmod.Wrap(err, "contract migration failed") } @@ -147,14 +147,15 @@ func (k Keeper) GetWasmClientState(ctx sdk.Context, clientID string) (*types.Cli } // InitializePinnedCodes updates wasmvm to pin to cache all contracts marked as pinned -func InitializePinnedCodes(ctx sdk.Context) error { +// TODO(jim): Make meth on Keeper probably. +func InitializePinnedCodes(ctx sdk.Context, k Keeper) error { checksums, err := types.GetAllChecksums(ctx) if err != nil { return err } for _, checksum := range checksums { - if err := ibcwasm.GetVM().Pin(checksum); err != nil { + if err := k.GetVM().Pin(checksum); err != nil { return err } } diff --git a/modules/light-clients/08-wasm/keeper/keeper_test.go b/modules/light-clients/08-wasm/keeper/keeper_test.go index d1c0a4f3863..03edd93da30 100644 --- a/modules/light-clients/08-wasm/keeper/keeper_test.go +++ b/modules/light-clients/08-wasm/keeper/keeper_test.go @@ -19,7 +19,6 @@ import ( authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" - "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper" wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing/simapp" @@ -156,7 +155,7 @@ func (suite *KeeperTestSuite) TestNewKeeper() { runtime.NewKVStoreService(GetSimApp(suite.chainA).GetKey(types.StoreKey)), GetSimApp(suite.chainA).IBCKeeper.ClientKeeper, GetSimApp(suite.chainA).WasmClientKeeper.GetAuthority(), - ibcwasm.GetVM(), + GetSimApp(suite.chainA).WasmClientKeeper.GetVM(), GetSimApp(suite.chainA).GRPCQueryRouter(), ) }, @@ -171,7 +170,7 @@ func (suite *KeeperTestSuite) TestNewKeeper() { runtime.NewKVStoreService(GetSimApp(suite.chainA).GetKey(types.StoreKey)), GetSimApp(suite.chainA).IBCKeeper.ClientKeeper, "", // authority - ibcwasm.GetVM(), + GetSimApp(suite.chainA).WasmClientKeeper.GetVM(), GetSimApp(suite.chainA).GRPCQueryRouter(), ) }, @@ -186,7 +185,7 @@ func (suite *KeeperTestSuite) TestNewKeeper() { runtime.NewKVStoreService(GetSimApp(suite.chainA).GetKey(types.StoreKey)), nil, // client keeper, GetSimApp(suite.chainA).WasmClientKeeper.GetAuthority(), - ibcwasm.GetVM(), + GetSimApp(suite.chainA).WasmClientKeeper.GetVM(), GetSimApp(suite.chainA).GRPCQueryRouter(), ) }, @@ -216,7 +215,7 @@ func (suite *KeeperTestSuite) TestNewKeeper() { nil, GetSimApp(suite.chainA).IBCKeeper.ClientKeeper, GetSimApp(suite.chainA).WasmClientKeeper.GetAuthority(), - ibcwasm.GetVM(), + GetSimApp(suite.chainA).WasmClientKeeper.GetVM(), GetSimApp(suite.chainA).GRPCQueryRouter(), ) }, @@ -231,7 +230,7 @@ func (suite *KeeperTestSuite) TestNewKeeper() { runtime.NewKVStoreService(GetSimApp(suite.chainA).GetKey(types.StoreKey)), GetSimApp(suite.chainA).IBCKeeper.ClientKeeper, GetSimApp(suite.chainA).WasmClientKeeper.GetAuthority(), - ibcwasm.GetVM(), + GetSimApp(suite.chainA).WasmClientKeeper.GetVM(), nil, ) }, @@ -313,7 +312,7 @@ func (suite *KeeperTestSuite) TestInitializedPinnedCodes() { // malleate after storing contracts tc.malleate() - err := keeper.InitializePinnedCodes(ctx) + err := keeper.InitializePinnedCodes(ctx, wasmClientKeeper) expPass := tc.expError == nil if expPass { diff --git a/modules/light-clients/08-wasm/keeper/keeper_vm.go b/modules/light-clients/08-wasm/keeper/keeper_vm.go index fea0ab3defd..12ed2880cd6 100644 --- a/modules/light-clients/08-wasm/keeper/keeper_vm.go +++ b/modules/light-clients/08-wasm/keeper/keeper_vm.go @@ -60,7 +60,6 @@ func NewKeeperWithVM( opt.apply(keeper) } - ibcwasm.SetVM(vm) ibcwasm.SetQueryRouter(queryRouter) ibcwasm.SetupWasmStoreService(storeService) diff --git a/modules/light-clients/08-wasm/keeper/msg_server.go b/modules/light-clients/08-wasm/keeper/msg_server.go index 46d55f397b5..8b8763f586a 100644 --- a/modules/light-clients/08-wasm/keeper/msg_server.go +++ b/modules/light-clients/08-wasm/keeper/msg_server.go @@ -22,7 +22,7 @@ func (k Keeper) StoreCode(goCtx context.Context, msg *types.MsgStoreCode) (*type } ctx := sdk.UnwrapSDKContext(goCtx) - checksum, err := k.storeWasmCode(ctx, msg.WasmByteCode, ibcwasm.GetVM().StoreCode) + checksum, err := k.storeWasmCode(ctx, msg.WasmByteCode, k.GetVM().StoreCode) if err != nil { return nil, errorsmod.Wrap(err, "failed to store wasm bytecode") } @@ -50,7 +50,7 @@ func (k Keeper) RemoveChecksum(goCtx context.Context, msg *types.MsgRemoveChecks } // unpin the code from the vm in-memory cache - if err := ibcwasm.GetVM().Unpin(msg.Checksum); err != nil { + if err := k.GetVM().Unpin(msg.Checksum); err != nil { return nil, errorsmod.Wrapf(err, "failed to unpin contract with checksum (%s) from vm cache", hex.EncodeToString(msg.Checksum)) } diff --git a/modules/light-clients/08-wasm/keeper/options_test.go b/modules/light-clients/08-wasm/keeper/options_test.go index 61b686927ee..ff54dd020f5 100644 --- a/modules/light-clients/08-wasm/keeper/options_test.go +++ b/modules/light-clients/08-wasm/keeper/options_test.go @@ -41,7 +41,7 @@ func (suite *KeeperTestSuite) TestNewKeeperWithOptions() { runtime.NewKVStoreService(GetSimApp(suite.chainA).GetKey(types.StoreKey)), GetSimApp(suite.chainA).IBCKeeper.ClientKeeper, GetSimApp(suite.chainA).WasmClientKeeper.GetAuthority(), - ibcwasm.GetVM(), + GetSimApp(suite.chainA).WasmClientKeeper.GetVM(), GetSimApp(suite.chainA).GRPCQueryRouter(), ) }, @@ -66,7 +66,7 @@ func (suite *KeeperTestSuite) TestNewKeeperWithOptions() { runtime.NewKVStoreService(GetSimApp(suite.chainA).GetKey(types.StoreKey)), GetSimApp(suite.chainA).IBCKeeper.ClientKeeper, GetSimApp(suite.chainA).WasmClientKeeper.GetAuthority(), - ibcwasm.GetVM(), + GetSimApp(suite.chainA).WasmClientKeeper.GetVM(), GetSimApp(suite.chainA).GRPCQueryRouter(), querierOption, ) @@ -92,7 +92,7 @@ func (suite *KeeperTestSuite) TestNewKeeperWithOptions() { runtime.NewKVStoreService(GetSimApp(suite.chainA).GetKey(types.StoreKey)), GetSimApp(suite.chainA).IBCKeeper.ClientKeeper, GetSimApp(suite.chainA).WasmClientKeeper.GetAuthority(), - ibcwasm.GetVM(), + GetSimApp(suite.chainA).WasmClientKeeper.GetVM(), GetSimApp(suite.chainA).GRPCQueryRouter(), querierOption, ) @@ -119,7 +119,7 @@ func (suite *KeeperTestSuite) TestNewKeeperWithOptions() { runtime.NewKVStoreService(GetSimApp(suite.chainA).GetKey(types.StoreKey)), GetSimApp(suite.chainA).IBCKeeper.ClientKeeper, GetSimApp(suite.chainA).WasmClientKeeper.GetAuthority(), - ibcwasm.GetVM(), + GetSimApp(suite.chainA).WasmClientKeeper.GetVM(), GetSimApp(suite.chainA).GRPCQueryRouter(), querierOption, ) diff --git a/modules/light-clients/08-wasm/keeper/snapshotter.go b/modules/light-clients/08-wasm/keeper/snapshotter.go index 2d474386e49..976e00f17ea 100644 --- a/modules/light-clients/08-wasm/keeper/snapshotter.go +++ b/modules/light-clients/08-wasm/keeper/snapshotter.go @@ -12,7 +12,6 @@ import ( cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" - "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" ) @@ -73,7 +72,7 @@ func (ws *WasmSnapshotter) SnapshotExtension(height uint64, payloadWriter snapsh } for _, checksum := range checksums { - wasmCode, err := ibcwasm.GetVM().GetCode(checksum) + wasmCode, err := ws.keeper.GetVM().GetCode(checksum) if err != nil { return err } @@ -112,12 +111,12 @@ func restoreV1(ctx sdk.Context, k *Keeper, compressedCode []byte) error { return errorsmod.Wrap(err, "failed to uncompress wasm code") } - checksum, err := ibcwasm.GetVM().StoreCodeUnchecked(wasmCode) + checksum, err := k.GetVM().StoreCodeUnchecked(wasmCode) if err != nil { return errorsmod.Wrap(err, "failed to store wasm code") } - if err := ibcwasm.GetVM().Pin(checksum); err != nil { + if err := k.GetVM().Pin(checksum); err != nil { return errorsmod.Wrapf(err, "failed to pin checksum: %s to in-memory cache", hex.EncodeToString(checksum)) } diff --git a/modules/light-clients/08-wasm/light_client_module.go b/modules/light-clients/08-wasm/light_client_module.go index 4d43b86af65..cfa8a90d0d0 100644 --- a/modules/light-clients/08-wasm/light_client_module.go +++ b/modules/light-clients/08-wasm/light_client_module.go @@ -11,6 +11,7 @@ import ( commitmenttypes "github.com/cosmos/ibc-go/v8/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" wasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" @@ -33,6 +34,11 @@ func NewLightClientModule(keeper wasmkeeper.Keeper) LightClientModule { } } +// GetVM returns the VM associated with this light client module. +func (l *LightClientModule) GetVM() ibcwasm.WasmEngine { + return l.keeper.GetVM() +} + // RegisterStoreProvider is called by core IBC when a LightClientModule is added to the router. // It allows the LightClientModule to set a ClientStoreProvider which supplies isolated prefix client stores // to IBC light client instances. @@ -65,7 +71,6 @@ func (l LightClientModule) Initialize(ctx sdk.Context, clientID string, clientSt } clientStore := l.storeProvider.ClientStore(ctx, clientID) - cdc := l.keeper.Codec() // Do not allow initialization of a client with a checksum that hasn't been previously stored via storeWasmCode. if !types.HasChecksum(ctx, clientState.Checksum) { @@ -78,7 +83,7 @@ func (l LightClientModule) Initialize(ctx sdk.Context, clientID string, clientSt Checksum: clientState.Checksum, } - return types.WasmInstantiate(ctx, cdc, clientStore, &clientState, payload) + return types.WasmInstantiate(ctx, l.GetVM(), clientID, l.keeper.Codec(), clientStore, &clientState, payload) } // VerifyClientMessage obtains the client state associated with the client identifier, it then must verify the ClientMessage. @@ -105,7 +110,7 @@ func (l LightClientModule) VerifyClientMessage(ctx sdk.Context, clientID string, payload := types.QueryMsg{ VerifyClientMessage: &types.VerifyClientMessageMsg{ClientMessage: clientMessage.Data}, } - _, err := types.WasmQuery[types.EmptyResult](ctx, clientStore, clientState, payload) + _, err := types.WasmQuery[types.EmptyResult](ctx, l.GetVM(), clientID, clientStore, clientState, payload) return err } @@ -131,7 +136,7 @@ func (l LightClientModule) CheckForMisbehaviour(ctx sdk.Context, clientID string CheckForMisbehaviour: &types.CheckForMisbehaviourMsg{ClientMessage: clientMessage.Data}, } - result, err := types.WasmQuery[types.CheckForMisbehaviourResult](ctx, clientStore, clientState, payload) + result, err := types.WasmQuery[types.CheckForMisbehaviourResult](ctx, l.GetVM(), clientID, clientStore, clientState, payload) if err != nil { return false } @@ -162,7 +167,7 @@ func (l LightClientModule) UpdateStateOnMisbehaviour(ctx sdk.Context, clientID s UpdateStateOnMisbehaviour: &types.UpdateStateOnMisbehaviourMsg{ClientMessage: clientMessage.Data}, } - _, err := types.WasmSudo[types.EmptyResult](ctx, cdc, clientStore, clientState, payload) + _, err := types.WasmSudo[types.EmptyResult](ctx, l.GetVM(), clientID, cdc, clientStore, clientState, payload) if err != nil { panic(err) } @@ -190,7 +195,7 @@ func (l LightClientModule) UpdateState(ctx sdk.Context, clientID string, clientM UpdateState: &types.UpdateStateMsg{ClientMessage: clientMessage.Data}, } - result, err := types.WasmSudo[types.UpdateStateResult](ctx, cdc, clientStore, clientState, payload) + result, err := types.WasmSudo[types.UpdateStateResult](ctx, l.GetVM(), clientID, cdc, clientStore, clientState, payload) if err != nil { panic(err) } @@ -254,7 +259,7 @@ func (l LightClientModule) VerifyMembership( Value: value, }, } - _, err := types.WasmSudo[types.EmptyResult](ctx, cdc, clientStore, clientState, payload) + _, err := types.WasmSudo[types.EmptyResult](ctx, l.GetVM(), clientID, cdc, clientStore, clientState, payload) return err } @@ -307,7 +312,7 @@ func (l LightClientModule) VerifyNonMembership( Path: merklePath, }, } - _, err := types.WasmSudo[types.EmptyResult](ctx, cdc, clientStore, clientState, payload) + _, err := types.WasmSudo[types.EmptyResult](ctx, l.GetVM(), clientID, cdc, clientStore, clientState, payload) return err } @@ -338,7 +343,7 @@ func (l LightClientModule) Status(ctx sdk.Context, clientID string) exported.Sta } payload := types.QueryMsg{Status: &types.StatusMsg{}} - result, err := types.WasmQuery[types.StatusResult](ctx, clientStore, clientState, payload) + result, err := types.WasmQuery[types.StatusResult](ctx, l.GetVM(), clientID, clientStore, clientState, payload) if err != nil { return exported.Unknown } @@ -386,7 +391,7 @@ func (l LightClientModule) TimestampAtHeight(ctx sdk.Context, clientID string, h }, } - result, err := types.WasmQuery[types.TimestampAtHeightResult](ctx, clientStore, clientState, payload) + result, err := types.WasmQuery[types.TimestampAtHeightResult](ctx, l.GetVM(), clientID, clientStore, clientState, payload) if err != nil { return 0, errorsmod.Wrapf(err, "height (%s)", height) } @@ -438,9 +443,8 @@ func (l LightClientModule) RecoverClient(ctx sdk.Context, clientID, substituteCl MigrateClientStore: &types.MigrateClientStoreMsg{}, } - _, err = types.WasmSudo[types.EmptyResult](ctx, cdc, store, clientState, payload) + _, err = types.WasmSudo[types.EmptyResult](ctx, l.GetVM(), clientID, cdc, store, clientState, payload) return err - // return clientState.CheckSubstituteAndUpdateState(ctx, cdc, clientStore, substituteClientStore, substituteClient) } // VerifyUpgradeAndUpdateState obtains the client state associated with the client identifier and calls into the appropriate contract endpoint. @@ -489,6 +493,6 @@ func (l LightClientModule) VerifyUpgradeAndUpdateState( }, } - _, err := types.WasmSudo[types.EmptyResult](ctx, cdc, clientStore, clientState, payload) + _, err := types.WasmSudo[types.EmptyResult](ctx, l.GetVM(), clientID, cdc, clientStore, clientState, payload) return err } diff --git a/modules/light-clients/08-wasm/testing/simapp/app.go b/modules/light-clients/08-wasm/testing/simapp/app.go index ba891455139..384725f9767 100644 --- a/modules/light-clients/08-wasm/testing/simapp/app.go +++ b/modules/light-clients/08-wasm/testing/simapp/app.go @@ -846,7 +846,7 @@ func NewSimApp( ctx := app.BaseApp.NewUncachedContext(true, cmtproto.Header{}) // Initialize pinned codes in wasmvm as they are not persisted there - if err := wasmkeeper.InitializePinnedCodes(ctx); err != nil { + if err := wasmkeeper.InitializePinnedCodes(ctx, app.WasmClientKeeper); err != nil { cmtos.Exit(fmt.Sprintf("failed initialize pinned codes %s", err)) } } diff --git a/modules/light-clients/08-wasm/types/export_test.go b/modules/light-clients/08-wasm/types/export_test.go index ad2d059293c..70c5ca39c9b 100644 --- a/modules/light-clients/08-wasm/types/export_test.go +++ b/modules/light-clients/08-wasm/types/export_test.go @@ -5,6 +5,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" ) /* @@ -19,11 +20,6 @@ var ( SubstitutePrefix = substitutePrefix ) -// GetClientID is a wrapper around getClientID to allow the function to be directly called in tests. -func GetClientID(clientStore storetypes.KVStore) (string, error) { - return getClientID(clientStore) -} - // NewMigrateProposalWrappedStore is a wrapper around newMigrateProposalWrappedStore to allow the function to be directly called in tests. // //nolint:revive // Returning unexported type for testing purposes. @@ -42,6 +38,6 @@ func SplitPrefix(key []byte) ([]byte, []byte) { } // WasmMigrate wraps wasmMigrate and is used solely for testing. -func WasmMigrate(ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, cs *ClientState, clientID string, payload []byte) error { - return wasmMigrate(ctx, cdc, clientStore, cs, clientID, payload) +func WasmMigrate(ctx sdk.Context, vm ibcwasm.WasmEngine, cdc codec.BinaryCodec, clientStore storetypes.KVStore, cs *ClientState, clientID string, payload []byte) error { + return wasmMigrate(ctx, vm, cdc, clientStore, cs, clientID, payload) } diff --git a/modules/light-clients/08-wasm/types/migrate_contract.go b/modules/light-clients/08-wasm/types/migrate_contract.go index f028c2c02fa..d34e1fd28ea 100644 --- a/modules/light-clients/08-wasm/types/migrate_contract.go +++ b/modules/light-clients/08-wasm/types/migrate_contract.go @@ -9,14 +9,16 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" ) // MigrateContract calls the migrate entry point on the contract with the given // migrateMsg. The contract must exist and the checksum must be found in the // store. If the checksum is the same as the current checksum, an error is returned. // This does not update the checksum in the client state. +// TODO(jim): Move to lcm? Keeper? func (cs ClientState) MigrateContract( - ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, + ctx sdk.Context, vm ibcwasm.WasmEngine, cdc codec.BinaryCodec, clientStore storetypes.KVStore, clientID string, newChecksum, migrateMsg []byte, ) error { if !HasChecksum(ctx, newChecksum) { @@ -32,7 +34,7 @@ func (cs ClientState) MigrateContract( // persisted to the client store. cs.Checksum = newChecksum - err := wasmMigrate(ctx, cdc, clientStore, &cs, clientID, migrateMsg) + err := wasmMigrate(ctx, vm, cdc, clientStore, &cs, clientID, migrateMsg) if err != nil { return err } diff --git a/modules/light-clients/08-wasm/types/migrate_contract_test.go b/modules/light-clients/08-wasm/types/migrate_contract_test.go index 78724712888..2f0d1ad3a2b 100644 --- a/modules/light-clients/08-wasm/types/migrate_contract_test.go +++ b/modules/light-clients/08-wasm/types/migrate_contract_test.go @@ -135,7 +135,8 @@ func (suite *TypesTestSuite) TestMigrateContract() { tc.malleate() - err = clientState.MigrateContract(suite.chainA.GetContext(), suite.chainA.App.AppCodec(), clientStore, endpointA.ClientID, newHash, payload) + vm := GetSimApp(suite.chainA).WasmClientKeeper.GetVM() + err = clientState.MigrateContract(suite.chainA.GetContext(), vm, suite.chainA.App.AppCodec(), clientStore, endpointA.ClientID, newHash, payload) // updated client state clientState, ok = endpointA.GetClientState().(*types.ClientState) diff --git a/modules/light-clients/08-wasm/types/store.go b/modules/light-clients/08-wasm/types/store.go index b63fa6611ea..e4e3c14c89d 100644 --- a/modules/light-clients/08-wasm/types/store.go +++ b/modules/light-clients/08-wasm/types/store.go @@ -5,15 +5,11 @@ import ( "context" "errors" "io" - "reflect" - "strings" wasmvm "github.com/CosmWasm/wasmvm/v2" wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" - errorsmod "cosmossdk.io/errors" "cosmossdk.io/store/cachekv" - storeprefix "cosmossdk.io/store/prefix" "cosmossdk.io/store/tracekv" storetypes "cosmossdk.io/store/types" @@ -293,47 +289,3 @@ func (s storeAdapter) Iterator(start, end []byte) wasmvmtypes.Iterator { func (s storeAdapter) ReverseIterator(start, end []byte) wasmvmtypes.Iterator { return s.parent.ReverseIterator(start, end) } - -// getClientID extracts and validates the clientID from the clientStore's prefix. -// -// Due to the 02-client module not passing the clientID to the 08-wasm module, -// this function was devised to infer it from the store's prefix. -// The expected format of the clientStore prefix is "/{clientID}/". -// If the clientStore is of type migrateProposalWrappedStore, the subjectStore's prefix is utilized instead. -func getClientID(clientStore storetypes.KVStore) (string, error) { - upws, isMigrateProposalWrappedStore := clientStore.(migrateClientWrappedStore) - if isMigrateProposalWrappedStore { - // if the clientStore is a migrateProposalWrappedStore, we retrieve the subjectStore - // because the contract call will be made on the client with the ID of the subjectStore - clientStore = upws.subjectStore - } - - store, ok := clientStore.(storeprefix.Store) - if !ok { - return "", errorsmod.Wrap(ErrRetrieveClientID, "clientStore is not a prefix store") - } - - // using reflect to retrieve the private prefix field - r := reflect.ValueOf(&store).Elem() - - f := r.FieldByName("prefix") - if !f.IsValid() { - return "", errorsmod.Wrap(ErrRetrieveClientID, "prefix field not found") - } - - prefix := string(f.Bytes()) - - split := strings.Split(prefix, "/") - if len(split) < 3 { - return "", errorsmod.Wrap(ErrRetrieveClientID, "prefix is not of the expected form") - } - - // the clientID is the second to last element of the prefix - // the prefix is expected to be of the form "/{clientID}/" - clientID := split[len(split)-2] - if err := ValidateClientID(clientID); err != nil { - return "", errorsmod.Wrapf(ErrRetrieveClientID, "prefix does not contain a valid clientID: %s", err.Error()) - } - - return clientID, nil -} diff --git a/modules/light-clients/08-wasm/types/store_test.go b/modules/light-clients/08-wasm/types/store_test.go index 7037ccbe0aa..880b2733db5 100644 --- a/modules/light-clients/08-wasm/types/store_test.go +++ b/modules/light-clients/08-wasm/types/store_test.go @@ -1,7 +1,6 @@ package types_test import ( - prefixstore "cosmossdk.io/store/prefix" storetypes "cosmossdk.io/store/types" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" @@ -492,79 +491,6 @@ func (suite *TypesTestSuite) TestNewMigrateClientWrappedStore() { } } -func (suite *TypesTestSuite) TestGetClientID() { - clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), defaultWasmClientID) - - testCases := []struct { - name string - malleate func() - expError error - }{ - { - "success: clientID retrieved", - func() {}, - nil, - }, - { - "success: clientID retrieved from migrateClientWrappedStore", - func() { - // substituteStore is ignored. - clientStore = types.NewMigrateProposalWrappedStore(clientStore, clientStore) - }, - nil, - }, - { - "failure: clientStore is nil", - func() { - clientStore = nil - }, - types.ErrRetrieveClientID, - }, - { - "failure: prefix store does not contain prefix", - func() { - clientStore = prefixstore.NewStore(nil, nil) - }, - types.ErrRetrieveClientID, - }, - { - "failure: prefix does not contain slash separated path", - func() { - clientStore = prefixstore.NewStore(nil, []byte("not-a-slash-separated-path")) - }, - types.ErrRetrieveClientID, - }, - { - "failure: prefix only contains one slash", - func() { - clientStore = prefixstore.NewStore(nil, []byte("only-one-slash/")) - }, - types.ErrRetrieveClientID, - }, - { - "failure: prefix does not contain a wasm clientID", - func() { - clientStore = prefixstore.NewStore(nil, []byte("/not-client-id/")) - }, - types.ErrRetrieveClientID, - }, - } - - for _, tc := range testCases { - suite.Run(tc.name, func() { - tc.malleate() - clientID, err := types.GetClientID(clientStore) - - if tc.expError == nil { - suite.Require().NoError(err) - suite.Require().Equal(defaultWasmClientID, clientID) - } else { - suite.Require().ErrorIs(err, tc.expError) - } - }) - } -} - // GetSubjectAndSubstituteStore returns a subject and substitute store for testing. func (suite *TypesTestSuite) GetSubjectAndSubstituteStore() (storetypes.KVStore, storetypes.KVStore) { suite.SetupWasmWithMockVM() diff --git a/modules/light-clients/08-wasm/types/vm.go b/modules/light-clients/08-wasm/types/vm.go index 50ed60cb4cf..42b3d9baef5 100644 --- a/modules/light-clients/08-wasm/types/vm.go +++ b/modules/light-clients/08-wasm/types/vm.go @@ -33,15 +33,11 @@ var ( ) // instantiateContract calls vm.Instantiate with appropriate arguments. -func instantiateContract(ctx sdk.Context, clientStore storetypes.KVStore, checksum Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) { +func instantiateContract(ctx sdk.Context, vm ibcwasm.WasmEngine, clientID string, clientStore storetypes.KVStore, checksum Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) { sdkGasMeter := ctx.GasMeter() multipliedGasMeter := NewMultipliedGasMeter(sdkGasMeter, VMGasRegister) gasLimit := VMGasRegister.RuntimeGasForContract(ctx) - clientID, err := getClientID(clientStore) - if err != nil { - return nil, errorsmod.Wrap(err, "failed to retrieve clientID for wasm contract instantiation") - } env := getEnv(ctx, clientID) msgInfo := wasmvmtypes.MessageInfo{ @@ -50,31 +46,27 @@ func instantiateContract(ctx sdk.Context, clientStore storetypes.KVStore, checks } ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: instantiate") - resp, gasUsed, err := ibcwasm.GetVM().Instantiate(checksum, env, msgInfo, msg, newStoreAdapter(clientStore), wasmvmAPI, newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, costJSONDeserialization) + resp, gasUsed, err := vm.Instantiate(checksum, env, msgInfo, msg, newStoreAdapter(clientStore), wasmvmAPI, newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, costJSONDeserialization) VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) return resp, err } // callContract calls vm.Sudo with internally constructed gas meter and environment. -func callContract(ctx sdk.Context, clientStore storetypes.KVStore, checksum Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) { +func callContract(ctx sdk.Context, vm ibcwasm.WasmEngine, clientID string, clientStore storetypes.KVStore, checksum Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) { sdkGasMeter := ctx.GasMeter() multipliedGasMeter := NewMultipliedGasMeter(sdkGasMeter, VMGasRegister) gasLimit := VMGasRegister.RuntimeGasForContract(ctx) - clientID, err := getClientID(clientStore) - if err != nil { - return nil, errorsmod.Wrap(err, "failed to retrieve clientID for wasm contract call") - } env := getEnv(ctx, clientID) ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: sudo") - resp, gasUsed, err := ibcwasm.GetVM().Sudo(checksum, env, msg, newStoreAdapter(clientStore), wasmvmAPI, newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, costJSONDeserialization) + resp, gasUsed, err := vm.Sudo(checksum, env, msg, newStoreAdapter(clientStore), wasmvmAPI, newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, costJSONDeserialization) VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) return resp, err } // migrateContract calls vm.Migrate with internally constructed gas meter and environment. -func migrateContract(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, checksum Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) { +func migrateContract(ctx sdk.Context, vm ibcwasm.WasmEngine, clientID string, clientStore storetypes.KVStore, checksum Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) { sdkGasMeter := ctx.GasMeter() multipliedGasMeter := NewMultipliedGasMeter(sdkGasMeter, VMGasRegister) gasLimit := VMGasRegister.RuntimeGasForContract(ctx) @@ -82,38 +74,34 @@ func migrateContract(ctx sdk.Context, clientID string, clientStore storetypes.KV env := getEnv(ctx, clientID) ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: migrate") - resp, gasUsed, err := ibcwasm.GetVM().Migrate(checksum, env, msg, newStoreAdapter(clientStore), wasmvmAPI, newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, costJSONDeserialization) + resp, gasUsed, err := vm.Migrate(checksum, env, msg, newStoreAdapter(clientStore), wasmvmAPI, newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, costJSONDeserialization) VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) return resp, err } // queryContract calls vm.Query. -func queryContract(ctx sdk.Context, clientStore storetypes.KVStore, checksum Checksum, msg []byte) (*wasmvmtypes.QueryResult, error) { +func queryContract(ctx sdk.Context, vm ibcwasm.WasmEngine, clientID string, clientStore storetypes.KVStore, checksum Checksum, msg []byte) (*wasmvmtypes.QueryResult, error) { sdkGasMeter := ctx.GasMeter() multipliedGasMeter := NewMultipliedGasMeter(sdkGasMeter, VMGasRegister) gasLimit := VMGasRegister.RuntimeGasForContract(ctx) - clientID, err := getClientID(clientStore) - if err != nil { - return nil, errorsmod.Wrap(err, "failed to retrieve clientID for wasm contract query") - } env := getEnv(ctx, clientID) ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: query") - resp, gasUsed, err := ibcwasm.GetVM().Query(checksum, env, msg, newStoreAdapter(clientStore), wasmvmAPI, newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, costJSONDeserialization) + resp, gasUsed, err := vm.Query(checksum, env, msg, newStoreAdapter(clientStore), wasmvmAPI, newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, costJSONDeserialization) VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) return resp, err } // WasmInstantiate accepts a message to instantiate a wasm contract, JSON encodes it and calls instantiateContract. -func WasmInstantiate(ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, cs *ClientState, payload InstantiateMessage) error { +func WasmInstantiate(ctx sdk.Context, vm ibcwasm.WasmEngine, clientID string, cdc codec.BinaryCodec, clientStore storetypes.KVStore, cs *ClientState, payload InstantiateMessage) error { encodedData, err := json.Marshal(payload) if err != nil { return errorsmod.Wrap(err, "failed to marshal payload for wasm contract instantiation") } checksum := cs.Checksum - res, err := instantiateContract(ctx, clientStore, checksum, encodedData) + res, err := instantiateContract(ctx, vm, clientID, clientStore, checksum, encodedData) if err != nil { return errorsmod.Wrap(ErrVMError, err.Error()) } @@ -146,7 +134,7 @@ func WasmInstantiate(ctx sdk.Context, cdc codec.BinaryCodec, clientStore storety // - the response of the contract call contains non-empty events // - the response of the contract call contains non-empty attributes // - the data bytes of the response cannot be unmarshaled into the result type -func WasmSudo[T ContractResult](ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, cs *ClientState, payload SudoMsg) (T, error) { +func WasmSudo[T ContractResult](ctx sdk.Context, vm ibcwasm.WasmEngine, clientID string, cdc codec.BinaryCodec, clientStore storetypes.KVStore, cs *ClientState, payload SudoMsg) (T, error) { var result T encodedData, err := json.Marshal(payload) @@ -155,7 +143,7 @@ func WasmSudo[T ContractResult](ctx sdk.Context, cdc codec.BinaryCodec, clientSt } checksum := cs.Checksum - res, err := callContract(ctx, clientStore, checksum, encodedData) + res, err := callContract(ctx, vm, clientID, clientStore, checksum, encodedData) if err != nil { return result, errorsmod.Wrap(ErrVMError, err.Error()) } @@ -187,8 +175,8 @@ func WasmSudo[T ContractResult](ctx sdk.Context, cdc codec.BinaryCodec, clientSt // wasmMigrate migrate calls the migrate entry point of the contract with the given payload and returns the result. // wasmMigrate returns an error if: // - the contract migration returns an error -func wasmMigrate(ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, cs *ClientState, clientID string, payload []byte) error { - res, err := migrateContract(ctx, clientID, clientStore, cs.Checksum, payload) +func wasmMigrate(ctx sdk.Context, vm ibcwasm.WasmEngine, cdc codec.BinaryCodec, clientStore storetypes.KVStore, cs *ClientState, clientID string, payload []byte) error { + res, err := migrateContract(ctx, vm, clientID, clientStore, cs.Checksum, payload) if err != nil { return errorsmod.Wrap(ErrVMError, err.Error()) } @@ -209,7 +197,7 @@ func wasmMigrate(ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes. // - the payload cannot be marshaled to JSON // - the contract query returns an error // - the data bytes of the response cannot be unmarshal into the result type -func WasmQuery[T ContractResult](ctx sdk.Context, clientStore storetypes.KVStore, cs *ClientState, payload QueryMsg) (T, error) { +func WasmQuery[T ContractResult](ctx sdk.Context, vm ibcwasm.WasmEngine, clientID string, clientStore storetypes.KVStore, cs *ClientState, payload QueryMsg) (T, error) { var result T encodedData, err := json.Marshal(payload) @@ -217,7 +205,7 @@ func WasmQuery[T ContractResult](ctx sdk.Context, clientStore storetypes.KVStore return result, errorsmod.Wrap(err, "failed to marshal payload for wasm query") } - res, err := queryContract(ctx, clientStore, cs.Checksum, encodedData) + res, err := queryContract(ctx, vm, clientID, clientStore, cs.Checksum, encodedData) if err != nil { return result, errorsmod.Wrap(ErrVMError, err.Error()) } diff --git a/modules/light-clients/08-wasm/types/vm_test.go b/modules/light-clients/08-wasm/types/vm_test.go index a2d4a2d2b9e..70c0cd34b49 100644 --- a/modules/light-clients/08-wasm/types/vm_test.go +++ b/modules/light-clients/08-wasm/types/vm_test.go @@ -175,7 +175,8 @@ func (suite *TypesTestSuite) TestWasmInstantiate() { } clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), defaultWasmClientID) - err := types.WasmInstantiate(suite.chainA.GetContext(), suite.chainA.App.AppCodec(), clientStore, &types.ClientState{Checksum: suite.checksum}, initMsg) + vm := GetSimApp(suite.chainA).WasmClientKeeper.GetVM() + err := types.WasmInstantiate(suite.chainA.GetContext(), vm, defaultWasmClientID, suite.chainA.App.AppCodec(), clientStore, &types.ClientState{Checksum: suite.checksum}, initMsg) expPass := tc.expError == nil if expPass { @@ -313,7 +314,8 @@ func (suite *TypesTestSuite) TestWasmMigrate() { tc.malleate() clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), defaultWasmClientID) - err = types.WasmMigrate(suite.chainA.GetContext(), suite.chainA.App.AppCodec(), clientStore, &types.ClientState{}, defaultWasmClientID, []byte("{}")) + vm := GetSimApp(suite.chainA).WasmClientKeeper.GetVM() + err = types.WasmMigrate(suite.chainA.GetContext(), vm, suite.chainA.App.AppCodec(), clientStore, &types.ClientState{}, defaultWasmClientID, []byte("{}")) expPass := tc.expError == nil if expPass { @@ -398,7 +400,8 @@ func (suite *TypesTestSuite) TestWasmQuery() { tc.malleate() - res, err := types.WasmQuery[types.StatusResult](suite.chainA.GetContext(), clientStore, wasmClientState, payload) + vm := GetSimApp(suite.chainA).WasmClientKeeper.GetVM() + res, err := types.WasmQuery[types.StatusResult](suite.chainA.GetContext(), vm, endpoint.ClientID, clientStore, wasmClientState, payload) expPass := tc.expError == nil if expPass { @@ -576,7 +579,8 @@ func (suite *TypesTestSuite) TestWasmSudo() { tc.malleate() - res, err := types.WasmSudo[types.UpdateStateResult](suite.chainA.GetContext(), suite.chainA.App.AppCodec(), clientStore, wasmClientState, payload) + vm := GetSimApp(suite.chainA).WasmClientKeeper.GetVM() + res, err := types.WasmSudo[types.UpdateStateResult](suite.chainA.GetContext(), vm, endpoint.ClientID, suite.chainA.App.AppCodec(), clientStore, wasmClientState, payload) expPass := tc.expError == nil if expPass { From c3ddc512bcdbef04b6bafd3bfd63c11e31265610 Mon Sep 17 00:00:00 2001 From: DimitrisJim Date: Thu, 4 Apr 2024 02:36:31 +0300 Subject: [PATCH 03/24] Move migrateContract from clientState to keeper method. - Matches moves for other functions. - Move is required to address future circular dependency for state management (keeper would import types and types would import keeper) - Rm migrate_contract(test).go files. --- .../light-clients/08-wasm/keeper/keeper.go | 24 ++- .../08-wasm/keeper/keeper_test.go | 147 +++++++++++++++++ .../08-wasm/keeper/msg_server.go | 2 +- .../08-wasm/types/export_test.go | 9 - .../08-wasm/types/migrate_contract.go | 43 ----- .../08-wasm/types/migrate_contract_test.go | 154 ------------------ modules/light-clients/08-wasm/types/vm.go | 6 +- 7 files changed, 172 insertions(+), 213 deletions(-) delete mode 100644 modules/light-clients/08-wasm/types/migrate_contract.go delete mode 100644 modules/light-clients/08-wasm/types/migrate_contract_test.go diff --git a/modules/light-clients/08-wasm/keeper/keeper.go b/modules/light-clients/08-wasm/keeper/keeper.go index 168db320027..271abf26fec 100644 --- a/modules/light-clients/08-wasm/keeper/keeper.go +++ b/modules/light-clients/08-wasm/keeper/keeper.go @@ -99,7 +99,8 @@ func (k Keeper) storeWasmCode(ctx sdk.Context, code []byte, storeFn func(code wa return checksum, nil } -func (k Keeper) migrateContractCode(ctx sdk.Context, clientID string, newChecksum, migrateMsg []byte) error { +// TODO(jim): Use an export_test.go here too and make private again. +func (k Keeper) MigrateContractCode(ctx sdk.Context, clientID string, newChecksum, migrateMsg []byte) error { wasmClientState, err := k.GetWasmClientState(ctx, clientID) if err != nil { return errorsmod.Wrap(err, "failed to retrieve wasm client state") @@ -107,10 +108,27 @@ func (k Keeper) migrateContractCode(ctx sdk.Context, clientID string, newChecksu oldChecksum := wasmClientState.Checksum clientStore := k.clientKeeper.ClientStore(ctx, clientID) + clientState, found := types.GetClientState(clientStore, k.cdc) + if !found { + return errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID) + } + + if !types.HasChecksum(ctx, newChecksum) { + return types.ErrWasmChecksumNotFound + } - err = wasmClientState.MigrateContract(ctx, k.GetVM(), k.cdc, clientStore, clientID, newChecksum, migrateMsg) + if bytes.Equal(clientState.Checksum, newChecksum) { + return errorsmod.Wrapf(types.ErrWasmCodeExists, "new checksum (%s) is the same as current checksum (%s)", hex.EncodeToString(newChecksum), hex.EncodeToString(clientState.Checksum)) + } + + // update the checksum, this needs to be done before the contract migration + // so that wasmMigrate can call the right code. Note that this is not + // persisted to the client store. + clientState.Checksum = newChecksum + + err = types.WasmMigrate(ctx, k.GetVM(), k.cdc, clientStore, clientState, clientID, migrateMsg) if err != nil { - return errorsmod.Wrap(err, "contract migration failed") + return err } // client state may be updated by the contract migration diff --git a/modules/light-clients/08-wasm/keeper/keeper_test.go b/modules/light-clients/08-wasm/keeper/keeper_test.go index 03edd93da30..81a89325c0e 100644 --- a/modules/light-clients/08-wasm/keeper/keeper_test.go +++ b/modules/light-clients/08-wasm/keeper/keeper_test.go @@ -19,6 +19,7 @@ import ( authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper" wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing/simapp" @@ -324,3 +325,149 @@ func (suite *KeeperTestSuite) TestInitializedPinnedCodes() { }) } } + +func (suite *KeeperTestSuite) TestMigrateContract() { + var ( + oldHash []byte + newHash []byte + payload []byte + expClientState *types.ClientState + ) + + testCases := []struct { + name string + malleate func() + expErr error + }{ + // TODO(Jim): Figure this out, appears like a bad setup after moving from types test suite. + /* + { + "success: no update to client state", + func() { + err := ibcwasm.Checksums.Set(suite.chainA.GetContext(), newHash) + suite.Require().NoError(err) + + payload = []byte{1} + expChecksum := wasmvmtypes.ForceNewChecksum(hex.EncodeToString(newHash)) + + suite.mockVM.MigrateFn = func(checksum wasmvm.Checksum, env wasmvmtypes.Env, msg []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + suite.Require().Equal(expChecksum, checksum) + suite.Require().Equal(defaultWasmClientID, env.Contract.Address) + suite.Require().Equal(payload, msg) + + data, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: data}}, wasmtesting.DefaultGasUsed, nil + } + }, + nil, + }, + */ + { + "success: update client state", + func() { + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + expClientState = types.NewClientState([]byte{1}, newHash, clienttypes.NewHeight(2000, 2)) + store.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), expClientState)) + + data, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: data}}, wasmtesting.DefaultGasUsed, nil + } + }, + nil, + }, + { + "failure: new and old checksum are the same", + func() { + newHash = oldHash + // this should not be called + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + panic("unreachable") + } + }, + types.ErrWasmCodeExists, + }, + { + "failure: checksum not found", + func() { + err := ibcwasm.Checksums.Remove(suite.chainA.GetContext(), newHash) + suite.Require().NoError(err) + }, + types.ErrWasmChecksumNotFound, + }, + { + "failure: vm returns error", + func() { + err := ibcwasm.Checksums.Set(suite.chainA.GetContext(), newHash) + suite.Require().NoError(err) + + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + return nil, wasmtesting.DefaultGasUsed, wasmtesting.ErrMockVM + } + }, + types.ErrVMError, + }, + { + "failure: contract returns error", + func() { + err := ibcwasm.Checksums.Set(suite.chainA.GetContext(), newHash) + suite.Require().NoError(err) + + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + return &wasmvmtypes.ContractResult{Err: wasmtesting.ErrMockContract.Error()}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmContractCallFailed, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + storeWasmCode(suite, wasmtesting.Code) + + var err error + oldHash, err = types.CreateChecksum(wasmtesting.Code) + suite.Require().NoError(err) + newHash, err = types.CreateChecksum(wasmtesting.CreateMockContract([]byte{1, 2, 3})) + suite.Require().NoError(err) + + err = ibcwasm.Checksums.Set(suite.chainA.GetContext(), newHash) + suite.Require().NoError(err) + + endpointA := wasmtesting.NewWasmEndpoint(suite.chainA) + err = endpointA.CreateClient() + suite.Require().NoError(err) + + // clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpointA.ClientID) + clientState, ok := endpointA.GetClientState().(*types.ClientState) + suite.Require().True(ok) + + expClientState = clientState + + tc.malleate() + + wasmKeeper := GetSimApp(suite.chainA).WasmClientKeeper + err = wasmKeeper.MigrateContractCode(suite.chainA.GetContext(), endpointA.ClientID, newHash, payload) + + // vm := GetSimApp(suite.chainA).WasmClientKeeper.GetVM() + // err = clientState.MigrateContract(suite.chainA.GetContext(), vm, suite.chainA.App.AppCodec(), clientStore, endpointA.ClientID, newHash, payload) + + // updated client state + clientState, ok = endpointA.GetClientState().(*types.ClientState) + suite.Require().True(ok) + + expPass := tc.expErr == nil + if expPass { + suite.Require().NoError(err) + suite.Require().Equal(expClientState, clientState) + } else { + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} diff --git a/modules/light-clients/08-wasm/keeper/msg_server.go b/modules/light-clients/08-wasm/keeper/msg_server.go index 8b8763f586a..04e7da212b6 100644 --- a/modules/light-clients/08-wasm/keeper/msg_server.go +++ b/modules/light-clients/08-wasm/keeper/msg_server.go @@ -65,7 +65,7 @@ func (k Keeper) MigrateContract(goCtx context.Context, msg *types.MsgMigrateCont ctx := sdk.UnwrapSDKContext(goCtx) - err := k.migrateContractCode(ctx, msg.ClientId, msg.Checksum, msg.Msg) + err := k.MigrateContractCode(ctx, msg.ClientId, msg.Checksum, msg.Msg) if err != nil { return nil, errorsmod.Wrap(err, "failed to migrate contract") } diff --git a/modules/light-clients/08-wasm/types/export_test.go b/modules/light-clients/08-wasm/types/export_test.go index 70c5ca39c9b..134e114f223 100644 --- a/modules/light-clients/08-wasm/types/export_test.go +++ b/modules/light-clients/08-wasm/types/export_test.go @@ -2,10 +2,6 @@ package types import ( storetypes "cosmossdk.io/store/types" - - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" ) /* @@ -36,8 +32,3 @@ func (ws migrateClientWrappedStore) GetStore(key []byte) (storetypes.KVStore, bo func SplitPrefix(key []byte) ([]byte, []byte) { return splitPrefix(key) } - -// WasmMigrate wraps wasmMigrate and is used solely for testing. -func WasmMigrate(ctx sdk.Context, vm ibcwasm.WasmEngine, cdc codec.BinaryCodec, clientStore storetypes.KVStore, cs *ClientState, clientID string, payload []byte) error { - return wasmMigrate(ctx, vm, cdc, clientStore, cs, clientID, payload) -} diff --git a/modules/light-clients/08-wasm/types/migrate_contract.go b/modules/light-clients/08-wasm/types/migrate_contract.go deleted file mode 100644 index d34e1fd28ea..00000000000 --- a/modules/light-clients/08-wasm/types/migrate_contract.go +++ /dev/null @@ -1,43 +0,0 @@ -package types - -import ( - "bytes" - "encoding/hex" - - errorsmod "cosmossdk.io/errors" - storetypes "cosmossdk.io/store/types" - - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" -) - -// MigrateContract calls the migrate entry point on the contract with the given -// migrateMsg. The contract must exist and the checksum must be found in the -// store. If the checksum is the same as the current checksum, an error is returned. -// This does not update the checksum in the client state. -// TODO(jim): Move to lcm? Keeper? -func (cs ClientState) MigrateContract( - ctx sdk.Context, vm ibcwasm.WasmEngine, cdc codec.BinaryCodec, clientStore storetypes.KVStore, - clientID string, newChecksum, migrateMsg []byte, -) error { - if !HasChecksum(ctx, newChecksum) { - return ErrWasmChecksumNotFound - } - - if bytes.Equal(cs.Checksum, newChecksum) { - return errorsmod.Wrapf(ErrWasmCodeExists, "new checksum (%s) is the same as current checksum (%s)", hex.EncodeToString(newChecksum), hex.EncodeToString(cs.Checksum)) - } - - // update the checksum, this needs to be done before the contract migration - // so that wasmMigrate can call the right code. Note that this is not - // persisted to the client store. - cs.Checksum = newChecksum - - err := wasmMigrate(ctx, vm, cdc, clientStore, &cs, clientID, migrateMsg) - if err != nil { - return err - } - - return nil -} diff --git a/modules/light-clients/08-wasm/types/migrate_contract_test.go b/modules/light-clients/08-wasm/types/migrate_contract_test.go deleted file mode 100644 index 2f0d1ad3a2b..00000000000 --- a/modules/light-clients/08-wasm/types/migrate_contract_test.go +++ /dev/null @@ -1,154 +0,0 @@ -package types_test - -import ( - "encoding/hex" - "encoding/json" - - wasmvm "github.com/CosmWasm/wasmvm/v2" - wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" - - "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" - wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing" - "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" - clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" - host "github.com/cosmos/ibc-go/v8/modules/core/24-host" -) - -func (suite *TypesTestSuite) TestMigrateContract() { - var ( - oldHash []byte - newHash []byte - payload []byte - expClientState *types.ClientState - ) - - testCases := []struct { - name string - malleate func() - expErr error - }{ - { - "success: no update to client state", - func() { - err := ibcwasm.Checksums.Set(suite.chainA.GetContext(), newHash) - suite.Require().NoError(err) - - payload = []byte{1} - expChecksum := wasmvmtypes.ForceNewChecksum(hex.EncodeToString(newHash)) - - suite.mockVM.MigrateFn = func(checksum wasmvm.Checksum, env wasmvmtypes.Env, msg []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - suite.Require().Equal(expChecksum, checksum) - suite.Require().Equal(defaultWasmClientID, env.Contract.Address) - suite.Require().Equal(payload, msg) - - data, err := json.Marshal(types.EmptyResult{}) - suite.Require().NoError(err) - - return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: data}}, wasmtesting.DefaultGasUsed, nil - } - }, - nil, - }, - { - "success: update client state", - func() { - suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - expClientState = types.NewClientState([]byte{1}, newHash, clienttypes.NewHeight(2000, 2)) - store.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), expClientState)) - - data, err := json.Marshal(types.EmptyResult{}) - suite.Require().NoError(err) - - return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: data}}, wasmtesting.DefaultGasUsed, nil - } - }, - nil, - }, - { - "failure: new and old checksum are the same", - func() { - newHash = oldHash - // this should not be called - suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - panic("unreachable") - } - }, - types.ErrWasmCodeExists, - }, - { - "failure: checksum not found", - func() { - err := ibcwasm.Checksums.Remove(suite.chainA.GetContext(), newHash) - suite.Require().NoError(err) - }, - types.ErrWasmChecksumNotFound, - }, - { - "failure: vm returns error", - func() { - err := ibcwasm.Checksums.Set(suite.chainA.GetContext(), newHash) - suite.Require().NoError(err) - - suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - return nil, wasmtesting.DefaultGasUsed, wasmtesting.ErrMockVM - } - }, - types.ErrVMError, - }, - { - "failure: contract returns error", - func() { - err := ibcwasm.Checksums.Set(suite.chainA.GetContext(), newHash) - suite.Require().NoError(err) - - suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - return &wasmvmtypes.ContractResult{Err: wasmtesting.ErrMockContract.Error()}, wasmtesting.DefaultGasUsed, nil - } - }, - types.ErrWasmContractCallFailed, - }, - } - - for _, tc := range testCases { - tc := tc - suite.Run(tc.name, func() { - suite.SetupWasmWithMockVM() - - var err error - oldHash, err = types.CreateChecksum(wasmtesting.Code) - suite.Require().NoError(err) - newHash, err = types.CreateChecksum(wasmtesting.CreateMockContract([]byte{1, 2, 3})) - suite.Require().NoError(err) - - err = ibcwasm.Checksums.Set(suite.chainA.GetContext(), newHash) - suite.Require().NoError(err) - - endpointA := wasmtesting.NewWasmEndpoint(suite.chainA) - err = endpointA.CreateClient() - suite.Require().NoError(err) - - clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpointA.ClientID) - clientState, ok := endpointA.GetClientState().(*types.ClientState) - suite.Require().True(ok) - - expClientState = clientState - - tc.malleate() - - vm := GetSimApp(suite.chainA).WasmClientKeeper.GetVM() - err = clientState.MigrateContract(suite.chainA.GetContext(), vm, suite.chainA.App.AppCodec(), clientStore, endpointA.ClientID, newHash, payload) - - // updated client state - clientState, ok = endpointA.GetClientState().(*types.ClientState) - suite.Require().True(ok) - - expPass := tc.expErr == nil - if expPass { - suite.Require().NoError(err) - suite.Require().Equal(expClientState, clientState) - } else { - suite.Require().ErrorIs(err, tc.expErr) - } - }) - } -} diff --git a/modules/light-clients/08-wasm/types/vm.go b/modules/light-clients/08-wasm/types/vm.go index 42b3d9baef5..9be6e877c0d 100644 --- a/modules/light-clients/08-wasm/types/vm.go +++ b/modules/light-clients/08-wasm/types/vm.go @@ -172,10 +172,10 @@ func WasmSudo[T ContractResult](ctx sdk.Context, vm ibcwasm.WasmEngine, clientID return result, nil } -// wasmMigrate migrate calls the migrate entry point of the contract with the given payload and returns the result. -// wasmMigrate returns an error if: +// WasmMigrate migrate calls the migrate entry point of the contract with the given payload and returns the result. +// WasmMigrate returns an error if: // - the contract migration returns an error -func wasmMigrate(ctx sdk.Context, vm ibcwasm.WasmEngine, cdc codec.BinaryCodec, clientStore storetypes.KVStore, cs *ClientState, clientID string, payload []byte) error { +func WasmMigrate(ctx sdk.Context, vm ibcwasm.WasmEngine, cdc codec.BinaryCodec, clientStore storetypes.KVStore, cs *ClientState, clientID string, payload []byte) error { res, err := migrateContract(ctx, vm, clientID, clientStore, cs.Checksum, payload) if err != nil { return errorsmod.Wrap(ErrVMError, err.Error()) From 6e9d4a6cebd92cdedbc963ad61ce01fd1f4c9f25 Mon Sep 17 00:00:00 2001 From: DimitrisJim Date: Thu, 4 Apr 2024 04:04:23 +0300 Subject: [PATCH 04/24] Move state management to keeper. - Remove globals for state (Checksums) - Move access functions from types to keeper, move tests. - Update calls using global to instead go through keeper. --- .../08-wasm/internal/ibcwasm/wasm.go | 24 ---- .../light-clients/08-wasm/keeper/genesis.go | 2 +- .../08-wasm/keeper/genesis_test.go | 2 +- .../08-wasm/keeper/grpc_query.go | 7 +- .../light-clients/08-wasm/keeper/keeper.go | 49 ++++++- .../08-wasm/keeper/keeper_test.go | 135 +++++++++++++++++- .../light-clients/08-wasm/keeper/keeper_vm.go | 12 +- .../08-wasm/keeper/migrations.go | 3 +- .../08-wasm/keeper/migrations_test.go | 3 +- .../08-wasm/keeper/msg_server.go | 5 +- .../08-wasm/keeper/msg_server_test.go | 6 +- .../08-wasm/keeper/snapshotter.go | 2 +- .../08-wasm/light_client_module.go | 4 +- .../08-wasm/light_client_module_test.go | 4 +- modules/light-clients/08-wasm/types/keys.go | 7 + modules/light-clients/08-wasm/types/store.go | 34 ----- .../light-clients/08-wasm/types/store_test.go | 118 --------------- 17 files changed, 210 insertions(+), 207 deletions(-) diff --git a/modules/light-clients/08-wasm/internal/ibcwasm/wasm.go b/modules/light-clients/08-wasm/internal/ibcwasm/wasm.go index 4bfd58bf9b3..64243047858 100644 --- a/modules/light-clients/08-wasm/internal/ibcwasm/wasm.go +++ b/modules/light-clients/08-wasm/internal/ibcwasm/wasm.go @@ -2,21 +2,11 @@ package ibcwasm import ( "errors" - - "cosmossdk.io/collections" - storetypes "cosmossdk.io/core/store" ) var ( queryRouter QueryRouter queryPlugins QueryPluginsI - - // state management - Schema collections.Schema - Checksums collections.KeySet[[]byte] - - // ChecksumsKey is the key under which all checksums are stored - ChecksumsKey = collections.NewPrefix(0) ) // SetQueryRouter sets the custom wasm query router for the 08-wasm module. @@ -45,17 +35,3 @@ func SetQueryPlugins(plugins QueryPluginsI) { func GetQueryPlugins() QueryPluginsI { return queryPlugins } - -// SetupWasmStoreService sets up the 08-wasm module's collections. -func SetupWasmStoreService(storeService storetypes.KVStoreService) { - sb := collections.NewSchemaBuilder(storeService) - - Checksums = collections.NewKeySet(sb, ChecksumsKey, "checksums", collections.BytesKey) - - schema, err := sb.Build() - if err != nil { - panic(err) - } - - Schema = schema -} diff --git a/modules/light-clients/08-wasm/keeper/genesis.go b/modules/light-clients/08-wasm/keeper/genesis.go index 51f61018f81..53ada55069f 100644 --- a/modules/light-clients/08-wasm/keeper/genesis.go +++ b/modules/light-clients/08-wasm/keeper/genesis.go @@ -28,7 +28,7 @@ func (k Keeper) InitGenesis(ctx sdk.Context, gs types.GenesisState) error { // ExportGenesis returns the 08-wasm module's exported genesis. This includes the code // for all contracts previously stored. func (k Keeper) ExportGenesis(ctx sdk.Context) types.GenesisState { - checksums, err := types.GetAllChecksums(ctx) + checksums, err := k.GetAllChecksums(ctx) if err != nil { panic(err) } diff --git a/modules/light-clients/08-wasm/keeper/genesis_test.go b/modules/light-clients/08-wasm/keeper/genesis_test.go index 0daa4acc739..8accae7476c 100644 --- a/modules/light-clients/08-wasm/keeper/genesis_test.go +++ b/modules/light-clients/08-wasm/keeper/genesis_test.go @@ -56,7 +56,7 @@ func (suite *KeeperTestSuite) TestInitGenesis() { suite.Require().NoError(err) var storedHashes []string - checksums, err := types.GetAllChecksums(suite.chainA.GetContext()) + checksums, err := GetSimApp(suite.chainA).WasmClientKeeper.GetAllChecksums(suite.chainA.GetContext()) suite.Require().NoError(err) for _, hash := range checksums { diff --git a/modules/light-clients/08-wasm/keeper/grpc_query.go b/modules/light-clients/08-wasm/keeper/grpc_query.go index 8e18ba6e313..21b2a558894 100644 --- a/modules/light-clients/08-wasm/keeper/grpc_query.go +++ b/modules/light-clients/08-wasm/keeper/grpc_query.go @@ -13,7 +13,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkquery "github.com/cosmos/cosmos-sdk/types/query" - "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" ) @@ -31,7 +30,7 @@ func (k Keeper) Code(goCtx context.Context, req *types.QueryCodeRequest) (*types } // Only return checksums we previously stored, not arbitrary checksums that might be stored via e.g Wasmd. - if !types.HasChecksum(sdk.UnwrapSDKContext(goCtx), checksum) { + if !k.HasChecksum(sdk.UnwrapSDKContext(goCtx), checksum) { return nil, status.Error(codes.NotFound, errorsmod.Wrap(types.ErrWasmChecksumNotFound, req.Checksum).Error()) } @@ -46,10 +45,10 @@ func (k Keeper) Code(goCtx context.Context, req *types.QueryCodeRequest) (*types } // Checksums implements the Query/Checksums gRPC method. It returns a list of hex encoded checksums stored. -func (Keeper) Checksums(goCtx context.Context, req *types.QueryChecksumsRequest) (*types.QueryChecksumsResponse, error) { +func (k Keeper) Checksums(goCtx context.Context, req *types.QueryChecksumsRequest) (*types.QueryChecksumsResponse, error) { checksums, pageRes, err := sdkquery.CollectionPaginate( goCtx, - ibcwasm.Checksums, + k.GetChecksums(), req.Pagination, func(key []byte, value collections.NoValue) (string, error) { return hex.EncodeToString(key), nil diff --git a/modules/light-clients/08-wasm/keeper/keeper.go b/modules/light-clients/08-wasm/keeper/keeper.go index 271abf26fec..c63befec836 100644 --- a/modules/light-clients/08-wasm/keeper/keeper.go +++ b/modules/light-clients/08-wasm/keeper/keeper.go @@ -2,10 +2,12 @@ package keeper import ( "bytes" + "context" "encoding/hex" wasmvm "github.com/CosmWasm/wasmvm/v2" + "cosmossdk.io/collections" "cosmossdk.io/core/store" errorsmod "cosmossdk.io/errors" @@ -25,6 +27,9 @@ type Keeper struct { vm ibcwasm.WasmEngine cdc codec.BinaryCodec + // state management + schema collections.Schema + checksums collections.KeySet[[]byte] storeService store.KVStoreService clientKeeper types.ClientKeeper @@ -47,6 +52,10 @@ func (k Keeper) GetVM() ibcwasm.WasmEngine { return k.vm } +func (k Keeper) GetChecksums() collections.KeySet[[]byte] { + return k.checksums +} + func (k Keeper) storeWasmCode(ctx sdk.Context, code []byte, storeFn func(code wasmvm.WasmCode, gasLimit uint64) (wasmvm.Checksum, uint64, error)) ([]byte, error) { var err error if types.IsGzip(code) { @@ -68,7 +77,7 @@ func (k Keeper) storeWasmCode(ctx sdk.Context, code []byte, storeFn func(code wa return nil, errorsmod.Wrap(err, "wasm bytecode checksum failed") } - if types.HasChecksum(ctx, checksum) { + if k.HasChecksum(ctx, checksum) { return nil, types.ErrWasmCodeExists } @@ -91,7 +100,7 @@ func (k Keeper) storeWasmCode(ctx sdk.Context, code []byte, storeFn func(code wa } // store the checksum - err = ibcwasm.Checksums.Set(ctx, checksum) + err = k.GetChecksums().Set(ctx, checksum) if err != nil { return nil, errorsmod.Wrap(err, "failed to store checksum") } @@ -113,7 +122,7 @@ func (k Keeper) MigrateContractCode(ctx sdk.Context, clientID string, newChecksu return errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID) } - if !types.HasChecksum(ctx, newChecksum) { + if !k.HasChecksum(ctx, newChecksum) { return types.ErrWasmChecksumNotFound } @@ -164,10 +173,42 @@ func (k Keeper) GetWasmClientState(ctx sdk.Context, clientID string) (*types.Cli return wasmClientState, nil } +// GetAllChecksums is a helper to get all checksums from the store. +// It returns an empty slice if no checksums are found +func (k Keeper) GetAllChecksums(ctx context.Context) ([]types.Checksum, error) { + iterator, err := k.GetChecksums().Iterate(ctx, nil) + if err != nil { + return nil, err + } + + keys, err := iterator.Keys() + if err != nil { + return nil, err + } + + checksums := []types.Checksum{} + for _, key := range keys { + checksums = append(checksums, key) + } + + return checksums, nil +} + +// HasChecksum returns true if the given checksum exists in the store and +// false otherwise. +func (k Keeper) HasChecksum(ctx context.Context, checksum types.Checksum) bool { + found, err := k.GetChecksums().Has(ctx, checksum) + if err != nil { + return false + } + + return found +} + // InitializePinnedCodes updates wasmvm to pin to cache all contracts marked as pinned // TODO(jim): Make meth on Keeper probably. func InitializePinnedCodes(ctx sdk.Context, k Keeper) error { - checksums, err := types.GetAllChecksums(ctx) + checksums, err := k.GetAllChecksums(ctx) if err != nil { return err } diff --git a/modules/light-clients/08-wasm/keeper/keeper_test.go b/modules/light-clients/08-wasm/keeper/keeper_test.go index 81a89325c0e..2369e50393f 100644 --- a/modules/light-clients/08-wasm/keeper/keeper_test.go +++ b/modules/light-clients/08-wasm/keeper/keeper_test.go @@ -19,7 +19,6 @@ import ( authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" - "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper" wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing/simapp" @@ -393,7 +392,8 @@ func (suite *KeeperTestSuite) TestMigrateContract() { { "failure: checksum not found", func() { - err := ibcwasm.Checksums.Remove(suite.chainA.GetContext(), newHash) + keeper := GetSimApp(suite.chainA).WasmClientKeeper + err := keeper.GetChecksums().Remove(suite.chainA.GetContext(), newHash) suite.Require().NoError(err) }, types.ErrWasmChecksumNotFound, @@ -401,7 +401,8 @@ func (suite *KeeperTestSuite) TestMigrateContract() { { "failure: vm returns error", func() { - err := ibcwasm.Checksums.Set(suite.chainA.GetContext(), newHash) + keeper := GetSimApp(suite.chainA).WasmClientKeeper + err := keeper.GetChecksums().Set(suite.chainA.GetContext(), newHash) suite.Require().NoError(err) suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { @@ -413,7 +414,8 @@ func (suite *KeeperTestSuite) TestMigrateContract() { { "failure: contract returns error", func() { - err := ibcwasm.Checksums.Set(suite.chainA.GetContext(), newHash) + keeper := GetSimApp(suite.chainA).WasmClientKeeper + err := keeper.GetChecksums().Set(suite.chainA.GetContext(), newHash) suite.Require().NoError(err) suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { @@ -436,7 +438,8 @@ func (suite *KeeperTestSuite) TestMigrateContract() { newHash, err = types.CreateChecksum(wasmtesting.CreateMockContract([]byte{1, 2, 3})) suite.Require().NoError(err) - err = ibcwasm.Checksums.Set(suite.chainA.GetContext(), newHash) + keeper := GetSimApp(suite.chainA).WasmClientKeeper + err = keeper.GetChecksums().Set(suite.chainA.GetContext(), newHash) suite.Require().NoError(err) endpointA := wasmtesting.NewWasmEndpoint(suite.chainA) @@ -471,3 +474,125 @@ func (suite *KeeperTestSuite) TestMigrateContract() { }) } } + +func (suite *KeeperTestSuite) TestGetChecksums() { + testCases := []struct { + name string + malleate func() + expResult func(checksums []types.Checksum) + }{ + { + "success: no contract stored.", + func() {}, + func(checksums []types.Checksum) { + suite.Require().Len(checksums, 0) + }, + }, + { + "success: default mock vm contract stored.", + func() { + suite.SetupWasmWithMockVM() + storeWasmCode(suite, wasmtesting.Code) + }, + func(checksums []types.Checksum) { + suite.Require().Len(checksums, 1) + expectedChecksum, err := types.CreateChecksum(wasmtesting.Code) + suite.Require().NoError(err) + suite.Require().Equal(expectedChecksum, checksums[0]) + }, + }, + { + "success: non-empty checksums", + func() { + suite.SetupWasmWithMockVM() + storeWasmCode(suite, wasmtesting.Code) + + err := GetSimApp(suite.chainA).WasmClientKeeper.GetChecksums().Set(suite.chainA.GetContext(), types.Checksum("checksum")) + suite.Require().NoError(err) + }, + func(checksums []types.Checksum) { + suite.Require().Len(checksums, 2) + suite.Require().Contains(checksums, types.Checksum("checksum")) + }, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + tc.malleate() + + checksums, err := GetSimApp(suite.chainA).WasmClientKeeper.GetAllChecksums(suite.chainA.GetContext()) + suite.Require().NoError(err) + tc.expResult(checksums) + }) + } +} + +func (suite *KeeperTestSuite) TestAddChecksum() { + suite.SetupWasmWithMockVM() + storeWasmCode(suite, wasmtesting.Code) + + wasmKeeper := GetSimApp(suite.chainA).WasmClientKeeper + + checksums, err := wasmKeeper.GetAllChecksums(suite.chainA.GetContext()) + suite.Require().NoError(err) + // default mock vm contract is stored + suite.Require().Len(checksums, 1) + + checksum1 := types.Checksum("checksum1") + checksum2 := types.Checksum("checksum2") + err = wasmKeeper.GetChecksums().Set(suite.chainA.GetContext(), checksum1) + suite.Require().NoError(err) + err = wasmKeeper.GetChecksums().Set(suite.chainA.GetContext(), checksum2) + suite.Require().NoError(err) + + // Test adding the same checksum twice + err = wasmKeeper.GetChecksums().Set(suite.chainA.GetContext(), checksum1) + suite.Require().NoError(err) + + checksums, err = wasmKeeper.GetAllChecksums(suite.chainA.GetContext()) + suite.Require().NoError(err) + suite.Require().Len(checksums, 3) + suite.Require().Contains(checksums, checksum1) + suite.Require().Contains(checksums, checksum2) +} + +func (suite *KeeperTestSuite) TestHasChecksum() { + var checksum types.Checksum + + testCases := []struct { + name string + malleate func() + exprResult bool + }{ + { + "success: checksum exists", + func() { + checksum = types.Checksum("checksum") + err := GetSimApp(suite.chainA).WasmClientKeeper.GetChecksums().Set(suite.chainA.GetContext(), checksum) + suite.Require().NoError(err) + }, + true, + }, + { + "success: checksum does not exist", + func() { + checksum = types.Checksum("non-existent-checksum") + }, + false, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + + tc.malleate() + + result := GetSimApp(suite.chainA).WasmClientKeeper.HasChecksum(suite.chainA.GetContext(), checksum) + suite.Require().Equal(tc.exprResult, result) + }) + } +} diff --git a/modules/light-clients/08-wasm/keeper/keeper_vm.go b/modules/light-clients/08-wasm/keeper/keeper_vm.go index 12ed2880cd6..80d9b076a69 100644 --- a/modules/light-clients/08-wasm/keeper/keeper_vm.go +++ b/modules/light-clients/08-wasm/keeper/keeper_vm.go @@ -9,6 +9,7 @@ import ( wasmvm "github.com/CosmWasm/wasmvm/v2" + "cosmossdk.io/collections" "cosmossdk.io/core/store" "github.com/cosmos/cosmos-sdk/codec" @@ -45,14 +46,24 @@ func NewKeeperWithVM( panic(errors.New("authority must be non-empty")) } + sb := collections.NewSchemaBuilder(storeService) + keeper := &Keeper{ cdc: cdc, vm: vm, + checksums: collections.NewKeySet(sb, types.ChecksumsKey, "checksums", collections.BytesKey), storeService: storeService, clientKeeper: clientKeeper, authority: authority, } + schema, err := sb.Build() + if err != nil { + panic(err) + } + + keeper.schema = schema + // set query plugins to ensure there is a non-nil query plugin // regardless of what options the user provides ibcwasm.SetQueryPlugins(types.NewDefaultQueryPlugins()) @@ -61,7 +72,6 @@ func NewKeeperWithVM( } ibcwasm.SetQueryRouter(queryRouter) - ibcwasm.SetupWasmStoreService(storeService) return *keeper } diff --git a/modules/light-clients/08-wasm/keeper/migrations.go b/modules/light-clients/08-wasm/keeper/migrations.go index 9961a7251a0..c37852e2941 100644 --- a/modules/light-clients/08-wasm/keeper/migrations.go +++ b/modules/light-clients/08-wasm/keeper/migrations.go @@ -3,7 +3,6 @@ package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" ) @@ -32,7 +31,7 @@ func (m Migrator) MigrateChecksums(ctx sdk.Context) error { } for _, hash := range checksums { - if err := ibcwasm.Checksums.Set(ctx, hash); err != nil { + if err := m.keeper.GetChecksums().Set(ctx, hash); err != nil { return err } } diff --git a/modules/light-clients/08-wasm/keeper/migrations_test.go b/modules/light-clients/08-wasm/keeper/migrations_test.go index 39c2d6ab24b..27f16495a86 100644 --- a/modules/light-clients/08-wasm/keeper/migrations_test.go +++ b/modules/light-clients/08-wasm/keeper/migrations_test.go @@ -1,7 +1,6 @@ package keeper_test import ( - "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" ) @@ -37,7 +36,7 @@ func (suite *KeeperTestSuite) TestMigrateWasmStore() { // check that they were stored in KeySet for _, hash := range tc.checksums { - suite.Require().True(ibcwasm.Checksums.Has(suite.chainA.GetContext(), hash)) + suite.Require().True(wasmKeeper.GetChecksums().Has(suite.chainA.GetContext(), hash)) } // check that the data under the old key was deleted diff --git a/modules/light-clients/08-wasm/keeper/msg_server.go b/modules/light-clients/08-wasm/keeper/msg_server.go index 04e7da212b6..19357b27d75 100644 --- a/modules/light-clients/08-wasm/keeper/msg_server.go +++ b/modules/light-clients/08-wasm/keeper/msg_server.go @@ -8,7 +8,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" ibcerrors "github.com/cosmos/ibc-go/v8/modules/core/errors" ) @@ -40,11 +39,11 @@ func (k Keeper) RemoveChecksum(goCtx context.Context, msg *types.MsgRemoveChecks return nil, errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "expected %s, got %s", k.GetAuthority(), msg.Signer) } - if !types.HasChecksum(goCtx, msg.Checksum) { + if !k.HasChecksum(goCtx, msg.Checksum) { return nil, types.ErrWasmChecksumNotFound } - err := ibcwasm.Checksums.Remove(goCtx, msg.Checksum) + err := k.GetChecksums().Remove(goCtx, msg.Checksum) if err != nil { return nil, errorsmod.Wrap(err, "failed to remove checksum") } diff --git a/modules/light-clients/08-wasm/keeper/msg_server_test.go b/modules/light-clients/08-wasm/keeper/msg_server_test.go index b3b67b1996e..6c4dfa59abc 100644 --- a/modules/light-clients/08-wasm/keeper/msg_server_test.go +++ b/modules/light-clients/08-wasm/keeper/msg_server_test.go @@ -12,7 +12,6 @@ import ( authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" - "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" @@ -358,7 +357,8 @@ func (suite *KeeperTestSuite) TestMsgRemoveChecksum() { checksum, err := types.CreateChecksum(mockCode) suite.Require().NoError(err) - err = ibcwasm.Checksums.Set(suite.chainA.GetContext(), checksum) + keeper := GetSimApp(suite.chainA).WasmClientKeeper + err = keeper.GetChecksums().Set(suite.chainA.GetContext(), checksum) suite.Require().NoError(err) expChecksums = append(expChecksums, checksum) @@ -413,7 +413,7 @@ func (suite *KeeperTestSuite) TestMsgRemoveChecksum() { suite.Require().NoError(err) suite.Require().NotNil(res) - checksums, err := types.GetAllChecksums(suite.chainA.GetContext()) + checksums, err := GetSimApp(suite.chainA).WasmClientKeeper.GetAllChecksums(suite.chainA.GetContext()) suite.Require().NoError(err) // Check equality of checksums up to order diff --git a/modules/light-clients/08-wasm/keeper/snapshotter.go b/modules/light-clients/08-wasm/keeper/snapshotter.go index 976e00f17ea..5a4bc97da17 100644 --- a/modules/light-clients/08-wasm/keeper/snapshotter.go +++ b/modules/light-clients/08-wasm/keeper/snapshotter.go @@ -66,7 +66,7 @@ func (ws *WasmSnapshotter) SnapshotExtension(height uint64, payloadWriter snapsh ctx := sdk.NewContext(cacheMS, cmtproto.Header{}, false, nil) - checksums, err := types.GetAllChecksums(ctx) + checksums, err := ws.keeper.GetAllChecksums(ctx) if err != nil { return err } diff --git a/modules/light-clients/08-wasm/light_client_module.go b/modules/light-clients/08-wasm/light_client_module.go index cfa8a90d0d0..22aadbee563 100644 --- a/modules/light-clients/08-wasm/light_client_module.go +++ b/modules/light-clients/08-wasm/light_client_module.go @@ -73,7 +73,7 @@ func (l LightClientModule) Initialize(ctx sdk.Context, clientID string, clientSt clientStore := l.storeProvider.ClientStore(ctx, clientID) // Do not allow initialization of a client with a checksum that hasn't been previously stored via storeWasmCode. - if !types.HasChecksum(ctx, clientState.Checksum) { + if !l.keeper.HasChecksum(ctx, clientState.Checksum) { return errorsmod.Wrapf(types.ErrInvalidChecksum, "checksum (%s) has not been previously stored", hex.EncodeToString(clientState.Checksum)) } @@ -338,7 +338,7 @@ func (l LightClientModule) Status(ctx sdk.Context, clientID string) exported.Sta } // Return unauthorized if the checksum hasn't been previously stored via storeWasmCode. - if !types.HasChecksum(ctx, clientState.Checksum) { + if !l.keeper.HasChecksum(ctx, clientState.Checksum) { return exported.Unauthorized } diff --git a/modules/light-clients/08-wasm/light_client_module_test.go b/modules/light-clients/08-wasm/light_client_module_test.go index 7a7ecddae55..4b6f00628bc 100644 --- a/modules/light-clients/08-wasm/light_client_module_test.go +++ b/modules/light-clients/08-wasm/light_client_module_test.go @@ -11,7 +11,6 @@ import ( errorsmod "cosmossdk.io/errors" - "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" @@ -79,7 +78,8 @@ func (suite *WasmTestSuite) TestStatus() { { "client status is unauthorized: checksum is not stored", func() { - err := ibcwasm.Checksums.Remove(suite.chainA.GetContext(), suite.checksum) + keeper := GetSimApp(suite.chainA).WasmClientKeeper + err := keeper.GetChecksums().Remove(suite.chainA.GetContext(), suite.checksum) suite.Require().NoError(err) }, exported.Unauthorized, diff --git a/modules/light-clients/08-wasm/types/keys.go b/modules/light-clients/08-wasm/types/keys.go index a4a9d90e972..5f026d706a4 100644 --- a/modules/light-clients/08-wasm/types/keys.go +++ b/modules/light-clients/08-wasm/types/keys.go @@ -1,5 +1,7 @@ package types +import "cosmossdk.io/collections" + const ( // ModuleName for the wasm client ModuleName = "08-wasm" @@ -14,3 +16,8 @@ const ( // Deprecated: in favor of collections.KeySet KeyChecksums = "checksums" ) + +var ( + // ChecksumsKey is the key under which all checksums are stored + ChecksumsKey = collections.NewPrefix(0) +) diff --git a/modules/light-clients/08-wasm/types/store.go b/modules/light-clients/08-wasm/types/store.go index e4e3c14c89d..50ae33fcd6b 100644 --- a/modules/light-clients/08-wasm/types/store.go +++ b/modules/light-clients/08-wasm/types/store.go @@ -2,7 +2,6 @@ package types import ( "bytes" - "context" "errors" "io" @@ -15,7 +14,6 @@ import ( "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" host "github.com/cosmos/ibc-go/v8/modules/core/24-host" ) @@ -52,38 +50,6 @@ func CreateChecksum(code []byte) (Checksum, error) { return wasmvm.CreateChecksum(code) } -// GetAllChecksums is a helper to get all checksums from the store. -// It returns an empty slice if no checksums are found -func GetAllChecksums(ctx context.Context) ([]Checksum, error) { - iterator, err := ibcwasm.Checksums.Iterate(ctx, nil) - if err != nil { - return nil, err - } - - keys, err := iterator.Keys() - if err != nil { - return nil, err - } - - checksums := []Checksum{} - for _, key := range keys { - checksums = append(checksums, key) - } - - return checksums, nil -} - -// HasChecksum returns true if the given checksum exists in the store and -// false otherwise. -func HasChecksum(ctx context.Context, checksum Checksum) bool { - found, err := ibcwasm.Checksums.Has(ctx, checksum) - if err != nil { - return false - } - - return found -} - // migrateClientWrappedStore combines two KVStores into one. // // Both stores are used for reads, but only the subjectStore is used for writes. For all operations, the key diff --git a/modules/light-clients/08-wasm/types/store_test.go b/modules/light-clients/08-wasm/types/store_test.go index 880b2733db5..81d0ef212fe 100644 --- a/modules/light-clients/08-wasm/types/store_test.go +++ b/modules/light-clients/08-wasm/types/store_test.go @@ -3,7 +3,6 @@ package types_test import ( storetypes "cosmossdk.io/store/types" - "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" host "github.com/cosmos/ibc-go/v8/modules/core/24-host" @@ -11,123 +10,6 @@ import ( var invalidPrefix = []byte("invalid/") -func (suite *TypesTestSuite) TestGetChecksums() { - testCases := []struct { - name string - malleate func() - expResult func(checksums []types.Checksum) - }{ - { - "success: no contract stored.", - func() {}, - func(checksums []types.Checksum) { - suite.Require().Len(checksums, 0) - }, - }, - { - "success: default mock vm contract stored.", - func() { - suite.SetupWasmWithMockVM() - }, - func(checksums []types.Checksum) { - suite.Require().Len(checksums, 1) - expectedChecksum, err := types.CreateChecksum(wasmtesting.Code) - suite.Require().NoError(err) - suite.Require().Equal(expectedChecksum, checksums[0]) - }, - }, - { - "success: non-empty checksums", - func() { - suite.SetupWasmWithMockVM() - - err := ibcwasm.Checksums.Set(suite.chainA.GetContext(), types.Checksum("checksum")) - suite.Require().NoError(err) - }, - func(checksums []types.Checksum) { - suite.Require().Len(checksums, 2) - suite.Require().Contains(checksums, types.Checksum("checksum")) - }, - }, - } - - for _, tc := range testCases { - tc := tc - suite.Run(tc.name, func() { - tc.malleate() - - checksums, err := types.GetAllChecksums(suite.chainA.GetContext()) - suite.Require().NoError(err) - tc.expResult(checksums) - }) - } -} - -func (suite *TypesTestSuite) TestAddChecksum() { - suite.SetupWasmWithMockVM() - - checksums, err := types.GetAllChecksums(suite.chainA.GetContext()) - suite.Require().NoError(err) - // default mock vm contract is stored - suite.Require().Len(checksums, 1) - - checksum1 := types.Checksum("checksum1") - checksum2 := types.Checksum("checksum2") - err = ibcwasm.Checksums.Set(suite.chainA.GetContext(), checksum1) - suite.Require().NoError(err) - err = ibcwasm.Checksums.Set(suite.chainA.GetContext(), checksum2) - suite.Require().NoError(err) - - // Test adding the same checksum twice - err = ibcwasm.Checksums.Set(suite.chainA.GetContext(), checksum1) - suite.Require().NoError(err) - - checksums, err = types.GetAllChecksums(suite.chainA.GetContext()) - suite.Require().NoError(err) - suite.Require().Len(checksums, 3) - suite.Require().Contains(checksums, checksum1) - suite.Require().Contains(checksums, checksum2) -} - -func (suite *TypesTestSuite) TestHasChecksum() { - var checksum types.Checksum - - testCases := []struct { - name string - malleate func() - exprResult bool - }{ - { - "success: checksum exists", - func() { - checksum = types.Checksum("checksum") - err := ibcwasm.Checksums.Set(suite.chainA.GetContext(), checksum) - suite.Require().NoError(err) - }, - true, - }, - { - "success: checksum does not exist", - func() { - checksum = types.Checksum("non-existent-checksum") - }, - false, - }, - } - - for _, tc := range testCases { - tc := tc - suite.Run(tc.name, func() { - suite.SetupWasmWithMockVM() - - tc.malleate() - - result := types.HasChecksum(suite.chainA.GetContext(), checksum) - suite.Require().Equal(tc.exprResult, result) - }) - } -} - // TestMigrateClientWrappedStoreGetStore tests the getStore method of the migrateClientWrappedStore. func (suite *TypesTestSuite) TestMigrateClientWrappedStoreGetStore() { // calls suite.SetupWasmWithMockVM() and creates two clients with their respective stores From 5cc45a467d4d3aeaae6365fa226e9cd840c70548 Mon Sep 17 00:00:00 2001 From: DimitrisJim Date: Thu, 4 Apr 2024 05:00:29 +0300 Subject: [PATCH 05/24] Make InitializePinnedCodes a method on the keeper. --- modules/light-clients/08-wasm/keeper/keeper.go | 3 +-- modules/light-clients/08-wasm/keeper/keeper_test.go | 2 +- modules/light-clients/08-wasm/testing/simapp/app.go | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/light-clients/08-wasm/keeper/keeper.go b/modules/light-clients/08-wasm/keeper/keeper.go index c63befec836..da97673e503 100644 --- a/modules/light-clients/08-wasm/keeper/keeper.go +++ b/modules/light-clients/08-wasm/keeper/keeper.go @@ -206,8 +206,7 @@ func (k Keeper) HasChecksum(ctx context.Context, checksum types.Checksum) bool { } // InitializePinnedCodes updates wasmvm to pin to cache all contracts marked as pinned -// TODO(jim): Make meth on Keeper probably. -func InitializePinnedCodes(ctx sdk.Context, k Keeper) error { +func (k Keeper) InitializePinnedCodes(ctx sdk.Context) error { checksums, err := k.GetAllChecksums(ctx) if err != nil { return err diff --git a/modules/light-clients/08-wasm/keeper/keeper_test.go b/modules/light-clients/08-wasm/keeper/keeper_test.go index 2369e50393f..a4c5f647083 100644 --- a/modules/light-clients/08-wasm/keeper/keeper_test.go +++ b/modules/light-clients/08-wasm/keeper/keeper_test.go @@ -312,7 +312,7 @@ func (suite *KeeperTestSuite) TestInitializedPinnedCodes() { // malleate after storing contracts tc.malleate() - err := keeper.InitializePinnedCodes(ctx, wasmClientKeeper) + err := wasmClientKeeper.InitializePinnedCodes(ctx) expPass := tc.expError == nil if expPass { diff --git a/modules/light-clients/08-wasm/testing/simapp/app.go b/modules/light-clients/08-wasm/testing/simapp/app.go index 384725f9767..578179944c4 100644 --- a/modules/light-clients/08-wasm/testing/simapp/app.go +++ b/modules/light-clients/08-wasm/testing/simapp/app.go @@ -846,7 +846,7 @@ func NewSimApp( ctx := app.BaseApp.NewUncachedContext(true, cmtproto.Header{}) // Initialize pinned codes in wasmvm as they are not persisted there - if err := wasmkeeper.InitializePinnedCodes(ctx, app.WasmClientKeeper); err != nil { + if err := app.WasmClientKeeper.InitializePinnedCodes(ctx); err != nil { cmtos.Exit(fmt.Sprintf("failed initialize pinned codes %s", err)) } } From 139c806550009d062bcfe6cec30083adf0e5588b Mon Sep 17 00:00:00 2001 From: DimitrisJim Date: Thu, 4 Apr 2024 05:23:14 +0300 Subject: [PATCH 06/24] Remove pending todo in test for migrate contract. This previously was testing a function that didn't set the client state after calling the contract (invoking through the keeper always sets a client state after contract invocation). Hence, even if we don't set explicitly in the migrateFn callback, we _still_ get a client state in the end. --- .../08-wasm/keeper/keeper_test.go | 43 +++---------------- 1 file changed, 5 insertions(+), 38 deletions(-) diff --git a/modules/light-clients/08-wasm/keeper/keeper_test.go b/modules/light-clients/08-wasm/keeper/keeper_test.go index a4c5f647083..bdea3fc8841 100644 --- a/modules/light-clients/08-wasm/keeper/keeper_test.go +++ b/modules/light-clients/08-wasm/keeper/keeper_test.go @@ -338,31 +338,6 @@ func (suite *KeeperTestSuite) TestMigrateContract() { malleate func() expErr error }{ - // TODO(Jim): Figure this out, appears like a bad setup after moving from types test suite. - /* - { - "success: no update to client state", - func() { - err := ibcwasm.Checksums.Set(suite.chainA.GetContext(), newHash) - suite.Require().NoError(err) - - payload = []byte{1} - expChecksum := wasmvmtypes.ForceNewChecksum(hex.EncodeToString(newHash)) - - suite.mockVM.MigrateFn = func(checksum wasmvm.Checksum, env wasmvmtypes.Env, msg []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - suite.Require().Equal(expChecksum, checksum) - suite.Require().Equal(defaultWasmClientID, env.Contract.Address) - suite.Require().Equal(payload, msg) - - data, err := json.Marshal(types.EmptyResult{}) - suite.Require().NoError(err) - - return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: data}}, wasmtesting.DefaultGasUsed, nil - } - }, - nil, - }, - */ { "success: update client state", func() { @@ -392,8 +367,7 @@ func (suite *KeeperTestSuite) TestMigrateContract() { { "failure: checksum not found", func() { - keeper := GetSimApp(suite.chainA).WasmClientKeeper - err := keeper.GetChecksums().Remove(suite.chainA.GetContext(), newHash) + err := GetSimApp(suite.chainA).WasmClientKeeper.GetChecksums().Remove(suite.chainA.GetContext(), newHash) suite.Require().NoError(err) }, types.ErrWasmChecksumNotFound, @@ -401,8 +375,7 @@ func (suite *KeeperTestSuite) TestMigrateContract() { { "failure: vm returns error", func() { - keeper := GetSimApp(suite.chainA).WasmClientKeeper - err := keeper.GetChecksums().Set(suite.chainA.GetContext(), newHash) + err := GetSimApp(suite.chainA).WasmClientKeeper.GetChecksums().Set(suite.chainA.GetContext(), newHash) suite.Require().NoError(err) suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { @@ -414,8 +387,7 @@ func (suite *KeeperTestSuite) TestMigrateContract() { { "failure: contract returns error", func() { - keeper := GetSimApp(suite.chainA).WasmClientKeeper - err := keeper.GetChecksums().Set(suite.chainA.GetContext(), newHash) + err := GetSimApp(suite.chainA).WasmClientKeeper.GetChecksums().Set(suite.chainA.GetContext(), newHash) suite.Require().NoError(err) suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { @@ -438,15 +410,14 @@ func (suite *KeeperTestSuite) TestMigrateContract() { newHash, err = types.CreateChecksum(wasmtesting.CreateMockContract([]byte{1, 2, 3})) suite.Require().NoError(err) - keeper := GetSimApp(suite.chainA).WasmClientKeeper - err = keeper.GetChecksums().Set(suite.chainA.GetContext(), newHash) + wasmKeeper := GetSimApp(suite.chainA).WasmClientKeeper + err = wasmKeeper.GetChecksums().Set(suite.chainA.GetContext(), newHash) suite.Require().NoError(err) endpointA := wasmtesting.NewWasmEndpoint(suite.chainA) err = endpointA.CreateClient() suite.Require().NoError(err) - // clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpointA.ClientID) clientState, ok := endpointA.GetClientState().(*types.ClientState) suite.Require().True(ok) @@ -454,12 +425,8 @@ func (suite *KeeperTestSuite) TestMigrateContract() { tc.malleate() - wasmKeeper := GetSimApp(suite.chainA).WasmClientKeeper err = wasmKeeper.MigrateContractCode(suite.chainA.GetContext(), endpointA.ClientID, newHash, payload) - // vm := GetSimApp(suite.chainA).WasmClientKeeper.GetVM() - // err = clientState.MigrateContract(suite.chainA.GetContext(), vm, suite.chainA.App.AppCodec(), clientStore, endpointA.ClientID, newHash, payload) - // updated client state clientState, ok = endpointA.GetClientState().(*types.ClientState) suite.Require().True(ok) From f3a7aae7432b8fbca4e260e89d21dd8a9a8f0dbf Mon Sep 17 00:00:00 2001 From: DimitrisJim Date: Fri, 5 Apr 2024 15:46:19 +0300 Subject: [PATCH 07/24] Move vm entrypoint functions in keeper/ package. --- .../08-wasm/keeper/contract_keeper.go | 225 ++++++++++++++++++ .../08-wasm/light_client_module.go | 25 +- .../08-wasm/types/gas_register_custom.go | 2 +- modules/light-clients/08-wasm/types/keys.go | 6 +- .../light-clients/08-wasm/types/querier.go | 4 +- modules/light-clients/08-wasm/types/store.go | 4 +- modules/light-clients/08-wasm/types/vm.go | 28 +-- 7 files changed, 258 insertions(+), 36 deletions(-) create mode 100644 modules/light-clients/08-wasm/keeper/contract_keeper.go diff --git a/modules/light-clients/08-wasm/keeper/contract_keeper.go b/modules/light-clients/08-wasm/keeper/contract_keeper.go new file mode 100644 index 00000000000..d227c211ff2 --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/contract_keeper.go @@ -0,0 +1,225 @@ +package keeper + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "errors" + + wasmvm "github.com/CosmWasm/wasmvm/v2" + wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" + + errorsmod "cosmossdk.io/errors" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" +) + +var ( + VMGasRegister = types.NewDefaultWasmGasRegister() + // wasmvmAPI is a wasmvm.GoAPI implementation that is passed to the wasmvm, it + // doesn't implement any functionality, directly returning an error. + wasmvmAPI = wasmvm.GoAPI{ + HumanizeAddress: humanizeAddress, + CanonicalizeAddress: canonicalizeAddress, + ValidateAddress: validateAddress, + } +) + +// WasmQuery queries the contract with the given payload and returns the result. +// WasmQuery returns an error if: +// - the payload cannot be marshaled to JSON +// - the contract query returns an error +// - the data bytes of the response cannot be unmarshal into the result type +func WasmQuery[T types.ContractResult](ctx sdk.Context, k Keeper, vm ibcwasm.WasmEngine, clientID string, clientStore storetypes.KVStore, cs *types.ClientState, payload types.QueryMsg) (T, error) { + var result T + + encodedData, err := json.Marshal(payload) + if err != nil { + return result, errorsmod.Wrap(err, "failed to marshal payload for wasm query") + } + + res, err := k.queryContract(ctx, vm, clientID, clientStore, cs.Checksum, encodedData) + if err != nil { + return result, errorsmod.Wrap(types.ErrVMError, err.Error()) + } + if res.Err != "" { + return result, errorsmod.Wrap(types.ErrWasmContractCallFailed, res.Err) + } + + if err := json.Unmarshal(res.Ok, &result); err != nil { + return result, errorsmod.Wrapf(types.ErrWasmInvalidResponseData, "failed to unmarshal result of wasm query: %v", err) + } + + return result, nil +} + +// WasmSudo calls the contract with the given payload and returns the result. +// WasmSudo returns an error if: +// - the payload cannot be marshaled to JSON +// - the contract call returns an error +// - the response of the contract call contains non-empty messages +// - the response of the contract call contains non-empty events +// - the response of the contract call contains non-empty attributes +// - the data bytes of the response cannot be unmarshaled into the result type +func WasmSudo[T types.ContractResult](ctx sdk.Context, k Keeper, vm ibcwasm.WasmEngine, clientID string, cdc codec.BinaryCodec, clientStore storetypes.KVStore, cs *types.ClientState, payload types.SudoMsg) (T, error) { + var result T + + encodedData, err := json.Marshal(payload) + if err != nil { + return result, errorsmod.Wrap(err, "failed to marshal payload for wasm execution") + } + + checksum := cs.Checksum + res, err := k.callContract(ctx, vm, clientID, clientStore, checksum, encodedData) + if err != nil { + return result, errorsmod.Wrap(types.ErrVMError, err.Error()) + } + if res.Err != "" { + return result, errorsmod.Wrap(types.ErrWasmContractCallFailed, res.Err) + } + + if err = types.CheckResponse(res.Ok); err != nil { + return result, errorsmod.Wrapf(err, "checksum (%s)", hex.EncodeToString(cs.Checksum)) + } + + if err := json.Unmarshal(res.Ok.Data, &result); err != nil { + return result, errorsmod.Wrap(types.ErrWasmInvalidResponseData, err.Error()) + } + + newClientState, err := types.ValidatePostExecutionClientState(clientStore, cdc) + if err != nil { + return result, err + } + + // Checksum should only be able to be modified during migration. + if !bytes.Equal(checksum, newClientState.Checksum) { + return result, errorsmod.Wrapf(types.ErrWasmInvalidContractModification, "expected checksum %s, got %s", hex.EncodeToString(checksum), hex.EncodeToString(newClientState.Checksum)) + } + + return result, nil +} + +// WasmInstantiate accepts a message to instantiate a wasm contract, JSON encodes it and calls instantiateContract. +func WasmInstantiate(ctx sdk.Context, k Keeper, vm ibcwasm.WasmEngine, clientID string, cdc codec.BinaryCodec, clientStore storetypes.KVStore, cs *types.ClientState, payload types.InstantiateMessage) error { + encodedData, err := json.Marshal(payload) + if err != nil { + return errorsmod.Wrap(err, "failed to marshal payload for wasm contract instantiation") + } + + checksum := cs.Checksum + res, err := k.instantiateContract(ctx, vm, clientID, clientStore, checksum, encodedData) + if err != nil { + return errorsmod.Wrap(types.ErrVMError, err.Error()) + } + if res.Err != "" { + return errorsmod.Wrap(types.ErrWasmContractCallFailed, res.Err) + } + + if err = types.CheckResponse(res.Ok); err != nil { + return errorsmod.Wrapf(err, "checksum (%s)", hex.EncodeToString(cs.Checksum)) + } + + newClientState, err := types.ValidatePostExecutionClientState(clientStore, cdc) + if err != nil { + return err + } + + // Checksum should only be able to be modified during migration. + if !bytes.Equal(checksum, newClientState.Checksum) { + return errorsmod.Wrapf(types.ErrWasmInvalidContractModification, "expected checksum %s, got %s", hex.EncodeToString(checksum), hex.EncodeToString(newClientState.Checksum)) + } + + return nil +} + +// queryContract calls vm.Query. +func (k Keeper) queryContract(ctx sdk.Context, vm ibcwasm.WasmEngine, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.QueryResult, error) { + sdkGasMeter := ctx.GasMeter() + multipliedGasMeter := types.NewMultipliedGasMeter(sdkGasMeter, VMGasRegister) + gasLimit := VMGasRegister.RuntimeGasForContract(ctx) + + env := getEnv(ctx, clientID) + + ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: query") + resp, gasUsed, err := vm.Query(checksum, env, msg, types.NewStoreAdapter(clientStore), wasmvmAPI, types.NewQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization) + VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) + return resp, err +} + +// callContract calls vm.Sudo with internally constructed gas meter and environment. +func (k Keeper) callContract(ctx sdk.Context, vm ibcwasm.WasmEngine, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) { + sdkGasMeter := ctx.GasMeter() + multipliedGasMeter := types.NewMultipliedGasMeter(sdkGasMeter, VMGasRegister) + gasLimit := VMGasRegister.RuntimeGasForContract(ctx) + + env := getEnv(ctx, clientID) + + ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: sudo") + resp, gasUsed, err := vm.Sudo(checksum, env, msg, types.NewStoreAdapter(clientStore), wasmvmAPI, types.NewQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization) + VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) + return resp, err +} + +// instantiateContract calls vm.Instantiate with appropriate arguments. +func (Keeper) instantiateContract(ctx sdk.Context, vm ibcwasm.WasmEngine, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) { + sdkGasMeter := ctx.GasMeter() + multipliedGasMeter := types.NewMultipliedGasMeter(sdkGasMeter, types.VMGasRegister) + gasLimit := VMGasRegister.RuntimeGasForContract(ctx) + + env := getEnv(ctx, clientID) + + msgInfo := wasmvmtypes.MessageInfo{ + Sender: "", + Funds: nil, + } + + ctx.GasMeter().ConsumeGas(types.VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: instantiate") + resp, gasUsed, err := vm.Instantiate(checksum, env, msgInfo, msg, types.NewStoreAdapter(clientStore), wasmvmAPI, types.NewQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization) + types.VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) + return resp, err +} + +// getEnv returns the state of the blockchain environment the contract is running on +func getEnv(ctx sdk.Context, contractAddr string) wasmvmtypes.Env { + chainID := ctx.BlockHeader().ChainID + height := ctx.BlockHeader().Height + + // safety checks before casting below + if height < 0 { + panic(errors.New("block height must never be negative")) + } + nsec := ctx.BlockTime().UnixNano() + if nsec < 0 { + panic(errors.New("block (unix) time must never be negative ")) + } + + env := wasmvmtypes.Env{ + Block: wasmvmtypes.BlockInfo{ + Height: uint64(height), + Time: wasmvmtypes.Uint64(nsec), + ChainID: chainID, + }, + Contract: wasmvmtypes.ContractInfo{ + Address: contractAddr, + }, + } + + return env +} + +func humanizeAddress(canon []byte) (string, uint64, error) { + return "", 0, errors.New("humanizeAddress not implemented") +} + +func canonicalizeAddress(human string) ([]byte, uint64, error) { + return nil, 0, errors.New("canonicalizeAddress not implemented") +} + +func validateAddress(human string) (uint64, error) { + return 0, errors.New("validateAddress not implemented") +} diff --git a/modules/light-clients/08-wasm/light_client_module.go b/modules/light-clients/08-wasm/light_client_module.go index 22aadbee563..1b593f53dd0 100644 --- a/modules/light-clients/08-wasm/light_client_module.go +++ b/modules/light-clients/08-wasm/light_client_module.go @@ -9,12 +9,11 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" - commitmenttypes "github.com/cosmos/ibc-go/v8/modules/core/23-commitment/types" - "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" wasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v8/modules/core/23-commitment/types" ibcerrors "github.com/cosmos/ibc-go/v8/modules/core/errors" "github.com/cosmos/ibc-go/v8/modules/core/exported" ) @@ -83,7 +82,7 @@ func (l LightClientModule) Initialize(ctx sdk.Context, clientID string, clientSt Checksum: clientState.Checksum, } - return types.WasmInstantiate(ctx, l.GetVM(), clientID, l.keeper.Codec(), clientStore, &clientState, payload) + return wasmkeeper.WasmInstantiate(ctx, l.keeper, l.GetVM(), clientID, l.keeper.Codec(), clientStore, &clientState, payload) } // VerifyClientMessage obtains the client state associated with the client identifier, it then must verify the ClientMessage. @@ -110,7 +109,7 @@ func (l LightClientModule) VerifyClientMessage(ctx sdk.Context, clientID string, payload := types.QueryMsg{ VerifyClientMessage: &types.VerifyClientMessageMsg{ClientMessage: clientMessage.Data}, } - _, err := types.WasmQuery[types.EmptyResult](ctx, l.GetVM(), clientID, clientStore, clientState, payload) + _, err := wasmkeeper.WasmQuery[types.EmptyResult](ctx, l.keeper, l.GetVM(), clientID, clientStore, clientState, payload) return err } @@ -136,7 +135,7 @@ func (l LightClientModule) CheckForMisbehaviour(ctx sdk.Context, clientID string CheckForMisbehaviour: &types.CheckForMisbehaviourMsg{ClientMessage: clientMessage.Data}, } - result, err := types.WasmQuery[types.CheckForMisbehaviourResult](ctx, l.GetVM(), clientID, clientStore, clientState, payload) + result, err := wasmkeeper.WasmQuery[types.CheckForMisbehaviourResult](ctx, l.keeper, l.GetVM(), clientID, clientStore, clientState, payload) if err != nil { return false } @@ -167,7 +166,7 @@ func (l LightClientModule) UpdateStateOnMisbehaviour(ctx sdk.Context, clientID s UpdateStateOnMisbehaviour: &types.UpdateStateOnMisbehaviourMsg{ClientMessage: clientMessage.Data}, } - _, err := types.WasmSudo[types.EmptyResult](ctx, l.GetVM(), clientID, cdc, clientStore, clientState, payload) + _, err := wasmkeeper.WasmSudo[types.EmptyResult](ctx, l.keeper, l.GetVM(), clientID, cdc, clientStore, clientState, payload) if err != nil { panic(err) } @@ -195,7 +194,7 @@ func (l LightClientModule) UpdateState(ctx sdk.Context, clientID string, clientM UpdateState: &types.UpdateStateMsg{ClientMessage: clientMessage.Data}, } - result, err := types.WasmSudo[types.UpdateStateResult](ctx, l.GetVM(), clientID, cdc, clientStore, clientState, payload) + result, err := wasmkeeper.WasmSudo[types.UpdateStateResult](ctx, l.keeper, l.GetVM(), clientID, cdc, clientStore, clientState, payload) if err != nil { panic(err) } @@ -259,7 +258,7 @@ func (l LightClientModule) VerifyMembership( Value: value, }, } - _, err := types.WasmSudo[types.EmptyResult](ctx, l.GetVM(), clientID, cdc, clientStore, clientState, payload) + _, err := wasmkeeper.WasmSudo[types.EmptyResult](ctx, l.keeper, l.GetVM(), clientID, cdc, clientStore, clientState, payload) return err } @@ -312,7 +311,7 @@ func (l LightClientModule) VerifyNonMembership( Path: merklePath, }, } - _, err := types.WasmSudo[types.EmptyResult](ctx, l.GetVM(), clientID, cdc, clientStore, clientState, payload) + _, err := wasmkeeper.WasmSudo[types.EmptyResult](ctx, l.keeper, l.GetVM(), clientID, cdc, clientStore, clientState, payload) return err } @@ -343,7 +342,7 @@ func (l LightClientModule) Status(ctx sdk.Context, clientID string) exported.Sta } payload := types.QueryMsg{Status: &types.StatusMsg{}} - result, err := types.WasmQuery[types.StatusResult](ctx, l.GetVM(), clientID, clientStore, clientState, payload) + result, err := wasmkeeper.WasmQuery[types.StatusResult](ctx, l.keeper, l.GetVM(), clientID, clientStore, clientState, payload) if err != nil { return exported.Unknown } @@ -391,7 +390,7 @@ func (l LightClientModule) TimestampAtHeight(ctx sdk.Context, clientID string, h }, } - result, err := types.WasmQuery[types.TimestampAtHeightResult](ctx, l.GetVM(), clientID, clientStore, clientState, payload) + result, err := wasmkeeper.WasmQuery[types.TimestampAtHeightResult](ctx, l.keeper, l.GetVM(), clientID, clientStore, clientState, payload) if err != nil { return 0, errorsmod.Wrapf(err, "height (%s)", height) } @@ -443,7 +442,7 @@ func (l LightClientModule) RecoverClient(ctx sdk.Context, clientID, substituteCl MigrateClientStore: &types.MigrateClientStoreMsg{}, } - _, err = types.WasmSudo[types.EmptyResult](ctx, l.GetVM(), clientID, cdc, store, clientState, payload) + _, err = wasmkeeper.WasmSudo[types.EmptyResult](ctx, l.keeper, l.GetVM(), clientID, cdc, store, clientState, payload) return err } @@ -493,6 +492,6 @@ func (l LightClientModule) VerifyUpgradeAndUpdateState( }, } - _, err := types.WasmSudo[types.EmptyResult](ctx, l.GetVM(), clientID, cdc, clientStore, clientState, payload) + _, err := wasmkeeper.WasmSudo[types.EmptyResult](ctx, l.keeper, l.GetVM(), clientID, cdc, clientStore, clientState, payload) return err } diff --git a/modules/light-clients/08-wasm/types/gas_register_custom.go b/modules/light-clients/08-wasm/types/gas_register_custom.go index 80b41663f2a..991d8779c43 100644 --- a/modules/light-clients/08-wasm/types/gas_register_custom.go +++ b/modules/light-clients/08-wasm/types/gas_register_custom.go @@ -19,7 +19,7 @@ const ( DefaultDeserializationCostPerByte = 1 ) -var costJSONDeserialization = wasmvmtypes.UFraction{ +var CostJSONDeserialization = wasmvmtypes.UFraction{ Numerator: DefaultDeserializationCostPerByte * DefaultGasMultiplier, Denominator: 1, } diff --git a/modules/light-clients/08-wasm/types/keys.go b/modules/light-clients/08-wasm/types/keys.go index 5f026d706a4..a2a159233c4 100644 --- a/modules/light-clients/08-wasm/types/keys.go +++ b/modules/light-clients/08-wasm/types/keys.go @@ -17,7 +17,5 @@ const ( KeyChecksums = "checksums" ) -var ( - // ChecksumsKey is the key under which all checksums are stored - ChecksumsKey = collections.NewPrefix(0) -) +// ChecksumsKey is the key under which all checksums are stored +var ChecksumsKey = collections.NewPrefix(0) diff --git a/modules/light-clients/08-wasm/types/querier.go b/modules/light-clients/08-wasm/types/querier.go index 5fbd7a57bf4..46514c4b41a 100644 --- a/modules/light-clients/08-wasm/types/querier.go +++ b/modules/light-clients/08-wasm/types/querier.go @@ -46,8 +46,8 @@ type queryHandler struct { CallerID string } -// newQueryHandler returns a default querier that can be used in the contract. -func newQueryHandler(ctx sdk.Context, callerID string) *queryHandler { +// NewQueryHandler returns a default querier that can be used in the contract. +func NewQueryHandler(ctx sdk.Context, callerID string) *queryHandler { return &queryHandler{ Ctx: ctx, CallerID: callerID, diff --git a/modules/light-clients/08-wasm/types/store.go b/modules/light-clients/08-wasm/types/store.go index 50ae33fcd6b..4a18502f218 100644 --- a/modules/light-clients/08-wasm/types/store.go +++ b/modules/light-clients/08-wasm/types/store.go @@ -223,8 +223,8 @@ type storeAdapter struct { parent storetypes.KVStore } -// newStoreAdapter constructor -func newStoreAdapter(s storetypes.KVStore) *storeAdapter { +// NewStoreAdapter constructor +func NewStoreAdapter(s storetypes.KVStore) *storeAdapter { if s == nil { panic(errors.New("store must not be nil")) } diff --git a/modules/light-clients/08-wasm/types/vm.go b/modules/light-clients/08-wasm/types/vm.go index 9be6e877c0d..87307d0ca61 100644 --- a/modules/light-clients/08-wasm/types/vm.go +++ b/modules/light-clients/08-wasm/types/vm.go @@ -46,7 +46,7 @@ func instantiateContract(ctx sdk.Context, vm ibcwasm.WasmEngine, clientID string } ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: instantiate") - resp, gasUsed, err := vm.Instantiate(checksum, env, msgInfo, msg, newStoreAdapter(clientStore), wasmvmAPI, newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, costJSONDeserialization) + resp, gasUsed, err := vm.Instantiate(checksum, env, msgInfo, msg, NewStoreAdapter(clientStore), wasmvmAPI, NewQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, CostJSONDeserialization) VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) return resp, err } @@ -60,7 +60,7 @@ func callContract(ctx sdk.Context, vm ibcwasm.WasmEngine, clientID string, clien env := getEnv(ctx, clientID) ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: sudo") - resp, gasUsed, err := vm.Sudo(checksum, env, msg, newStoreAdapter(clientStore), wasmvmAPI, newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, costJSONDeserialization) + resp, gasUsed, err := vm.Sudo(checksum, env, msg, NewStoreAdapter(clientStore), wasmvmAPI, NewQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, CostJSONDeserialization) VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) return resp, err } @@ -74,7 +74,7 @@ func migrateContract(ctx sdk.Context, vm ibcwasm.WasmEngine, clientID string, cl env := getEnv(ctx, clientID) ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: migrate") - resp, gasUsed, err := vm.Migrate(checksum, env, msg, newStoreAdapter(clientStore), wasmvmAPI, newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, costJSONDeserialization) + resp, gasUsed, err := vm.Migrate(checksum, env, msg, NewStoreAdapter(clientStore), wasmvmAPI, NewQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, CostJSONDeserialization) VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) return resp, err } @@ -88,7 +88,7 @@ func queryContract(ctx sdk.Context, vm ibcwasm.WasmEngine, clientID string, clie env := getEnv(ctx, clientID) ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: query") - resp, gasUsed, err := vm.Query(checksum, env, msg, newStoreAdapter(clientStore), wasmvmAPI, newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, costJSONDeserialization) + resp, gasUsed, err := vm.Query(checksum, env, msg, NewStoreAdapter(clientStore), wasmvmAPI, NewQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, CostJSONDeserialization) VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) return resp, err } @@ -109,11 +109,11 @@ func WasmInstantiate(ctx sdk.Context, vm ibcwasm.WasmEngine, clientID string, cd return errorsmod.Wrap(ErrWasmContractCallFailed, res.Err) } - if err = checkResponse(res.Ok); err != nil { + if err = CheckResponse(res.Ok); err != nil { return errorsmod.Wrapf(err, "checksum (%s)", hex.EncodeToString(cs.Checksum)) } - newClientState, err := validatePostExecutionClientState(clientStore, cdc) + newClientState, err := ValidatePostExecutionClientState(clientStore, cdc) if err != nil { return err } @@ -151,7 +151,7 @@ func WasmSudo[T ContractResult](ctx sdk.Context, vm ibcwasm.WasmEngine, clientID return result, errorsmod.Wrap(ErrWasmContractCallFailed, res.Err) } - if err = checkResponse(res.Ok); err != nil { + if err = CheckResponse(res.Ok); err != nil { return result, errorsmod.Wrapf(err, "checksum (%s)", hex.EncodeToString(cs.Checksum)) } @@ -159,7 +159,7 @@ func WasmSudo[T ContractResult](ctx sdk.Context, vm ibcwasm.WasmEngine, clientID return result, errorsmod.Wrap(ErrWasmInvalidResponseData, err.Error()) } - newClientState, err := validatePostExecutionClientState(clientStore, cdc) + newClientState, err := ValidatePostExecutionClientState(clientStore, cdc) if err != nil { return result, err } @@ -184,11 +184,11 @@ func WasmMigrate(ctx sdk.Context, vm ibcwasm.WasmEngine, cdc codec.BinaryCodec, return errorsmod.Wrap(ErrWasmContractCallFailed, res.Err) } - if err = checkResponse(res.Ok); err != nil { + if err = CheckResponse(res.Ok); err != nil { return errorsmod.Wrapf(err, "checksum (%s)", hex.EncodeToString(cs.Checksum)) } - _, err = validatePostExecutionClientState(clientStore, cdc) + _, err = ValidatePostExecutionClientState(clientStore, cdc) return err } @@ -220,12 +220,12 @@ func WasmQuery[T ContractResult](ctx sdk.Context, vm ibcwasm.WasmEngine, clientI return result, nil } -// validatePostExecutionClientState validates that the contract has not many any invalid modifications +// ValidatePostExecutionClientState validates that the contract has not many any invalid modifications // to the client state during execution. It ensures that // - the client state is still present // - the client state can be unmarshaled successfully. // - the client state is of type *ClientState -func validatePostExecutionClientState(clientStore storetypes.KVStore, cdc codec.BinaryCodec) (*ClientState, error) { +func ValidatePostExecutionClientState(clientStore storetypes.KVStore, cdc codec.BinaryCodec) (*ClientState, error) { key := host.ClientStateKey() _, ok := clientStore.(migrateClientWrappedStore) if ok { @@ -300,9 +300,9 @@ func validateAddress(human string) (uint64, error) { return 0, errors.New("validateAddress not implemented") } -// checkResponse returns an error if the response from a sudo, instantiate or migrate call +// CheckResponse returns an error if the response from a sudo, instantiate or migrate call // to the Wasm VM contains messages, events or attributes. -func checkResponse(response *wasmvmtypes.Response) error { +func CheckResponse(response *wasmvmtypes.Response) error { // Only allow Data to flow back to us. SubMessages, Events and Attributes are not allowed. if len(response.Messages) > 0 { return ErrWasmSubMessagesNotAllowed From 6f5616ad90f9ba9093ca7599b7048b9e66cedd1e Mon Sep 17 00:00:00 2001 From: DimitrisJim Date: Sun, 7 Apr 2024 17:42:54 +0300 Subject: [PATCH 08/24] feat: move vm entry points to keeper. --- .../08-wasm/keeper/contract_keeper.go | 102 ++- .../light-clients/08-wasm/keeper/keeper.go | 2 +- .../08-wasm/types/export_test.go | 9 +- .../08-wasm/types/gas_register.go | 11 +- modules/light-clients/08-wasm/types/store.go | 52 +- modules/light-clients/08-wasm/types/vm.go | 317 ---------- .../light-clients/08-wasm/types/vm_test.go | 594 ------------------ 7 files changed, 134 insertions(+), 953 deletions(-) delete mode 100644 modules/light-clients/08-wasm/types/vm_test.go diff --git a/modules/light-clients/08-wasm/keeper/contract_keeper.go b/modules/light-clients/08-wasm/keeper/contract_keeper.go index d227c211ff2..ad127a557b0 100644 --- a/modules/light-clients/08-wasm/keeper/contract_keeper.go +++ b/modules/light-clients/08-wasm/keeper/contract_keeper.go @@ -17,6 +17,9 @@ import ( "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v8/modules/core/24-host" + "github.com/cosmos/ibc-go/v8/modules/core/exported" ) var ( @@ -83,7 +86,7 @@ func WasmSudo[T types.ContractResult](ctx sdk.Context, k Keeper, vm ibcwasm.Wasm return result, errorsmod.Wrap(types.ErrWasmContractCallFailed, res.Err) } - if err = types.CheckResponse(res.Ok); err != nil { + if err = checkResponse(res.Ok); err != nil { return result, errorsmod.Wrapf(err, "checksum (%s)", hex.EncodeToString(cs.Checksum)) } @@ -91,7 +94,7 @@ func WasmSudo[T types.ContractResult](ctx sdk.Context, k Keeper, vm ibcwasm.Wasm return result, errorsmod.Wrap(types.ErrWasmInvalidResponseData, err.Error()) } - newClientState, err := types.ValidatePostExecutionClientState(clientStore, cdc) + newClientState, err := ValidatePostExecutionClientState(clientStore, cdc) if err != nil { return result, err } @@ -120,11 +123,11 @@ func WasmInstantiate(ctx sdk.Context, k Keeper, vm ibcwasm.WasmEngine, clientID return errorsmod.Wrap(types.ErrWasmContractCallFailed, res.Err) } - if err = types.CheckResponse(res.Ok); err != nil { + if err = checkResponse(res.Ok); err != nil { return errorsmod.Wrapf(err, "checksum (%s)", hex.EncodeToString(cs.Checksum)) } - newClientState, err := types.ValidatePostExecutionClientState(clientStore, cdc) + newClientState, err := ValidatePostExecutionClientState(clientStore, cdc) if err != nil { return err } @@ -137,6 +140,40 @@ func WasmInstantiate(ctx sdk.Context, k Keeper, vm ibcwasm.WasmEngine, clientID return nil } +// WasmMigrate migrate calls the migrate entry point of the contract with the given payload and returns the result. +// WasmMigrate returns an error if: +// - the contract migration returns an error +func WasmMigrate(ctx sdk.Context, keeper Keeper, vm ibcwasm.WasmEngine, cdc codec.BinaryCodec, clientStore storetypes.KVStore, cs *types.ClientState, clientID string, payload []byte) error { + res, err := keeper.migrateContract(ctx, vm, clientID, clientStore, cs.Checksum, payload) + if err != nil { + return errorsmod.Wrap(types.ErrVMError, err.Error()) + } + if res.Err != "" { + return errorsmod.Wrap(types.ErrWasmContractCallFailed, res.Err) + } + + if err = checkResponse(res.Ok); err != nil { + return errorsmod.Wrapf(err, "checksum (%s)", hex.EncodeToString(cs.Checksum)) + } + + _, err = ValidatePostExecutionClientState(clientStore, cdc) + return err +} + +// migrateContract calls vm.Migrate with internally constructed gas meter and environment. +func (k Keeper) migrateContract(ctx sdk.Context, vm ibcwasm.WasmEngine, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) { + sdkGasMeter := ctx.GasMeter() + multipliedGasMeter := types.NewMultipliedGasMeter(sdkGasMeter, VMGasRegister) + gasLimit := VMGasRegister.RuntimeGasForContract(ctx) + + env := getEnv(ctx, clientID) + + ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: migrate") + resp, gasUsed, err := vm.Migrate(checksum, env, msg, types.NewStoreAdapter(clientStore), wasmvmAPI, types.NewQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization) + VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) + return resp, err +} + // queryContract calls vm.Query. func (k Keeper) queryContract(ctx sdk.Context, vm ibcwasm.WasmEngine, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.QueryResult, error) { sdkGasMeter := ctx.GasMeter() @@ -184,6 +221,46 @@ func (Keeper) instantiateContract(ctx sdk.Context, vm ibcwasm.WasmEngine, client return resp, err } +// ValidatePostExecutionClientState validates that the contract has not many any invalid modifications +// to the client state during execution. It ensures that +// - the client state is still present +// - the client state can be unmarshaled successfully. +// - the client state is of type *ClientState +func ValidatePostExecutionClientState(clientStore storetypes.KVStore, cdc codec.BinaryCodec) (*types.ClientState, error) { + key := host.ClientStateKey() + _, ok := clientStore.(types.MigrateClientWrappedStore) + if ok { + key = append(types.SubjectPrefix, key...) + } + + bz := clientStore.Get(key) + if len(bz) == 0 { + return nil, errorsmod.Wrap(types.ErrWasmInvalidContractModification, clienttypes.ErrClientNotFound.Error()) + } + + clientState, err := unmarshalClientState(cdc, bz) + if err != nil { + return nil, errorsmod.Wrap(types.ErrWasmInvalidContractModification, err.Error()) + } + + cs, ok := clientState.(*types.ClientState) + if !ok { + return nil, errorsmod.Wrapf(types.ErrWasmInvalidContractModification, "expected client state type %T, got %T", (*types.ClientState)(nil), clientState) + } + + return cs, nil +} + +// unmarshalClientState unmarshals the client state from the given bytes. +func unmarshalClientState(cdc codec.BinaryCodec, bz []byte) (exported.ClientState, error) { + var clientState exported.ClientState + if err := cdc.UnmarshalInterface(bz, &clientState); err != nil { + return nil, err + } + + return clientState, nil +} + // getEnv returns the state of the blockchain environment the contract is running on func getEnv(ctx sdk.Context, contractAddr string) wasmvmtypes.Env { chainID := ctx.BlockHeader().ChainID @@ -223,3 +300,20 @@ func canonicalizeAddress(human string) ([]byte, uint64, error) { func validateAddress(human string) (uint64, error) { return 0, errors.New("validateAddress not implemented") } + +// checkResponse returns an error if the response from a sudo, instantiate or migrate call +// to the Wasm VM contains messages, events or attributes. +func checkResponse(response *wasmvmtypes.Response) error { + // Only allow Data to flow back to us. SubMessages, Events and Attributes are not allowed. + if len(response.Messages) > 0 { + return types.ErrWasmSubMessagesNotAllowed + } + if len(response.Events) > 0 { + return types.ErrWasmEventsNotAllowed + } + if len(response.Attributes) > 0 { + return types.ErrWasmAttributesNotAllowed + } + + return nil +} diff --git a/modules/light-clients/08-wasm/keeper/keeper.go b/modules/light-clients/08-wasm/keeper/keeper.go index da97673e503..bebcafb298c 100644 --- a/modules/light-clients/08-wasm/keeper/keeper.go +++ b/modules/light-clients/08-wasm/keeper/keeper.go @@ -135,7 +135,7 @@ func (k Keeper) MigrateContractCode(ctx sdk.Context, clientID string, newChecksu // persisted to the client store. clientState.Checksum = newChecksum - err = types.WasmMigrate(ctx, k.GetVM(), k.cdc, clientStore, clientState, clientID, migrateMsg) + err = WasmMigrate(ctx, k, k.GetVM(), k.cdc, clientStore, clientState, clientID, migrateMsg) if err != nil { return err } diff --git a/modules/light-clients/08-wasm/types/export_test.go b/modules/light-clients/08-wasm/types/export_test.go index 134e114f223..0dd12853582 100644 --- a/modules/light-clients/08-wasm/types/export_test.go +++ b/modules/light-clients/08-wasm/types/export_test.go @@ -11,20 +11,15 @@ import ( // MaxWasmSize is the maximum size of a wasm code in bytes. const MaxWasmSize = maxWasmSize -var ( - SubjectPrefix = subjectPrefix - SubstitutePrefix = substitutePrefix -) - // NewMigrateProposalWrappedStore is a wrapper around newMigrateProposalWrappedStore to allow the function to be directly called in tests. // //nolint:revive // Returning unexported type for testing purposes. -func NewMigrateProposalWrappedStore(subjectStore, substituteStore storetypes.KVStore) migrateClientWrappedStore { +func NewMigrateProposalWrappedStore(subjectStore, substituteStore storetypes.KVStore) MigrateClientWrappedStore { return NewMigrateClientWrappedStore(subjectStore, substituteStore) } // GetStore is a wrapper around getStore to allow the function to be directly called in tests. -func (ws migrateClientWrappedStore) GetStore(key []byte) (storetypes.KVStore, bool) { +func (ws MigrateClientWrappedStore) GetStore(key []byte) (storetypes.KVStore, bool) { return ws.getStore(key) } diff --git a/modules/light-clients/08-wasm/types/gas_register.go b/modules/light-clients/08-wasm/types/gas_register.go index 54f15bc83fc..512df7a8b0f 100644 --- a/modules/light-clients/08-wasm/types/gas_register.go +++ b/modules/light-clients/08-wasm/types/gas_register.go @@ -63,10 +63,13 @@ const ( // default: 0.15 gas. // see https://github.com/CosmWasm/wasmd/pull/898#discussion_r937727200 -var defaultPerByteUncompressCost = wasmvmtypes.UFraction{ - Numerator: 15, - Denominator: 100, -} +var ( + defaultPerByteUncompressCost = wasmvmtypes.UFraction{ + Numerator: 15, + Denominator: 100, + } + VMGasRegister = NewDefaultWasmGasRegister() +) // DefaultPerByteUncompressCost is how much SDK gas we charge per source byte to unpack func DefaultPerByteUncompressCost() wasmvmtypes.UFraction { diff --git a/modules/light-clients/08-wasm/types/store.go b/modules/light-clients/08-wasm/types/store.go index 4a18502f218..cb0f6424dd0 100644 --- a/modules/light-clients/08-wasm/types/store.go +++ b/modules/light-clients/08-wasm/types/store.go @@ -20,10 +20,10 @@ import ( var ( _ wasmvmtypes.KVStore = &storeAdapter{} - _ storetypes.KVStore = &migrateClientWrappedStore{} + _ storetypes.KVStore = &MigrateClientWrappedStore{} - subjectPrefix = []byte("subject/") - substitutePrefix = []byte("substitute/") + SubjectPrefix = []byte("subject/") + SubstitutePrefix = []byte("substitute/") ) // GetClientState retrieves the client state from the store using the provided KVStore and codec. @@ -50,17 +50,17 @@ func CreateChecksum(code []byte) (Checksum, error) { return wasmvm.CreateChecksum(code) } -// migrateClientWrappedStore combines two KVStores into one. +// MigrateClientWrappedStore combines two KVStores into one. // // Both stores are used for reads, but only the subjectStore is used for writes. For all operations, the key // is checked to determine which store to use and must be prefixed with either "subject/" or "substitute/" accordingly. // If the key is not prefixed with either "subject/" or "substitute/", a default action is taken (e.g. no-op for Set/Delete). -type migrateClientWrappedStore struct { +type MigrateClientWrappedStore struct { subjectStore storetypes.KVStore substituteStore storetypes.KVStore } -func NewMigrateClientWrappedStore(subjectStore, substituteStore storetypes.KVStore) migrateClientWrappedStore { +func NewMigrateClientWrappedStore(subjectStore, substituteStore storetypes.KVStore) MigrateClientWrappedStore { if subjectStore == nil { panic(errors.New("subjectStore must not be nil")) } @@ -68,7 +68,7 @@ func NewMigrateClientWrappedStore(subjectStore, substituteStore storetypes.KVSto panic(errors.New("substituteStore must not be nil")) } - return migrateClientWrappedStore{ + return MigrateClientWrappedStore{ subjectStore: subjectStore, substituteStore: substituteStore, } @@ -77,7 +77,7 @@ func NewMigrateClientWrappedStore(subjectStore, substituteStore storetypes.KVSto // Get implements the storetypes.KVStore interface. It allows reads from both the subjectStore and substituteStore. // // Get will return an empty byte slice if the key is not prefixed with either "subject/" or "substitute/". -func (ws migrateClientWrappedStore) Get(key []byte) []byte { +func (ws MigrateClientWrappedStore) Get(key []byte) []byte { prefix, key := splitPrefix(key) store, found := ws.getStore(prefix) @@ -92,7 +92,7 @@ func (ws migrateClientWrappedStore) Get(key []byte) []byte { // Has implements the storetypes.KVStore interface. It allows reads from both the subjectStore and substituteStore. // // Note: contracts do not have access to the Has method, it is only implemented here to satisfy the storetypes.KVStore interface. -func (ws migrateClientWrappedStore) Has(key []byte) bool { +func (ws MigrateClientWrappedStore) Has(key []byte) bool { prefix, key := splitPrefix(key) store, found := ws.getStore(prefix) @@ -107,9 +107,9 @@ func (ws migrateClientWrappedStore) Has(key []byte) bool { // Set implements the storetypes.KVStore interface. It allows writes solely to the subjectStore. // // Set will no-op if the key is not prefixed with "subject/". -func (ws migrateClientWrappedStore) Set(key, value []byte) { +func (ws MigrateClientWrappedStore) Set(key, value []byte) { prefix, key := splitPrefix(key) - if !bytes.Equal(prefix, subjectPrefix) { + if !bytes.Equal(prefix, SubjectPrefix) { return // no-op } @@ -119,9 +119,9 @@ func (ws migrateClientWrappedStore) Set(key, value []byte) { // Delete implements the storetypes.KVStore interface. It allows deletions solely to the subjectStore. // // Delete will no-op if the key is not prefixed with "subject/". -func (ws migrateClientWrappedStore) Delete(key []byte) { +func (ws MigrateClientWrappedStore) Delete(key []byte) { prefix, key := splitPrefix(key) - if !bytes.Equal(prefix, subjectPrefix) { + if !bytes.Equal(prefix, SubjectPrefix) { return // no-op } @@ -131,7 +131,7 @@ func (ws migrateClientWrappedStore) Delete(key []byte) { // Iterator implements the storetypes.KVStore interface. It allows iteration over both the subjectStore and substituteStore. // // Iterator will return a closed iterator if the start or end keys are not prefixed with either "subject/" or "substitute/". -func (ws migrateClientWrappedStore) Iterator(start, end []byte) storetypes.Iterator { +func (ws MigrateClientWrappedStore) Iterator(start, end []byte) storetypes.Iterator { prefixStart, start := splitPrefix(start) prefixEnd, end := splitPrefix(end) @@ -150,7 +150,7 @@ func (ws migrateClientWrappedStore) Iterator(start, end []byte) storetypes.Itera // ReverseIterator implements the storetypes.KVStore interface. It allows iteration over both the subjectStore and substituteStore. // // ReverseIterator will return a closed iterator if the start or end keys are not prefixed with either "subject/" or "substitute/". -func (ws migrateClientWrappedStore) ReverseIterator(start, end []byte) storetypes.Iterator { +func (ws MigrateClientWrappedStore) ReverseIterator(start, end []byte) storetypes.Iterator { prefixStart, start := splitPrefix(start) prefixEnd, end := splitPrefix(end) @@ -167,17 +167,17 @@ func (ws migrateClientWrappedStore) ReverseIterator(start, end []byte) storetype } // GetStoreType implements the storetypes.KVStore interface, it is implemented solely to satisfy the interface. -func (ws migrateClientWrappedStore) GetStoreType() storetypes.StoreType { +func (ws MigrateClientWrappedStore) GetStoreType() storetypes.StoreType { return ws.substituteStore.GetStoreType() } // CacheWrap implements the storetypes.KVStore interface, it is implemented solely to satisfy the interface. -func (ws migrateClientWrappedStore) CacheWrap() storetypes.CacheWrap { +func (ws MigrateClientWrappedStore) CacheWrap() storetypes.CacheWrap { return cachekv.NewStore(ws) } // CacheWrapWithTrace implements the storetypes.KVStore interface, it is implemented solely to satisfy the interface. -func (ws migrateClientWrappedStore) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap { +func (ws MigrateClientWrappedStore) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap { return cachekv.NewStore(tracekv.NewStore(ws, w, tc)) } @@ -186,10 +186,10 @@ func (ws migrateClientWrappedStore) CacheWrapWithTrace(w io.Writer, tc storetype // the substituteStore is returned. // // If the key is not prefixed with either "subject/" or "substitute/", a nil store is returned and the boolean flag is false. -func (ws migrateClientWrappedStore) getStore(prefix []byte) (storetypes.KVStore, bool) { - if bytes.Equal(prefix, subjectPrefix) { +func (ws MigrateClientWrappedStore) getStore(prefix []byte) (storetypes.KVStore, bool) { + if bytes.Equal(prefix, SubjectPrefix) { return ws.subjectStore, true - } else if bytes.Equal(prefix, substitutePrefix) { + } else if bytes.Equal(prefix, SubstitutePrefix) { return ws.substituteStore, true } @@ -198,7 +198,7 @@ func (ws migrateClientWrappedStore) getStore(prefix []byte) (storetypes.KVStore, // closedIterator returns an iterator that is always closed, used when Iterator() or ReverseIterator() is called // with an invalid prefix or start/end key. -func (ws migrateClientWrappedStore) closedIterator() storetypes.Iterator { +func (ws MigrateClientWrappedStore) closedIterator() storetypes.Iterator { // Create a dummy iterator that is always closed right away. it := ws.subjectStore.Iterator([]byte{0}, []byte{1}) it.Close() @@ -209,10 +209,10 @@ func (ws migrateClientWrappedStore) closedIterator() storetypes.Iterator { // splitPrefix splits the key into the prefix and the key itself, if the key is prefixed with either "subject/" or "substitute/". // If the key is not prefixed with either "subject/" or "substitute/", the prefix is nil. func splitPrefix(key []byte) ([]byte, []byte) { - if bytes.HasPrefix(key, subjectPrefix) { - return subjectPrefix, bytes.TrimPrefix(key, subjectPrefix) - } else if bytes.HasPrefix(key, substitutePrefix) { - return substitutePrefix, bytes.TrimPrefix(key, substitutePrefix) + if bytes.HasPrefix(key, SubjectPrefix) { + return SubjectPrefix, bytes.TrimPrefix(key, SubjectPrefix) + } else if bytes.HasPrefix(key, SubstitutePrefix) { + return SubstitutePrefix, bytes.TrimPrefix(key, SubstitutePrefix) } return nil, key diff --git a/modules/light-clients/08-wasm/types/vm.go b/modules/light-clients/08-wasm/types/vm.go index 87307d0ca61..ab1254f4c2b 100644 --- a/modules/light-clients/08-wasm/types/vm.go +++ b/modules/light-clients/08-wasm/types/vm.go @@ -1,318 +1 @@ package types - -import ( - "bytes" - "encoding/hex" - "encoding/json" - "errors" - - wasmvm "github.com/CosmWasm/wasmvm/v2" - wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" - - errorsmod "cosmossdk.io/errors" - storetypes "cosmossdk.io/store/types" - - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" - "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" - host "github.com/cosmos/ibc-go/v8/modules/core/24-host" - "github.com/cosmos/ibc-go/v8/modules/core/exported" -) - -var ( - VMGasRegister = NewDefaultWasmGasRegister() - // wasmvmAPI is a wasmvm.GoAPI implementation that is passed to the wasmvm, it - // doesn't implement any functionality, directly returning an error. - wasmvmAPI = wasmvm.GoAPI{ - HumanizeAddress: humanizeAddress, - CanonicalizeAddress: canonicalizeAddress, - ValidateAddress: validateAddress, - } -) - -// instantiateContract calls vm.Instantiate with appropriate arguments. -func instantiateContract(ctx sdk.Context, vm ibcwasm.WasmEngine, clientID string, clientStore storetypes.KVStore, checksum Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) { - sdkGasMeter := ctx.GasMeter() - multipliedGasMeter := NewMultipliedGasMeter(sdkGasMeter, VMGasRegister) - gasLimit := VMGasRegister.RuntimeGasForContract(ctx) - - env := getEnv(ctx, clientID) - - msgInfo := wasmvmtypes.MessageInfo{ - Sender: "", - Funds: nil, - } - - ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: instantiate") - resp, gasUsed, err := vm.Instantiate(checksum, env, msgInfo, msg, NewStoreAdapter(clientStore), wasmvmAPI, NewQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, CostJSONDeserialization) - VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) - return resp, err -} - -// callContract calls vm.Sudo with internally constructed gas meter and environment. -func callContract(ctx sdk.Context, vm ibcwasm.WasmEngine, clientID string, clientStore storetypes.KVStore, checksum Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) { - sdkGasMeter := ctx.GasMeter() - multipliedGasMeter := NewMultipliedGasMeter(sdkGasMeter, VMGasRegister) - gasLimit := VMGasRegister.RuntimeGasForContract(ctx) - - env := getEnv(ctx, clientID) - - ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: sudo") - resp, gasUsed, err := vm.Sudo(checksum, env, msg, NewStoreAdapter(clientStore), wasmvmAPI, NewQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, CostJSONDeserialization) - VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) - return resp, err -} - -// migrateContract calls vm.Migrate with internally constructed gas meter and environment. -func migrateContract(ctx sdk.Context, vm ibcwasm.WasmEngine, clientID string, clientStore storetypes.KVStore, checksum Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) { - sdkGasMeter := ctx.GasMeter() - multipliedGasMeter := NewMultipliedGasMeter(sdkGasMeter, VMGasRegister) - gasLimit := VMGasRegister.RuntimeGasForContract(ctx) - - env := getEnv(ctx, clientID) - - ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: migrate") - resp, gasUsed, err := vm.Migrate(checksum, env, msg, NewStoreAdapter(clientStore), wasmvmAPI, NewQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, CostJSONDeserialization) - VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) - return resp, err -} - -// queryContract calls vm.Query. -func queryContract(ctx sdk.Context, vm ibcwasm.WasmEngine, clientID string, clientStore storetypes.KVStore, checksum Checksum, msg []byte) (*wasmvmtypes.QueryResult, error) { - sdkGasMeter := ctx.GasMeter() - multipliedGasMeter := NewMultipliedGasMeter(sdkGasMeter, VMGasRegister) - gasLimit := VMGasRegister.RuntimeGasForContract(ctx) - - env := getEnv(ctx, clientID) - - ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: query") - resp, gasUsed, err := vm.Query(checksum, env, msg, NewStoreAdapter(clientStore), wasmvmAPI, NewQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, CostJSONDeserialization) - VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) - return resp, err -} - -// WasmInstantiate accepts a message to instantiate a wasm contract, JSON encodes it and calls instantiateContract. -func WasmInstantiate(ctx sdk.Context, vm ibcwasm.WasmEngine, clientID string, cdc codec.BinaryCodec, clientStore storetypes.KVStore, cs *ClientState, payload InstantiateMessage) error { - encodedData, err := json.Marshal(payload) - if err != nil { - return errorsmod.Wrap(err, "failed to marshal payload for wasm contract instantiation") - } - - checksum := cs.Checksum - res, err := instantiateContract(ctx, vm, clientID, clientStore, checksum, encodedData) - if err != nil { - return errorsmod.Wrap(ErrVMError, err.Error()) - } - if res.Err != "" { - return errorsmod.Wrap(ErrWasmContractCallFailed, res.Err) - } - - if err = CheckResponse(res.Ok); err != nil { - return errorsmod.Wrapf(err, "checksum (%s)", hex.EncodeToString(cs.Checksum)) - } - - newClientState, err := ValidatePostExecutionClientState(clientStore, cdc) - if err != nil { - return err - } - - // Checksum should only be able to be modified during migration. - if !bytes.Equal(checksum, newClientState.Checksum) { - return errorsmod.Wrapf(ErrWasmInvalidContractModification, "expected checksum %s, got %s", hex.EncodeToString(checksum), hex.EncodeToString(newClientState.Checksum)) - } - - return nil -} - -// WasmSudo calls the contract with the given payload and returns the result. -// WasmSudo returns an error if: -// - the payload cannot be marshaled to JSON -// - the contract call returns an error -// - the response of the contract call contains non-empty messages -// - the response of the contract call contains non-empty events -// - the response of the contract call contains non-empty attributes -// - the data bytes of the response cannot be unmarshaled into the result type -func WasmSudo[T ContractResult](ctx sdk.Context, vm ibcwasm.WasmEngine, clientID string, cdc codec.BinaryCodec, clientStore storetypes.KVStore, cs *ClientState, payload SudoMsg) (T, error) { - var result T - - encodedData, err := json.Marshal(payload) - if err != nil { - return result, errorsmod.Wrap(err, "failed to marshal payload for wasm execution") - } - - checksum := cs.Checksum - res, err := callContract(ctx, vm, clientID, clientStore, checksum, encodedData) - if err != nil { - return result, errorsmod.Wrap(ErrVMError, err.Error()) - } - if res.Err != "" { - return result, errorsmod.Wrap(ErrWasmContractCallFailed, res.Err) - } - - if err = CheckResponse(res.Ok); err != nil { - return result, errorsmod.Wrapf(err, "checksum (%s)", hex.EncodeToString(cs.Checksum)) - } - - if err := json.Unmarshal(res.Ok.Data, &result); err != nil { - return result, errorsmod.Wrap(ErrWasmInvalidResponseData, err.Error()) - } - - newClientState, err := ValidatePostExecutionClientState(clientStore, cdc) - if err != nil { - return result, err - } - - // Checksum should only be able to be modified during migration. - if !bytes.Equal(checksum, newClientState.Checksum) { - return result, errorsmod.Wrapf(ErrWasmInvalidContractModification, "expected checksum %s, got %s", hex.EncodeToString(checksum), hex.EncodeToString(newClientState.Checksum)) - } - - return result, nil -} - -// WasmMigrate migrate calls the migrate entry point of the contract with the given payload and returns the result. -// WasmMigrate returns an error if: -// - the contract migration returns an error -func WasmMigrate(ctx sdk.Context, vm ibcwasm.WasmEngine, cdc codec.BinaryCodec, clientStore storetypes.KVStore, cs *ClientState, clientID string, payload []byte) error { - res, err := migrateContract(ctx, vm, clientID, clientStore, cs.Checksum, payload) - if err != nil { - return errorsmod.Wrap(ErrVMError, err.Error()) - } - if res.Err != "" { - return errorsmod.Wrap(ErrWasmContractCallFailed, res.Err) - } - - if err = CheckResponse(res.Ok); err != nil { - return errorsmod.Wrapf(err, "checksum (%s)", hex.EncodeToString(cs.Checksum)) - } - - _, err = ValidatePostExecutionClientState(clientStore, cdc) - return err -} - -// WasmQuery queries the contract with the given payload and returns the result. -// WasmQuery returns an error if: -// - the payload cannot be marshaled to JSON -// - the contract query returns an error -// - the data bytes of the response cannot be unmarshal into the result type -func WasmQuery[T ContractResult](ctx sdk.Context, vm ibcwasm.WasmEngine, clientID string, clientStore storetypes.KVStore, cs *ClientState, payload QueryMsg) (T, error) { - var result T - - encodedData, err := json.Marshal(payload) - if err != nil { - return result, errorsmod.Wrap(err, "failed to marshal payload for wasm query") - } - - res, err := queryContract(ctx, vm, clientID, clientStore, cs.Checksum, encodedData) - if err != nil { - return result, errorsmod.Wrap(ErrVMError, err.Error()) - } - if res.Err != "" { - return result, errorsmod.Wrap(ErrWasmContractCallFailed, res.Err) - } - - if err := json.Unmarshal(res.Ok, &result); err != nil { - return result, errorsmod.Wrapf(ErrWasmInvalidResponseData, "failed to unmarshal result of wasm query: %v", err) - } - - return result, nil -} - -// ValidatePostExecutionClientState validates that the contract has not many any invalid modifications -// to the client state during execution. It ensures that -// - the client state is still present -// - the client state can be unmarshaled successfully. -// - the client state is of type *ClientState -func ValidatePostExecutionClientState(clientStore storetypes.KVStore, cdc codec.BinaryCodec) (*ClientState, error) { - key := host.ClientStateKey() - _, ok := clientStore.(migrateClientWrappedStore) - if ok { - key = append(subjectPrefix, key...) - } - - bz := clientStore.Get(key) - if len(bz) == 0 { - return nil, errorsmod.Wrap(ErrWasmInvalidContractModification, types.ErrClientNotFound.Error()) - } - - clientState, err := unmarshalClientState(cdc, bz) - if err != nil { - return nil, errorsmod.Wrap(ErrWasmInvalidContractModification, err.Error()) - } - - cs, ok := clientState.(*ClientState) - if !ok { - return nil, errorsmod.Wrapf(ErrWasmInvalidContractModification, "expected client state type %T, got %T", (*ClientState)(nil), clientState) - } - - return cs, nil -} - -// unmarshalClientState unmarshals the client state from the given bytes. -func unmarshalClientState(cdc codec.BinaryCodec, bz []byte) (exported.ClientState, error) { - var clientState exported.ClientState - if err := cdc.UnmarshalInterface(bz, &clientState); err != nil { - return nil, err - } - - return clientState, nil -} - -// getEnv returns the state of the blockchain environment the contract is running on -func getEnv(ctx sdk.Context, contractAddr string) wasmvmtypes.Env { - chainID := ctx.BlockHeader().ChainID - height := ctx.BlockHeader().Height - - // safety checks before casting below - if height < 0 { - panic(errors.New("block height must never be negative")) - } - nsec := ctx.BlockTime().UnixNano() - if nsec < 0 { - panic(errors.New("block (unix) time must never be negative ")) - } - - env := wasmvmtypes.Env{ - Block: wasmvmtypes.BlockInfo{ - Height: uint64(height), - Time: wasmvmtypes.Uint64(nsec), - ChainID: chainID, - }, - Contract: wasmvmtypes.ContractInfo{ - Address: contractAddr, - }, - } - - return env -} - -func humanizeAddress(canon []byte) (string, uint64, error) { - return "", 0, errors.New("humanizeAddress not implemented") -} - -func canonicalizeAddress(human string) ([]byte, uint64, error) { - return nil, 0, errors.New("canonicalizeAddress not implemented") -} - -func validateAddress(human string) (uint64, error) { - return 0, errors.New("validateAddress not implemented") -} - -// CheckResponse returns an error if the response from a sudo, instantiate or migrate call -// to the Wasm VM contains messages, events or attributes. -func CheckResponse(response *wasmvmtypes.Response) error { - // Only allow Data to flow back to us. SubMessages, Events and Attributes are not allowed. - if len(response.Messages) > 0 { - return ErrWasmSubMessagesNotAllowed - } - if len(response.Events) > 0 { - return ErrWasmEventsNotAllowed - } - if len(response.Attributes) > 0 { - return ErrWasmAttributesNotAllowed - } - - return nil -} diff --git a/modules/light-clients/08-wasm/types/vm_test.go b/modules/light-clients/08-wasm/types/vm_test.go deleted file mode 100644 index 70c0cd34b49..00000000000 --- a/modules/light-clients/08-wasm/types/vm_test.go +++ /dev/null @@ -1,594 +0,0 @@ -package types_test - -import ( - "encoding/json" - - wasmvm "github.com/CosmWasm/wasmvm/v2" - wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" - - wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing" - "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" - clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" - host "github.com/cosmos/ibc-go/v8/modules/core/24-host" - "github.com/cosmos/ibc-go/v8/modules/core/exported" - ibctm "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" - localhost "github.com/cosmos/ibc-go/v8/modules/light-clients/09-localhost" -) - -func (suite *TypesTestSuite) TestWasmInstantiate() { - testCases := []struct { - name string - malleate func() - expError error - }{ - { - "success", - func() { - suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, initMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - // Ensure GoAPI is set - suite.Require().NotNil(goapi.CanonicalizeAddress) - suite.Require().NotNil(goapi.HumanizeAddress) - suite.Require().NotNil(goapi.ValidateAddress) - - var payload types.InstantiateMessage - err := json.Unmarshal(initMsg, &payload) - suite.Require().NoError(err) - - wrappedClientState := clienttypes.MustUnmarshalClientState(suite.chainA.App.AppCodec(), payload.ClientState).(*ibctm.ClientState) - - clientState := types.NewClientState(payload.ClientState, payload.Checksum, wrappedClientState.LatestHeight) - clientStateBz := clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), clientState) - store.Set(host.ClientStateKey(), clientStateBz) - - consensusState := types.NewConsensusState(payload.ConsensusState) - consensusStateBz := clienttypes.MustMarshalConsensusState(suite.chainA.App.AppCodec(), consensusState) - store.Set(host.ConsensusStateKey(clientState.LatestHeight), consensusStateBz) - - return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{}}, 0, nil - } - }, - nil, - }, - { - "failure: vm returns error", - func() { - suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - return nil, 0, wasmtesting.ErrMockVM - } - }, - types.ErrVMError, - }, - { - "failure: contract returns error", - func() { - suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - return &wasmvmtypes.ContractResult{Err: wasmtesting.ErrMockContract.Error()}, 0, nil - } - }, - types.ErrWasmContractCallFailed, - }, - { - "failure: contract returns non-empty messages", - func() { - suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - resp := wasmvmtypes.Response{Messages: []wasmvmtypes.SubMsg{{}}} - - return &wasmvmtypes.ContractResult{Ok: &resp}, wasmtesting.DefaultGasUsed, nil - } - }, - types.ErrWasmSubMessagesNotAllowed, - }, - { - "failure: contract returns non-empty events", - func() { - suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - resp := wasmvmtypes.Response{Events: []wasmvmtypes.Event{{}}} - - return &wasmvmtypes.ContractResult{Ok: &resp}, wasmtesting.DefaultGasUsed, nil - } - }, - types.ErrWasmEventsNotAllowed, - }, - { - "failure: contract returns non-empty attributes", - func() { - suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - resp := wasmvmtypes.Response{Attributes: []wasmvmtypes.EventAttribute{{}}} - - return &wasmvmtypes.ContractResult{Ok: &resp}, wasmtesting.DefaultGasUsed, nil - } - }, - types.ErrWasmAttributesNotAllowed, - }, - { - "failure: change clientstate type", - func() { - suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - newClientState := localhost.NewClientState(clienttypes.NewHeight(1, 1)) - store.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), newClientState)) - - data, err := json.Marshal(types.EmptyResult{}) - suite.Require().NoError(err) - return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: data}}, wasmtesting.DefaultGasUsed, nil - } - }, - types.ErrWasmInvalidContractModification, - }, - { - "failure: delete clientstate", - func() { - suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - store.Delete(host.ClientStateKey()) - data, err := json.Marshal(types.EmptyResult{}) - suite.Require().NoError(err) - return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: data}}, wasmtesting.DefaultGasUsed, nil - } - }, - types.ErrWasmInvalidContractModification, - }, - { - "failure: unmarshallable clientstate", - func() { - suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - store.Set(host.ClientStateKey(), []byte("invalid json")) - data, err := json.Marshal(types.EmptyResult{}) - suite.Require().NoError(err) - return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: data}}, wasmtesting.DefaultGasUsed, nil - } - }, - types.ErrWasmInvalidContractModification, - }, - { - "failure: change checksum", - func() { - suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, initMsg []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - var payload types.InstantiateMessage - err := json.Unmarshal(initMsg, &payload) - suite.Require().NoError(err) - - // Change the checksum to something else. - wrappedClientState := clienttypes.MustUnmarshalClientState(suite.chainA.App.AppCodec(), payload.ClientState).(*ibctm.ClientState) - clientState := types.NewClientState(payload.ClientState, []byte("new checksum"), wrappedClientState.LatestHeight) - store.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), clientState)) - - resp, err := json.Marshal(types.UpdateStateResult{}) - suite.Require().NoError(err) - - return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: resp}}, wasmtesting.DefaultGasUsed, nil - } - }, - types.ErrWasmInvalidContractModification, - }, - } - - for _, tc := range testCases { - tc := tc - suite.Run(tc.name, func() { - suite.SetupWasmWithMockVM() - - tc.malleate() - - initMsg := types.InstantiateMessage{ - ClientState: clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), wasmtesting.MockTendermitClientState), - ConsensusState: clienttypes.MustMarshalConsensusState(suite.chainA.App.AppCodec(), wasmtesting.MockTendermintClientConsensusState), - Checksum: suite.checksum, - } - - clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), defaultWasmClientID) - vm := GetSimApp(suite.chainA).WasmClientKeeper.GetVM() - err := types.WasmInstantiate(suite.chainA.GetContext(), vm, defaultWasmClientID, suite.chainA.App.AppCodec(), clientStore, &types.ClientState{Checksum: suite.checksum}, initMsg) - - expPass := tc.expError == nil - if expPass { - suite.Require().NoError(err) - } else { - suite.Require().ErrorIs(err, tc.expError) - } - }) - } -} - -func (suite *TypesTestSuite) TestWasmMigrate() { - testCases := []struct { - name string - malleate func() - expError error - }{ - { - "success", - func() { - suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, goapi wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - // Ensure GoAPI is set - suite.Require().NotNil(goapi.CanonicalizeAddress) - suite.Require().NotNil(goapi.HumanizeAddress) - suite.Require().NotNil(goapi.ValidateAddress) - - resp, err := json.Marshal(types.EmptyResult{}) - suite.Require().NoError(err) - - return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: resp}}, 0, nil - } - }, - nil, - }, - { - "failure: vm returns error", - func() { - suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - return nil, 0, wasmtesting.ErrMockVM - } - }, - types.ErrVMError, - }, - { - "failure: contract returns error", - func() { - suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - return &wasmvmtypes.ContractResult{Err: wasmtesting.ErrMockContract.Error()}, 0, nil - } - }, - types.ErrWasmContractCallFailed, - }, - { - "failure: contract returns non-empty messages", - func() { - suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - resp := wasmvmtypes.Response{Messages: []wasmvmtypes.SubMsg{{}}} - - return &wasmvmtypes.ContractResult{Ok: &resp}, wasmtesting.DefaultGasUsed, nil - } - }, - types.ErrWasmSubMessagesNotAllowed, - }, - { - "failure: contract returns non-empty events", - func() { - suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - resp := wasmvmtypes.Response{Events: []wasmvmtypes.Event{{}}} - - return &wasmvmtypes.ContractResult{Ok: &resp}, wasmtesting.DefaultGasUsed, nil - } - }, - types.ErrWasmEventsNotAllowed, - }, - { - "failure: contract returns non-empty attributes", - func() { - suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - resp := wasmvmtypes.Response{Attributes: []wasmvmtypes.EventAttribute{{}}} - - return &wasmvmtypes.ContractResult{Ok: &resp}, wasmtesting.DefaultGasUsed, nil - } - }, - types.ErrWasmAttributesNotAllowed, - }, - { - "failure: change clientstate type", - func() { - suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - newClientState := localhost.NewClientState(clienttypes.NewHeight(1, 1)) - store.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), newClientState)) - - data, err := json.Marshal(types.EmptyResult{}) - suite.Require().NoError(err) - return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: data}}, wasmtesting.DefaultGasUsed, nil - } - }, - types.ErrWasmInvalidContractModification, - }, - { - "failure: delete clientstate", - func() { - suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - store.Delete(host.ClientStateKey()) - data, err := json.Marshal(types.EmptyResult{}) - suite.Require().NoError(err) - return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: data}}, wasmtesting.DefaultGasUsed, nil - } - }, - types.ErrWasmInvalidContractModification, - }, - { - "failure: unmarshallable clientstate", - func() { - suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - store.Set(host.ClientStateKey(), []byte("invalid json")) - data, err := json.Marshal(types.EmptyResult{}) - suite.Require().NoError(err) - return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: data}}, wasmtesting.DefaultGasUsed, nil - } - }, - types.ErrWasmInvalidContractModification, - }, - } - - for _, tc := range testCases { - tc := tc - suite.Run(tc.name, func() { - suite.SetupWasmWithMockVM() - - endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) - err := endpoint.CreateClient() - suite.Require().NoError(err) - - tc.malleate() - - clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), defaultWasmClientID) - vm := GetSimApp(suite.chainA).WasmClientKeeper.GetVM() - err = types.WasmMigrate(suite.chainA.GetContext(), vm, suite.chainA.App.AppCodec(), clientStore, &types.ClientState{}, defaultWasmClientID, []byte("{}")) - - expPass := tc.expError == nil - if expPass { - suite.Require().NoError(err) - } else { - suite.Require().ErrorIs(err, tc.expError) - } - }) - } -} - -func (suite *TypesTestSuite) TestWasmQuery() { - var payload types.QueryMsg - - testCases := []struct { - name string - malleate func() - expError error - }{ - { - "success", - func() { - suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, goapi wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { - // Ensure GoAPI is set - suite.Require().NotNil(goapi.CanonicalizeAddress) - suite.Require().NotNil(goapi.HumanizeAddress) - suite.Require().NotNil(goapi.ValidateAddress) - - resp, err := json.Marshal(types.StatusResult{Status: exported.Frozen.String()}) - suite.Require().NoError(err) - - return &wasmvmtypes.QueryResult{Ok: resp}, wasmtesting.DefaultGasUsed, nil - }) - }, - nil, - }, - { - "failure: vm returns error", - func() { - suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { - return nil, wasmtesting.DefaultGasUsed, wasmtesting.ErrMockVM - }) - }, - types.ErrVMError, - }, - { - "failure: contract returns error", - func() { - suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { - return &wasmvmtypes.QueryResult{Err: wasmtesting.ErrMockContract.Error()}, wasmtesting.DefaultGasUsed, nil - }) - }, - types.ErrWasmContractCallFailed, - }, - { - "failure: response fails to unmarshal", - func() { - suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { - return &wasmvmtypes.QueryResult{Ok: []byte("invalid json")}, wasmtesting.DefaultGasUsed, nil - }) - }, - types.ErrWasmInvalidResponseData, - }, - } - - for _, tc := range testCases { - tc := tc - suite.Run(tc.name, func() { - suite.SetupWasmWithMockVM() - - endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) - err := endpoint.CreateClient() - suite.Require().NoError(err) - - clientState := endpoint.GetClientState() - clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpoint.ClientID) - - wasmClientState, ok := clientState.(*types.ClientState) - suite.Require().True(ok) - - payload = types.QueryMsg{Status: &types.StatusMsg{}} - - tc.malleate() - - vm := GetSimApp(suite.chainA).WasmClientKeeper.GetVM() - res, err := types.WasmQuery[types.StatusResult](suite.chainA.GetContext(), vm, endpoint.ClientID, clientStore, wasmClientState, payload) - - expPass := tc.expError == nil - if expPass { - suite.Require().NoError(err) - suite.Require().NotNil(res) - } else { - suite.Require().ErrorIs(err, tc.expError) - } - }) - } -} - -func (suite *TypesTestSuite) TestWasmSudo() { - var payload types.SudoMsg - - testCases := []struct { - name string - malleate func() - expError error - }{ - { - "success", - func() { - suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, goapi wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - // Ensure GoAPI is set - suite.Require().NotNil(goapi.CanonicalizeAddress) - suite.Require().NotNil(goapi.HumanizeAddress) - suite.Require().NotNil(goapi.ValidateAddress) - - resp, err := json.Marshal(types.UpdateStateResult{}) - suite.Require().NoError(err) - - return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: resp}}, wasmtesting.DefaultGasUsed, nil - }) - }, - nil, - }, - { - "failure: vm returns error", - func() { - suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - return nil, wasmtesting.DefaultGasUsed, wasmtesting.ErrMockVM - }) - }, - types.ErrVMError, - }, - { - "failure: contract returns error", - func() { - suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - return &wasmvmtypes.ContractResult{Err: wasmtesting.ErrMockContract.Error()}, wasmtesting.DefaultGasUsed, nil - }) - }, - types.ErrWasmContractCallFailed, - }, - { - "failure: contract returns non-empty messages", - func() { - suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - resp := wasmvmtypes.Response{Messages: []wasmvmtypes.SubMsg{{}}} - - return &wasmvmtypes.ContractResult{Ok: &resp}, wasmtesting.DefaultGasUsed, nil - }) - }, - types.ErrWasmSubMessagesNotAllowed, - }, - { - "failure: contract returns non-empty events", - func() { - suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - resp := wasmvmtypes.Response{Events: []wasmvmtypes.Event{{}}} - - return &wasmvmtypes.ContractResult{Ok: &resp}, wasmtesting.DefaultGasUsed, nil - }) - }, - types.ErrWasmEventsNotAllowed, - }, - { - "failure: contract returns non-empty attributes", - func() { - suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - resp := wasmvmtypes.Response{Attributes: []wasmvmtypes.EventAttribute{{}}} - - return &wasmvmtypes.ContractResult{Ok: &resp}, wasmtesting.DefaultGasUsed, nil - }) - }, - types.ErrWasmAttributesNotAllowed, - }, - { - "failure: response fails to unmarshal", - func() { - suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: []byte("invalid json")}}, wasmtesting.DefaultGasUsed, nil - }) - }, - types.ErrWasmInvalidResponseData, - }, - { - "failure: invalid clientstate type", - func() { - suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - newClientState := localhost.NewClientState(clienttypes.NewHeight(1, 1)) - store.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), newClientState)) - - resp, err := json.Marshal(types.UpdateStateResult{}) - suite.Require().NoError(err) - - return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: resp}}, wasmtesting.DefaultGasUsed, nil - }) - }, - types.ErrWasmInvalidContractModification, - }, - { - "failure: unmarshallable clientstate bytes", - func() { - suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - store.Set(host.ClientStateKey(), []byte("invalid json")) - - resp, err := json.Marshal(types.UpdateStateResult{}) - suite.Require().NoError(err) - - return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: resp}}, wasmtesting.DefaultGasUsed, nil - }) - }, - types.ErrWasmInvalidContractModification, - }, - { - "failure: delete clientstate", - func() { - suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - store.Delete(host.ClientStateKey()) - - resp, err := json.Marshal(types.UpdateStateResult{}) - suite.Require().NoError(err) - - return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: resp}}, wasmtesting.DefaultGasUsed, nil - }) - }, - types.ErrWasmInvalidContractModification, - }, - { - "failure: change checksum", - func() { - suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - clientState := suite.chainA.GetClientState(defaultWasmClientID) - clientState.(*types.ClientState).Checksum = []byte("new checksum") - store.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), clientState)) - - resp, err := json.Marshal(types.UpdateStateResult{}) - suite.Require().NoError(err) - - return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: resp}}, wasmtesting.DefaultGasUsed, nil - }) - }, - types.ErrWasmInvalidContractModification, - }, - } - - for _, tc := range testCases { - tc := tc - suite.Run(tc.name, func() { - suite.SetupWasmWithMockVM() - - endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) - err := endpoint.CreateClient() - suite.Require().NoError(err) - - clientState := endpoint.GetClientState() - clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpoint.ClientID) - - wasmClientState, ok := clientState.(*types.ClientState) - suite.Require().True(ok) - - payload = types.SudoMsg{UpdateState: &types.UpdateStateMsg{}} - - tc.malleate() - - vm := GetSimApp(suite.chainA).WasmClientKeeper.GetVM() - res, err := types.WasmSudo[types.UpdateStateResult](suite.chainA.GetContext(), vm, endpoint.ClientID, suite.chainA.App.AppCodec(), clientStore, wasmClientState, payload) - - expPass := tc.expError == nil - if expPass { - suite.Require().NoError(err) - suite.Require().NotNil(res) - } else { - suite.Require().ErrorIs(err, tc.expError) - } - }) - } -} From 7373de02c1abc32f264831c65d677b4c86b212c4 Mon Sep 17 00:00:00 2001 From: DimitrisJim Date: Sun, 7 Apr 2024 19:58:28 +0300 Subject: [PATCH 09/24] chore: simplify signatures. --- .../08-wasm/keeper/contract_keeper.go | 38 +++++++++---------- .../light-clients/08-wasm/keeper/keeper.go | 2 +- .../08-wasm/light_client_module.go | 28 ++++++-------- .../light-clients/08-wasm/types/querier.go | 14 +++---- modules/light-clients/08-wasm/types/store.go | 20 +++++----- 5 files changed, 48 insertions(+), 54 deletions(-) diff --git a/modules/light-clients/08-wasm/keeper/contract_keeper.go b/modules/light-clients/08-wasm/keeper/contract_keeper.go index ad127a557b0..041122cee06 100644 --- a/modules/light-clients/08-wasm/keeper/contract_keeper.go +++ b/modules/light-clients/08-wasm/keeper/contract_keeper.go @@ -38,7 +38,7 @@ var ( // - the payload cannot be marshaled to JSON // - the contract query returns an error // - the data bytes of the response cannot be unmarshal into the result type -func WasmQuery[T types.ContractResult](ctx sdk.Context, k Keeper, vm ibcwasm.WasmEngine, clientID string, clientStore storetypes.KVStore, cs *types.ClientState, payload types.QueryMsg) (T, error) { +func WasmQuery[T types.ContractResult](ctx sdk.Context, k Keeper, clientID string, clientStore storetypes.KVStore, cs *types.ClientState, payload types.QueryMsg) (T, error) { var result T encodedData, err := json.Marshal(payload) @@ -46,7 +46,7 @@ func WasmQuery[T types.ContractResult](ctx sdk.Context, k Keeper, vm ibcwasm.Was return result, errorsmod.Wrap(err, "failed to marshal payload for wasm query") } - res, err := k.queryContract(ctx, vm, clientID, clientStore, cs.Checksum, encodedData) + res, err := k.queryContract(ctx, clientID, clientStore, cs.Checksum, encodedData) if err != nil { return result, errorsmod.Wrap(types.ErrVMError, err.Error()) } @@ -69,7 +69,7 @@ func WasmQuery[T types.ContractResult](ctx sdk.Context, k Keeper, vm ibcwasm.Was // - the response of the contract call contains non-empty events // - the response of the contract call contains non-empty attributes // - the data bytes of the response cannot be unmarshaled into the result type -func WasmSudo[T types.ContractResult](ctx sdk.Context, k Keeper, vm ibcwasm.WasmEngine, clientID string, cdc codec.BinaryCodec, clientStore storetypes.KVStore, cs *types.ClientState, payload types.SudoMsg) (T, error) { +func WasmSudo[T types.ContractResult](ctx sdk.Context, k Keeper, clientID string, clientStore storetypes.KVStore, cs *types.ClientState, payload types.SudoMsg) (T, error) { var result T encodedData, err := json.Marshal(payload) @@ -78,7 +78,7 @@ func WasmSudo[T types.ContractResult](ctx sdk.Context, k Keeper, vm ibcwasm.Wasm } checksum := cs.Checksum - res, err := k.callContract(ctx, vm, clientID, clientStore, checksum, encodedData) + res, err := k.callContract(ctx, clientID, clientStore, checksum, encodedData) if err != nil { return result, errorsmod.Wrap(types.ErrVMError, err.Error()) } @@ -94,7 +94,7 @@ func WasmSudo[T types.ContractResult](ctx sdk.Context, k Keeper, vm ibcwasm.Wasm return result, errorsmod.Wrap(types.ErrWasmInvalidResponseData, err.Error()) } - newClientState, err := ValidatePostExecutionClientState(clientStore, cdc) + newClientState, err := validatePostExecutionClientState(clientStore, k.Codec()) if err != nil { return result, err } @@ -108,14 +108,14 @@ func WasmSudo[T types.ContractResult](ctx sdk.Context, k Keeper, vm ibcwasm.Wasm } // WasmInstantiate accepts a message to instantiate a wasm contract, JSON encodes it and calls instantiateContract. -func WasmInstantiate(ctx sdk.Context, k Keeper, vm ibcwasm.WasmEngine, clientID string, cdc codec.BinaryCodec, clientStore storetypes.KVStore, cs *types.ClientState, payload types.InstantiateMessage) error { +func WasmInstantiate(ctx sdk.Context, k Keeper, clientID string, clientStore storetypes.KVStore, cs *types.ClientState, payload types.InstantiateMessage) error { encodedData, err := json.Marshal(payload) if err != nil { return errorsmod.Wrap(err, "failed to marshal payload for wasm contract instantiation") } checksum := cs.Checksum - res, err := k.instantiateContract(ctx, vm, clientID, clientStore, checksum, encodedData) + res, err := k.instantiateContract(ctx, k.GetVM(), clientID, clientStore, checksum, encodedData) if err != nil { return errorsmod.Wrap(types.ErrVMError, err.Error()) } @@ -127,7 +127,7 @@ func WasmInstantiate(ctx sdk.Context, k Keeper, vm ibcwasm.WasmEngine, clientID return errorsmod.Wrapf(err, "checksum (%s)", hex.EncodeToString(cs.Checksum)) } - newClientState, err := ValidatePostExecutionClientState(clientStore, cdc) + newClientState, err := validatePostExecutionClientState(clientStore, k.Codec()) if err != nil { return err } @@ -143,8 +143,8 @@ func WasmInstantiate(ctx sdk.Context, k Keeper, vm ibcwasm.WasmEngine, clientID // WasmMigrate migrate calls the migrate entry point of the contract with the given payload and returns the result. // WasmMigrate returns an error if: // - the contract migration returns an error -func WasmMigrate(ctx sdk.Context, keeper Keeper, vm ibcwasm.WasmEngine, cdc codec.BinaryCodec, clientStore storetypes.KVStore, cs *types.ClientState, clientID string, payload []byte) error { - res, err := keeper.migrateContract(ctx, vm, clientID, clientStore, cs.Checksum, payload) +func WasmMigrate(ctx sdk.Context, keeper Keeper, clientStore storetypes.KVStore, cs *types.ClientState, clientID string, payload []byte) error { + res, err := keeper.migrateContract(ctx, clientID, clientStore, cs.Checksum, payload) if err != nil { return errorsmod.Wrap(types.ErrVMError, err.Error()) } @@ -156,12 +156,12 @@ func WasmMigrate(ctx sdk.Context, keeper Keeper, vm ibcwasm.WasmEngine, cdc code return errorsmod.Wrapf(err, "checksum (%s)", hex.EncodeToString(cs.Checksum)) } - _, err = ValidatePostExecutionClientState(clientStore, cdc) + _, err = validatePostExecutionClientState(clientStore, keeper.cdc) return err } // migrateContract calls vm.Migrate with internally constructed gas meter and environment. -func (k Keeper) migrateContract(ctx sdk.Context, vm ibcwasm.WasmEngine, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) { +func (k Keeper) migrateContract(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) { sdkGasMeter := ctx.GasMeter() multipliedGasMeter := types.NewMultipliedGasMeter(sdkGasMeter, VMGasRegister) gasLimit := VMGasRegister.RuntimeGasForContract(ctx) @@ -169,13 +169,13 @@ func (k Keeper) migrateContract(ctx sdk.Context, vm ibcwasm.WasmEngine, clientID env := getEnv(ctx, clientID) ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: migrate") - resp, gasUsed, err := vm.Migrate(checksum, env, msg, types.NewStoreAdapter(clientStore), wasmvmAPI, types.NewQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization) + resp, gasUsed, err := k.GetVM().Migrate(checksum, env, msg, types.NewStoreAdapter(clientStore), wasmvmAPI, types.NewQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization) VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) return resp, err } // queryContract calls vm.Query. -func (k Keeper) queryContract(ctx sdk.Context, vm ibcwasm.WasmEngine, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.QueryResult, error) { +func (k Keeper) queryContract(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.QueryResult, error) { sdkGasMeter := ctx.GasMeter() multipliedGasMeter := types.NewMultipliedGasMeter(sdkGasMeter, VMGasRegister) gasLimit := VMGasRegister.RuntimeGasForContract(ctx) @@ -183,13 +183,13 @@ func (k Keeper) queryContract(ctx sdk.Context, vm ibcwasm.WasmEngine, clientID s env := getEnv(ctx, clientID) ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: query") - resp, gasUsed, err := vm.Query(checksum, env, msg, types.NewStoreAdapter(clientStore), wasmvmAPI, types.NewQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization) + resp, gasUsed, err := k.GetVM().Query(checksum, env, msg, types.NewStoreAdapter(clientStore), wasmvmAPI, types.NewQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization) VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) return resp, err } // callContract calls vm.Sudo with internally constructed gas meter and environment. -func (k Keeper) callContract(ctx sdk.Context, vm ibcwasm.WasmEngine, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) { +func (k Keeper) callContract(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) { sdkGasMeter := ctx.GasMeter() multipliedGasMeter := types.NewMultipliedGasMeter(sdkGasMeter, VMGasRegister) gasLimit := VMGasRegister.RuntimeGasForContract(ctx) @@ -197,7 +197,7 @@ func (k Keeper) callContract(ctx sdk.Context, vm ibcwasm.WasmEngine, clientID st env := getEnv(ctx, clientID) ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: sudo") - resp, gasUsed, err := vm.Sudo(checksum, env, msg, types.NewStoreAdapter(clientStore), wasmvmAPI, types.NewQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization) + resp, gasUsed, err := k.GetVM().Sudo(checksum, env, msg, types.NewStoreAdapter(clientStore), wasmvmAPI, types.NewQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization) VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) return resp, err } @@ -221,12 +221,12 @@ func (Keeper) instantiateContract(ctx sdk.Context, vm ibcwasm.WasmEngine, client return resp, err } -// ValidatePostExecutionClientState validates that the contract has not many any invalid modifications +// validatePostExecutionClientState validates that the contract has not many any invalid modifications // to the client state during execution. It ensures that // - the client state is still present // - the client state can be unmarshaled successfully. // - the client state is of type *ClientState -func ValidatePostExecutionClientState(clientStore storetypes.KVStore, cdc codec.BinaryCodec) (*types.ClientState, error) { +func validatePostExecutionClientState(clientStore storetypes.KVStore, cdc codec.BinaryCodec) (*types.ClientState, error) { key := host.ClientStateKey() _, ok := clientStore.(types.MigrateClientWrappedStore) if ok { diff --git a/modules/light-clients/08-wasm/keeper/keeper.go b/modules/light-clients/08-wasm/keeper/keeper.go index bebcafb298c..d10285c0190 100644 --- a/modules/light-clients/08-wasm/keeper/keeper.go +++ b/modules/light-clients/08-wasm/keeper/keeper.go @@ -135,7 +135,7 @@ func (k Keeper) MigrateContractCode(ctx sdk.Context, clientID string, newChecksu // persisted to the client store. clientState.Checksum = newChecksum - err = WasmMigrate(ctx, k, k.GetVM(), k.cdc, clientStore, clientState, clientID, migrateMsg) + err = WasmMigrate(ctx, k, clientStore, clientState, clientID, migrateMsg) if err != nil { return err } diff --git a/modules/light-clients/08-wasm/light_client_module.go b/modules/light-clients/08-wasm/light_client_module.go index 1b593f53dd0..fea19ac1a14 100644 --- a/modules/light-clients/08-wasm/light_client_module.go +++ b/modules/light-clients/08-wasm/light_client_module.go @@ -9,7 +9,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" wasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" @@ -33,11 +32,6 @@ func NewLightClientModule(keeper wasmkeeper.Keeper) LightClientModule { } } -// GetVM returns the VM associated with this light client module. -func (l *LightClientModule) GetVM() ibcwasm.WasmEngine { - return l.keeper.GetVM() -} - // RegisterStoreProvider is called by core IBC when a LightClientModule is added to the router. // It allows the LightClientModule to set a ClientStoreProvider which supplies isolated prefix client stores // to IBC light client instances. @@ -82,7 +76,7 @@ func (l LightClientModule) Initialize(ctx sdk.Context, clientID string, clientSt Checksum: clientState.Checksum, } - return wasmkeeper.WasmInstantiate(ctx, l.keeper, l.GetVM(), clientID, l.keeper.Codec(), clientStore, &clientState, payload) + return wasmkeeper.WasmInstantiate(ctx, l.keeper, clientID, clientStore, &clientState, payload) } // VerifyClientMessage obtains the client state associated with the client identifier, it then must verify the ClientMessage. @@ -109,7 +103,7 @@ func (l LightClientModule) VerifyClientMessage(ctx sdk.Context, clientID string, payload := types.QueryMsg{ VerifyClientMessage: &types.VerifyClientMessageMsg{ClientMessage: clientMessage.Data}, } - _, err := wasmkeeper.WasmQuery[types.EmptyResult](ctx, l.keeper, l.GetVM(), clientID, clientStore, clientState, payload) + _, err := wasmkeeper.WasmQuery[types.EmptyResult](ctx, l.keeper, clientID, clientStore, clientState, payload) return err } @@ -135,7 +129,7 @@ func (l LightClientModule) CheckForMisbehaviour(ctx sdk.Context, clientID string CheckForMisbehaviour: &types.CheckForMisbehaviourMsg{ClientMessage: clientMessage.Data}, } - result, err := wasmkeeper.WasmQuery[types.CheckForMisbehaviourResult](ctx, l.keeper, l.GetVM(), clientID, clientStore, clientState, payload) + result, err := wasmkeeper.WasmQuery[types.CheckForMisbehaviourResult](ctx, l.keeper, clientID, clientStore, clientState, payload) if err != nil { return false } @@ -166,7 +160,7 @@ func (l LightClientModule) UpdateStateOnMisbehaviour(ctx sdk.Context, clientID s UpdateStateOnMisbehaviour: &types.UpdateStateOnMisbehaviourMsg{ClientMessage: clientMessage.Data}, } - _, err := wasmkeeper.WasmSudo[types.EmptyResult](ctx, l.keeper, l.GetVM(), clientID, cdc, clientStore, clientState, payload) + _, err := wasmkeeper.WasmSudo[types.EmptyResult](ctx, l.keeper, clientID, clientStore, clientState, payload) if err != nil { panic(err) } @@ -194,7 +188,7 @@ func (l LightClientModule) UpdateState(ctx sdk.Context, clientID string, clientM UpdateState: &types.UpdateStateMsg{ClientMessage: clientMessage.Data}, } - result, err := wasmkeeper.WasmSudo[types.UpdateStateResult](ctx, l.keeper, l.GetVM(), clientID, cdc, clientStore, clientState, payload) + result, err := wasmkeeper.WasmSudo[types.UpdateStateResult](ctx, l.keeper, clientID, clientStore, clientState, payload) if err != nil { panic(err) } @@ -258,7 +252,7 @@ func (l LightClientModule) VerifyMembership( Value: value, }, } - _, err := wasmkeeper.WasmSudo[types.EmptyResult](ctx, l.keeper, l.GetVM(), clientID, cdc, clientStore, clientState, payload) + _, err := wasmkeeper.WasmSudo[types.EmptyResult](ctx, l.keeper, clientID, clientStore, clientState, payload) return err } @@ -311,7 +305,7 @@ func (l LightClientModule) VerifyNonMembership( Path: merklePath, }, } - _, err := wasmkeeper.WasmSudo[types.EmptyResult](ctx, l.keeper, l.GetVM(), clientID, cdc, clientStore, clientState, payload) + _, err := wasmkeeper.WasmSudo[types.EmptyResult](ctx, l.keeper, clientID, clientStore, clientState, payload) return err } @@ -342,7 +336,7 @@ func (l LightClientModule) Status(ctx sdk.Context, clientID string) exported.Sta } payload := types.QueryMsg{Status: &types.StatusMsg{}} - result, err := wasmkeeper.WasmQuery[types.StatusResult](ctx, l.keeper, l.GetVM(), clientID, clientStore, clientState, payload) + result, err := wasmkeeper.WasmQuery[types.StatusResult](ctx, l.keeper, clientID, clientStore, clientState, payload) if err != nil { return exported.Unknown } @@ -390,7 +384,7 @@ func (l LightClientModule) TimestampAtHeight(ctx sdk.Context, clientID string, h }, } - result, err := wasmkeeper.WasmQuery[types.TimestampAtHeightResult](ctx, l.keeper, l.GetVM(), clientID, clientStore, clientState, payload) + result, err := wasmkeeper.WasmQuery[types.TimestampAtHeightResult](ctx, l.keeper, clientID, clientStore, clientState, payload) if err != nil { return 0, errorsmod.Wrapf(err, "height (%s)", height) } @@ -442,7 +436,7 @@ func (l LightClientModule) RecoverClient(ctx sdk.Context, clientID, substituteCl MigrateClientStore: &types.MigrateClientStoreMsg{}, } - _, err = wasmkeeper.WasmSudo[types.EmptyResult](ctx, l.keeper, l.GetVM(), clientID, cdc, store, clientState, payload) + _, err = wasmkeeper.WasmSudo[types.EmptyResult](ctx, l.keeper, clientID, store, clientState, payload) return err } @@ -492,6 +486,6 @@ func (l LightClientModule) VerifyUpgradeAndUpdateState( }, } - _, err := wasmkeeper.WasmSudo[types.EmptyResult](ctx, l.keeper, l.GetVM(), clientID, cdc, clientStore, clientState, payload) + _, err := wasmkeeper.WasmSudo[types.EmptyResult](ctx, l.keeper, clientID, clientStore, clientState, payload) return err } diff --git a/modules/light-clients/08-wasm/types/querier.go b/modules/light-clients/08-wasm/types/querier.go index 46514c4b41a..eca247eb7da 100644 --- a/modules/light-clients/08-wasm/types/querier.go +++ b/modules/light-clients/08-wasm/types/querier.go @@ -30,7 +30,7 @@ This design is based on wasmd's (v0.50.0) querier plugin design. */ var ( - _ wasmvmtypes.Querier = (*queryHandler)(nil) + _ wasmvmtypes.Querier = (*QueryHandler)(nil) _ ibcwasm.QueryPluginsI = (*QueryPlugins)(nil) ) @@ -39,28 +39,28 @@ var defaultAcceptList = []string{ "/ibc.core.client.v1.Query/VerifyMembership", } -// queryHandler is a wrapper around the sdk.Context and the CallerID that calls +// QueryHandler is a wrapper around the sdk.Context and the CallerID that calls // into the query plugins. -type queryHandler struct { +type QueryHandler struct { Ctx sdk.Context CallerID string } // NewQueryHandler returns a default querier that can be used in the contract. -func NewQueryHandler(ctx sdk.Context, callerID string) *queryHandler { - return &queryHandler{ +func NewQueryHandler(ctx sdk.Context, callerID string) *QueryHandler { + return &QueryHandler{ Ctx: ctx, CallerID: callerID, } } // GasConsumed implements the wasmvmtypes.Querier interface. -func (q *queryHandler) GasConsumed() uint64 { +func (q *QueryHandler) GasConsumed() uint64 { return VMGasRegister.ToWasmVMGas(q.Ctx.GasMeter().GasConsumed()) } // Query implements the wasmvmtypes.Querier interface. -func (q *queryHandler) Query(request wasmvmtypes.QueryRequest, gasLimit uint64) ([]byte, error) { +func (q *QueryHandler) Query(request wasmvmtypes.QueryRequest, gasLimit uint64) ([]byte, error) { sdkGas := VMGasRegister.FromWasmVMGas(gasLimit) // discard all changes/events in subCtx by not committing the cached context diff --git a/modules/light-clients/08-wasm/types/store.go b/modules/light-clients/08-wasm/types/store.go index cb0f6424dd0..bb5f5f785e8 100644 --- a/modules/light-clients/08-wasm/types/store.go +++ b/modules/light-clients/08-wasm/types/store.go @@ -19,7 +19,7 @@ import ( ) var ( - _ wasmvmtypes.KVStore = &storeAdapter{} + _ wasmvmtypes.KVStore = &StoreAdapter{} _ storetypes.KVStore = &MigrateClientWrappedStore{} SubjectPrefix = []byte("subject/") @@ -218,40 +218,40 @@ func splitPrefix(key []byte) ([]byte, []byte) { return nil, key } -// storeAdapter bridges the SDK store implementation to wasmvm one. It implements the wasmvmtypes.KVStore interface. -type storeAdapter struct { +// StoreAdapter bridges the SDK store implementation to wasmvm one. It implements the wasmvmtypes.KVStore interface. +type StoreAdapter struct { parent storetypes.KVStore } // NewStoreAdapter constructor -func NewStoreAdapter(s storetypes.KVStore) *storeAdapter { +func NewStoreAdapter(s storetypes.KVStore) *StoreAdapter { if s == nil { panic(errors.New("store must not be nil")) } - return &storeAdapter{parent: s} + return &StoreAdapter{parent: s} } // Get implements the wasmvmtypes.KVStore interface. -func (s storeAdapter) Get(key []byte) []byte { +func (s StoreAdapter) Get(key []byte) []byte { return s.parent.Get(key) } // Set implements the wasmvmtypes.KVStore interface. -func (s storeAdapter) Set(key, value []byte) { +func (s StoreAdapter) Set(key, value []byte) { s.parent.Set(key, value) } // Delete implements the wasmvmtypes.KVStore interface. -func (s storeAdapter) Delete(key []byte) { +func (s StoreAdapter) Delete(key []byte) { s.parent.Delete(key) } // Iterator implements the wasmvmtypes.KVStore interface. -func (s storeAdapter) Iterator(start, end []byte) wasmvmtypes.Iterator { +func (s StoreAdapter) Iterator(start, end []byte) wasmvmtypes.Iterator { return s.parent.Iterator(start, end) } // ReverseIterator implements the wasmvmtypes.KVStore interface. -func (s storeAdapter) ReverseIterator(start, end []byte) wasmvmtypes.Iterator { +func (s StoreAdapter) ReverseIterator(start, end []byte) wasmvmtypes.Iterator { return s.parent.ReverseIterator(start, end) } From 1692f8bebfc6a5329883a77981fa694234f51017 Mon Sep 17 00:00:00 2001 From: DimitrisJim Date: Sun, 7 Apr 2024 21:01:52 +0300 Subject: [PATCH 10/24] test: add amended tests for contact keeper. --- .../08-wasm/keeper/contract_keeper_test.go | 599 ++++++++++++++++++ 1 file changed, 599 insertions(+) create mode 100644 modules/light-clients/08-wasm/keeper/contract_keeper_test.go diff --git a/modules/light-clients/08-wasm/keeper/contract_keeper_test.go b/modules/light-clients/08-wasm/keeper/contract_keeper_test.go new file mode 100644 index 00000000000..754a035c092 --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/contract_keeper_test.go @@ -0,0 +1,599 @@ +package keeper_test + +import ( + "encoding/json" + + wasmvm "github.com/CosmWasm/wasmvm/v2" + wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" + + wasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper" + wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v8/modules/core/24-host" + "github.com/cosmos/ibc-go/v8/modules/core/exported" + ibctm "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" + localhost "github.com/cosmos/ibc-go/v8/modules/light-clients/09-localhost" +) + +func (suite *KeeperTestSuite) TestWasmInstantiate() { + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() { + suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, initMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + // Ensure GoAPI is set + suite.Require().NotNil(goapi.CanonicalizeAddress) + suite.Require().NotNil(goapi.HumanizeAddress) + suite.Require().NotNil(goapi.ValidateAddress) + + var payload types.InstantiateMessage + err := json.Unmarshal(initMsg, &payload) + suite.Require().NoError(err) + + wrappedClientState := clienttypes.MustUnmarshalClientState(suite.chainA.App.AppCodec(), payload.ClientState).(*ibctm.ClientState) + + clientState := types.NewClientState(payload.ClientState, payload.Checksum, wrappedClientState.LatestHeight) + clientStateBz := clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), clientState) + store.Set(host.ClientStateKey(), clientStateBz) + + consensusState := types.NewConsensusState(payload.ConsensusState) + consensusStateBz := clienttypes.MustMarshalConsensusState(suite.chainA.App.AppCodec(), consensusState) + store.Set(host.ConsensusStateKey(clientState.LatestHeight), consensusStateBz) + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{}}, 0, nil + } + }, + nil, + }, + { + "failure: vm returns error", + func() { + suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + return nil, 0, wasmtesting.ErrMockVM + } + }, + types.ErrVMError, + }, + { + "failure: contract returns error", + func() { + suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + return &wasmvmtypes.ContractResult{Err: wasmtesting.ErrMockContract.Error()}, 0, nil + } + }, + types.ErrWasmContractCallFailed, + }, + { + "failure: contract returns non-empty messages", + func() { + suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + resp := wasmvmtypes.Response{Messages: []wasmvmtypes.SubMsg{{}}} + + return &wasmvmtypes.ContractResult{Ok: &resp}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmSubMessagesNotAllowed, + }, + { + "failure: contract returns non-empty events", + func() { + suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + resp := wasmvmtypes.Response{Events: []wasmvmtypes.Event{{}}} + + return &wasmvmtypes.ContractResult{Ok: &resp}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmEventsNotAllowed, + }, + { + "failure: contract returns non-empty attributes", + func() { + suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + resp := wasmvmtypes.Response{Attributes: []wasmvmtypes.EventAttribute{{}}} + + return &wasmvmtypes.ContractResult{Ok: &resp}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmAttributesNotAllowed, + }, + { + "failure: change clientstate type", + func() { + suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + newClientState := localhost.NewClientState(clienttypes.NewHeight(1, 1)) + store.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), newClientState)) + + data, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: data}}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmInvalidContractModification, + }, + { + "failure: delete clientstate", + func() { + suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + store.Delete(host.ClientStateKey()) + data, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: data}}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmInvalidContractModification, + }, + { + "failure: unmarshallable clientstate", + func() { + suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + store.Set(host.ClientStateKey(), []byte("invalid json")) + data, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: data}}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmInvalidContractModification, + }, + { + "failure: change checksum", + func() { + suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, initMsg []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + var payload types.InstantiateMessage + err := json.Unmarshal(initMsg, &payload) + suite.Require().NoError(err) + + // Change the checksum to something else. + wrappedClientState := clienttypes.MustUnmarshalClientState(suite.chainA.App.AppCodec(), payload.ClientState).(*ibctm.ClientState) + clientState := types.NewClientState(payload.ClientState, []byte("new checksum"), wrappedClientState.LatestHeight) + store.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), clientState)) + + resp, err := json.Marshal(types.UpdateStateResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: resp}}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmInvalidContractModification, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + checksum := storeWasmCode(suite, wasmtesting.Code) + + tc.malleate() + + initMsg := types.InstantiateMessage{ + ClientState: clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), wasmtesting.MockTendermitClientState), + ConsensusState: clienttypes.MustMarshalConsensusState(suite.chainA.App.AppCodec(), wasmtesting.MockTendermintClientConsensusState), + Checksum: checksum, + } + + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), defaultWasmClientID) + wasmClientKeeper := GetSimApp(suite.chainA).WasmClientKeeper + err := wasmkeeper.WasmInstantiate(suite.chainA.GetContext(), wasmClientKeeper, defaultWasmClientID, clientStore, &types.ClientState{Checksum: checksum}, initMsg) + + expPass := tc.expError == nil + if expPass { + suite.Require().NoError(err) + } else { + suite.Require().ErrorIs(err, tc.expError) + } + }) + } +} + +func (suite *KeeperTestSuite) TestWasmMigrate() { + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() { + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, goapi wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + // Ensure GoAPI is set + suite.Require().NotNil(goapi.CanonicalizeAddress) + suite.Require().NotNil(goapi.HumanizeAddress) + suite.Require().NotNil(goapi.ValidateAddress) + + resp, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: resp}}, 0, nil + } + }, + nil, + }, + { + "failure: vm returns error", + func() { + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + return nil, 0, wasmtesting.ErrMockVM + } + }, + types.ErrVMError, + }, + { + "failure: contract returns error", + func() { + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + return &wasmvmtypes.ContractResult{Err: wasmtesting.ErrMockContract.Error()}, 0, nil + } + }, + types.ErrWasmContractCallFailed, + }, + { + "failure: contract returns non-empty messages", + func() { + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + resp := wasmvmtypes.Response{Messages: []wasmvmtypes.SubMsg{{}}} + + return &wasmvmtypes.ContractResult{Ok: &resp}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmSubMessagesNotAllowed, + }, + { + "failure: contract returns non-empty events", + func() { + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + resp := wasmvmtypes.Response{Events: []wasmvmtypes.Event{{}}} + + return &wasmvmtypes.ContractResult{Ok: &resp}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmEventsNotAllowed, + }, + { + "failure: contract returns non-empty attributes", + func() { + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + resp := wasmvmtypes.Response{Attributes: []wasmvmtypes.EventAttribute{{}}} + + return &wasmvmtypes.ContractResult{Ok: &resp}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmAttributesNotAllowed, + }, + { + "failure: change clientstate type", + func() { + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + newClientState := localhost.NewClientState(clienttypes.NewHeight(1, 1)) + store.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), newClientState)) + + data, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: data}}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmInvalidContractModification, + }, + { + "failure: delete clientstate", + func() { + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + store.Delete(host.ClientStateKey()) + data, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: data}}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmInvalidContractModification, + }, + { + "failure: unmarshallable clientstate", + func() { + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + store.Set(host.ClientStateKey(), []byte("invalid json")) + data, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: data}}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmInvalidContractModification, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + _ = storeWasmCode(suite, wasmtesting.Code) + + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + + tc.malleate() + + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), defaultWasmClientID) + wasmClientKeeper := GetSimApp(suite.chainA).WasmClientKeeper + err = wasmkeeper.WasmMigrate(suite.chainA.GetContext(), wasmClientKeeper, clientStore, &types.ClientState{}, defaultWasmClientID, []byte("{}")) + + expPass := tc.expError == nil + if expPass { + suite.Require().NoError(err) + } else { + suite.Require().ErrorIs(err, tc.expError) + } + }) + } +} + +func (suite *KeeperTestSuite) TestWasmQuery() { + var payload types.QueryMsg + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() { + suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, goapi wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + // Ensure GoAPI is set + suite.Require().NotNil(goapi.CanonicalizeAddress) + suite.Require().NotNil(goapi.HumanizeAddress) + suite.Require().NotNil(goapi.ValidateAddress) + + resp, err := json.Marshal(types.StatusResult{Status: exported.Frozen.String()}) + suite.Require().NoError(err) + + return &wasmvmtypes.QueryResult{Ok: resp}, wasmtesting.DefaultGasUsed, nil + }) + }, + nil, + }, + { + "failure: vm returns error", + func() { + suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + return nil, wasmtesting.DefaultGasUsed, wasmtesting.ErrMockVM + }) + }, + types.ErrVMError, + }, + { + "failure: contract returns error", + func() { + suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + return &wasmvmtypes.QueryResult{Err: wasmtesting.ErrMockContract.Error()}, wasmtesting.DefaultGasUsed, nil + }) + }, + types.ErrWasmContractCallFailed, + }, + { + "failure: response fails to unmarshal", + func() { + suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + return &wasmvmtypes.QueryResult{Ok: []byte("invalid json")}, wasmtesting.DefaultGasUsed, nil + }) + }, + types.ErrWasmInvalidResponseData, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + _ = storeWasmCode(suite, wasmtesting.Code) + + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + + clientState := endpoint.GetClientState() + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpoint.ClientID) + + wasmClientState, ok := clientState.(*types.ClientState) + suite.Require().True(ok) + + payload = types.QueryMsg{Status: &types.StatusMsg{}} + + tc.malleate() + + wasmClientKeeper := GetSimApp(suite.chainA).WasmClientKeeper + res, err := wasmkeeper.WasmQuery[types.StatusResult](suite.chainA.GetContext(), wasmClientKeeper, endpoint.ClientID, clientStore, wasmClientState, payload) + + expPass := tc.expError == nil + if expPass { + suite.Require().NoError(err) + suite.Require().NotNil(res) + } else { + suite.Require().ErrorIs(err, tc.expError) + } + }) + } +} + +func (suite *KeeperTestSuite) TestWasmSudo() { + var payload types.SudoMsg + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, goapi wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + // Ensure GoAPI is set + suite.Require().NotNil(goapi.CanonicalizeAddress) + suite.Require().NotNil(goapi.HumanizeAddress) + suite.Require().NotNil(goapi.ValidateAddress) + + resp, err := json.Marshal(types.UpdateStateResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: resp}}, wasmtesting.DefaultGasUsed, nil + }) + }, + nil, + }, + { + "failure: vm returns error", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + return nil, wasmtesting.DefaultGasUsed, wasmtesting.ErrMockVM + }) + }, + types.ErrVMError, + }, + { + "failure: contract returns error", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + return &wasmvmtypes.ContractResult{Err: wasmtesting.ErrMockContract.Error()}, wasmtesting.DefaultGasUsed, nil + }) + }, + types.ErrWasmContractCallFailed, + }, + { + "failure: contract returns non-empty messages", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + resp := wasmvmtypes.Response{Messages: []wasmvmtypes.SubMsg{{}}} + + return &wasmvmtypes.ContractResult{Ok: &resp}, wasmtesting.DefaultGasUsed, nil + }) + }, + types.ErrWasmSubMessagesNotAllowed, + }, + { + "failure: contract returns non-empty events", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + resp := wasmvmtypes.Response{Events: []wasmvmtypes.Event{{}}} + + return &wasmvmtypes.ContractResult{Ok: &resp}, wasmtesting.DefaultGasUsed, nil + }) + }, + types.ErrWasmEventsNotAllowed, + }, + { + "failure: contract returns non-empty attributes", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + resp := wasmvmtypes.Response{Attributes: []wasmvmtypes.EventAttribute{{}}} + + return &wasmvmtypes.ContractResult{Ok: &resp}, wasmtesting.DefaultGasUsed, nil + }) + }, + types.ErrWasmAttributesNotAllowed, + }, + { + "failure: response fails to unmarshal", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: []byte("invalid json")}}, wasmtesting.DefaultGasUsed, nil + }) + }, + types.ErrWasmInvalidResponseData, + }, + { + "failure: invalid clientstate type", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + newClientState := localhost.NewClientState(clienttypes.NewHeight(1, 1)) + store.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), newClientState)) + + resp, err := json.Marshal(types.UpdateStateResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: resp}}, wasmtesting.DefaultGasUsed, nil + }) + }, + types.ErrWasmInvalidContractModification, + }, + { + "failure: unmarshallable clientstate bytes", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + store.Set(host.ClientStateKey(), []byte("invalid json")) + + resp, err := json.Marshal(types.UpdateStateResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: resp}}, wasmtesting.DefaultGasUsed, nil + }) + }, + types.ErrWasmInvalidContractModification, + }, + { + "failure: delete clientstate", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + store.Delete(host.ClientStateKey()) + + resp, err := json.Marshal(types.UpdateStateResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: resp}}, wasmtesting.DefaultGasUsed, nil + }) + }, + types.ErrWasmInvalidContractModification, + }, + { + "failure: change checksum", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + clientState := suite.chainA.GetClientState(defaultWasmClientID) + clientState.(*types.ClientState).Checksum = []byte("new checksum") + store.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), clientState)) + + resp, err := json.Marshal(types.UpdateStateResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: resp}}, wasmtesting.DefaultGasUsed, nil + }) + }, + types.ErrWasmInvalidContractModification, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + _ = storeWasmCode(suite, wasmtesting.Code) + + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + + clientState := endpoint.GetClientState() + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpoint.ClientID) + + wasmClientState, ok := clientState.(*types.ClientState) + suite.Require().True(ok) + + payload = types.SudoMsg{UpdateState: &types.UpdateStateMsg{}} + + tc.malleate() + + wasmClientKeeper := GetSimApp(suite.chainA).WasmClientKeeper + res, err := wasmkeeper.WasmSudo[types.UpdateStateResult](suite.chainA.GetContext(), wasmClientKeeper, endpoint.ClientID, clientStore, wasmClientState, payload) + + expPass := tc.expError == nil + if expPass { + suite.Require().NoError(err) + suite.Require().NotNil(res) + } else { + suite.Require().ErrorIs(err, tc.expError) + } + }) + } +} From 51271262223afbc50e0ba4942457c9a93febe053 Mon Sep 17 00:00:00 2001 From: DimitrisJim Date: Mon, 8 Apr 2024 04:10:01 +0300 Subject: [PATCH 11/24] refactor: move querier to keeper package. --- .../08-wasm/keeper/contract_keeper.go | 10 +++--- .../light-clients/08-wasm/keeper/keeper.go | 16 ++++++++++ .../light-clients/08-wasm/keeper/keeper_vm.go | 2 +- .../08-wasm/keeper/migrations.go | 2 +- .../light-clients/08-wasm/keeper/options.go | 5 ++- .../08-wasm/keeper/options_test.go | 31 ++++++++++--------- .../08-wasm/{types => keeper}/querier.go | 5 +-- .../08-wasm/{types => keeper}/querier_test.go | 26 +++++++++------- modules/light-clients/08-wasm/types/events.go | 13 -------- 9 files changed, 59 insertions(+), 51 deletions(-) rename modules/light-clients/08-wasm/{types => keeper}/querier.go (97%) rename modules/light-clients/08-wasm/{types => keeper}/querier_test.go (93%) diff --git a/modules/light-clients/08-wasm/keeper/contract_keeper.go b/modules/light-clients/08-wasm/keeper/contract_keeper.go index 041122cee06..32fe7a3bb2a 100644 --- a/modules/light-clients/08-wasm/keeper/contract_keeper.go +++ b/modules/light-clients/08-wasm/keeper/contract_keeper.go @@ -169,7 +169,7 @@ func (k Keeper) migrateContract(ctx sdk.Context, clientID string, clientStore st env := getEnv(ctx, clientID) ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: migrate") - resp, gasUsed, err := k.GetVM().Migrate(checksum, env, msg, types.NewStoreAdapter(clientStore), wasmvmAPI, types.NewQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization) + resp, gasUsed, err := k.GetVM().Migrate(checksum, env, msg, types.NewStoreAdapter(clientStore), wasmvmAPI, k.newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization) VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) return resp, err } @@ -183,7 +183,7 @@ func (k Keeper) queryContract(ctx sdk.Context, clientID string, clientStore stor env := getEnv(ctx, clientID) ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: query") - resp, gasUsed, err := k.GetVM().Query(checksum, env, msg, types.NewStoreAdapter(clientStore), wasmvmAPI, types.NewQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization) + resp, gasUsed, err := k.GetVM().Query(checksum, env, msg, types.NewStoreAdapter(clientStore), wasmvmAPI, k.newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization) VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) return resp, err } @@ -197,13 +197,13 @@ func (k Keeper) callContract(ctx sdk.Context, clientID string, clientStore store env := getEnv(ctx, clientID) ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: sudo") - resp, gasUsed, err := k.GetVM().Sudo(checksum, env, msg, types.NewStoreAdapter(clientStore), wasmvmAPI, types.NewQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization) + resp, gasUsed, err := k.GetVM().Sudo(checksum, env, msg, types.NewStoreAdapter(clientStore), wasmvmAPI, k.newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization) VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) return resp, err } // instantiateContract calls vm.Instantiate with appropriate arguments. -func (Keeper) instantiateContract(ctx sdk.Context, vm ibcwasm.WasmEngine, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) { +func (k Keeper) instantiateContract(ctx sdk.Context, vm ibcwasm.WasmEngine, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) { sdkGasMeter := ctx.GasMeter() multipliedGasMeter := types.NewMultipliedGasMeter(sdkGasMeter, types.VMGasRegister) gasLimit := VMGasRegister.RuntimeGasForContract(ctx) @@ -216,7 +216,7 @@ func (Keeper) instantiateContract(ctx sdk.Context, vm ibcwasm.WasmEngine, client } ctx.GasMeter().ConsumeGas(types.VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: instantiate") - resp, gasUsed, err := vm.Instantiate(checksum, env, msgInfo, msg, types.NewStoreAdapter(clientStore), wasmvmAPI, types.NewQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization) + resp, gasUsed, err := vm.Instantiate(checksum, env, msgInfo, msg, types.NewStoreAdapter(clientStore), wasmvmAPI, k.newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization) types.VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) return resp, err } diff --git a/modules/light-clients/08-wasm/keeper/keeper.go b/modules/light-clients/08-wasm/keeper/keeper.go index d10285c0190..aef3ec3f733 100644 --- a/modules/light-clients/08-wasm/keeper/keeper.go +++ b/modules/light-clients/08-wasm/keeper/keeper.go @@ -5,6 +5,8 @@ import ( "context" "encoding/hex" + "cosmossdk.io/log" + wasmvm "github.com/CosmWasm/wasmvm/v2" "cosmossdk.io/collections" @@ -17,6 +19,7 @@ import ( "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v8/modules/core/exported" ) // Keeper defines the 08-wasm keeper @@ -47,6 +50,15 @@ func (k Keeper) GetAuthority() string { return k.authority } +// Logger returns a module-specific logger. +func (k Keeper) Logger(ctx sdk.Context) log.Logger { + return moduleLogger(ctx) +} + +func moduleLogger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", "x/"+exported.ModuleName+"-"+types.ModuleName) +} + // GetVM returns the keeper's vm engine. func (k Keeper) GetVM() ibcwasm.WasmEngine { return k.vm @@ -56,6 +68,10 @@ func (k Keeper) GetChecksums() collections.KeySet[[]byte] { return k.checksums } +func (k Keeper) newQueryHandler(ctx sdk.Context, callerID string) *QueryHandler { + return NewQueryHandler(ctx, callerID) +} + func (k Keeper) storeWasmCode(ctx sdk.Context, code []byte, storeFn func(code wasmvm.WasmCode, gasLimit uint64) (wasmvm.Checksum, uint64, error)) ([]byte, error) { var err error if types.IsGzip(code) { diff --git a/modules/light-clients/08-wasm/keeper/keeper_vm.go b/modules/light-clients/08-wasm/keeper/keeper_vm.go index 80d9b076a69..4ed63c920a3 100644 --- a/modules/light-clients/08-wasm/keeper/keeper_vm.go +++ b/modules/light-clients/08-wasm/keeper/keeper_vm.go @@ -66,7 +66,7 @@ func NewKeeperWithVM( // set query plugins to ensure there is a non-nil query plugin // regardless of what options the user provides - ibcwasm.SetQueryPlugins(types.NewDefaultQueryPlugins()) + ibcwasm.SetQueryPlugins(NewDefaultQueryPlugins()) for _, opt := range opts { opt.apply(keeper) } diff --git a/modules/light-clients/08-wasm/keeper/migrations.go b/modules/light-clients/08-wasm/keeper/migrations.go index c37852e2941..bf0a1488494 100644 --- a/modules/light-clients/08-wasm/keeper/migrations.go +++ b/modules/light-clients/08-wasm/keeper/migrations.go @@ -41,7 +41,7 @@ func (m Migrator) MigrateChecksums(ctx sdk.Context) error { return err } - types.Logger(ctx).Info("successfully migrated Checksums to collections") + m.keeper.Logger(ctx).Info("successfully migrated Checksums to collections") return nil } diff --git a/modules/light-clients/08-wasm/keeper/options.go b/modules/light-clients/08-wasm/keeper/options.go index 8977a6df371..7b9f0c8a97a 100644 --- a/modules/light-clients/08-wasm/keeper/options.go +++ b/modules/light-clients/08-wasm/keeper/options.go @@ -4,7 +4,6 @@ import ( "errors" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" - "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" ) // Option is an extension point to instantiate keeper with non default values @@ -20,9 +19,9 @@ func (f optsFn) apply(keeper *Keeper) { // WithQueryPlugins is an optional constructor parameter to pass custom query plugins for wasmVM requests. // Missing fields will be filled with default queriers. -func WithQueryPlugins(plugins *types.QueryPlugins) Option { +func WithQueryPlugins(plugins *QueryPlugins) Option { return optsFn(func(_ *Keeper) { - currentPlugins, ok := ibcwasm.GetQueryPlugins().(*types.QueryPlugins) + currentPlugins, ok := ibcwasm.GetQueryPlugins().(*QueryPlugins) if !ok { panic(errors.New("invalid query plugins type")) } diff --git a/modules/light-clients/08-wasm/keeper/options_test.go b/modules/light-clients/08-wasm/keeper/options_test.go index ff54dd020f5..6b784ba9d26 100644 --- a/modules/light-clients/08-wasm/keeper/options_test.go +++ b/modules/light-clients/08-wasm/keeper/options_test.go @@ -11,16 +11,17 @@ import ( "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper" + wasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" ) -func MockCustomQuerier() func(sdk.Context, json.RawMessage) ([]byte, error) { +func MockErrorCustomQuerier() func(sdk.Context, json.RawMessage) ([]byte, error) { return func(_ sdk.Context, _ json.RawMessage) ([]byte, error) { return nil, errors.New("custom querier error for TestNewKeeperWithOptions") } } -func MockStargateQuerier() func(sdk.Context, *wasmvmtypes.StargateQuery) ([]byte, error) { +func MockErrorStargateQuerier() func(sdk.Context, *wasmvmtypes.StargateQuery) ([]byte, error) { return func(_ sdk.Context, _ *wasmvmtypes.StargateQuery) ([]byte, error) { return nil, errors.New("stargate querier error for TestNewKeeperWithOptions") } @@ -46,7 +47,7 @@ func (suite *KeeperTestSuite) TestNewKeeperWithOptions() { ) }, func(k keeper.Keeper) { - plugins := ibcwasm.GetQueryPlugins().(*types.QueryPlugins) + plugins := ibcwasm.GetQueryPlugins().(*wasmkeeper.QueryPlugins) _, err := plugins.Custom(sdk.Context{}, nil) suite.Require().ErrorIs(err, wasmvmtypes.UnsupportedRequest{Kind: "Custom queries are not allowed"}) @@ -58,8 +59,8 @@ func (suite *KeeperTestSuite) TestNewKeeperWithOptions() { { "success: custom querier", func() { - querierOption := keeper.WithQueryPlugins(&types.QueryPlugins{ - Custom: MockCustomQuerier(), + querierOption := keeper.WithQueryPlugins(&wasmkeeper.QueryPlugins{ + Custom: MockErrorCustomQuerier(), }) k = keeper.NewKeeperWithVM( GetSimApp(suite.chainA).AppCodec(), @@ -72,7 +73,7 @@ func (suite *KeeperTestSuite) TestNewKeeperWithOptions() { ) }, func(k keeper.Keeper) { - plugins := ibcwasm.GetQueryPlugins().(*types.QueryPlugins) + plugins := ibcwasm.GetQueryPlugins().(*wasmkeeper.QueryPlugins) _, err := plugins.Custom(sdk.Context{}, nil) suite.Require().ErrorContains(err, "custom querier error for TestNewKeeperWithOptions") @@ -84,8 +85,8 @@ func (suite *KeeperTestSuite) TestNewKeeperWithOptions() { { "success: stargate querier", func() { - querierOption := keeper.WithQueryPlugins(&types.QueryPlugins{ - Stargate: MockStargateQuerier(), + querierOption := keeper.WithQueryPlugins(&wasmkeeper.QueryPlugins{ + Stargate: MockErrorStargateQuerier(), }) k = keeper.NewKeeperWithVM( GetSimApp(suite.chainA).AppCodec(), @@ -98,7 +99,7 @@ func (suite *KeeperTestSuite) TestNewKeeperWithOptions() { ) }, func(k keeper.Keeper) { - plugins := ibcwasm.GetQueryPlugins().(*types.QueryPlugins) + plugins := ibcwasm.GetQueryPlugins().(*wasmkeeper.QueryPlugins) _, err := plugins.Custom(sdk.Context{}, nil) suite.Require().ErrorIs(err, wasmvmtypes.UnsupportedRequest{Kind: "Custom queries are not allowed"}) @@ -110,9 +111,9 @@ func (suite *KeeperTestSuite) TestNewKeeperWithOptions() { { "success: both queriers", func() { - querierOption := keeper.WithQueryPlugins(&types.QueryPlugins{ - Custom: MockCustomQuerier(), - Stargate: MockStargateQuerier(), + querierOption := keeper.WithQueryPlugins(&wasmkeeper.QueryPlugins{ + Custom: MockErrorCustomQuerier(), + Stargate: MockErrorStargateQuerier(), }) k = keeper.NewKeeperWithVM( GetSimApp(suite.chainA).AppCodec(), @@ -125,7 +126,7 @@ func (suite *KeeperTestSuite) TestNewKeeperWithOptions() { ) }, func(k keeper.Keeper) { - plugins := ibcwasm.GetQueryPlugins().(*types.QueryPlugins) + plugins := ibcwasm.GetQueryPlugins().(*wasmkeeper.QueryPlugins) _, err := plugins.Custom(sdk.Context{}, nil) suite.Require().ErrorContains(err, "custom querier error for TestNewKeeperWithOptions") @@ -142,13 +143,13 @@ func (suite *KeeperTestSuite) TestNewKeeperWithOptions() { suite.Run(tc.name, func() { // make sure the default query plugins are set - ibcwasm.SetQueryPlugins(types.NewDefaultQueryPlugins()) + ibcwasm.SetQueryPlugins(wasmkeeper.NewDefaultQueryPlugins()) tc.malleate() tc.verifyFn(k) // reset query plugins after each test - ibcwasm.SetQueryPlugins(types.NewDefaultQueryPlugins()) + ibcwasm.SetQueryPlugins(wasmkeeper.NewDefaultQueryPlugins()) }) } } diff --git a/modules/light-clients/08-wasm/types/querier.go b/modules/light-clients/08-wasm/keeper/querier.go similarity index 97% rename from modules/light-clients/08-wasm/types/querier.go rename to modules/light-clients/08-wasm/keeper/querier.go index eca247eb7da..4faff662b43 100644 --- a/modules/light-clients/08-wasm/types/querier.go +++ b/modules/light-clients/08-wasm/keeper/querier.go @@ -1,4 +1,4 @@ -package types +package keeper import ( "encoding/json" @@ -47,6 +47,7 @@ type QueryHandler struct { } // NewQueryHandler returns a default querier that can be used in the contract. +// TODO(jim): Make private and use export_test? func NewQueryHandler(ctx sdk.Context, callerID string) *QueryHandler { return &QueryHandler{ Ctx: ctx, @@ -76,7 +77,7 @@ func (q *QueryHandler) Query(request wasmvmtypes.QueryRequest, gasLimit uint64) return res, nil } - Logger(q.Ctx).Debug("Redacting query error", "cause", err) + moduleLogger(q.Ctx).Debug("Redacting query error", "cause", err) return nil, redactError(err) } diff --git a/modules/light-clients/08-wasm/types/querier_test.go b/modules/light-clients/08-wasm/keeper/querier_test.go similarity index 93% rename from modules/light-clients/08-wasm/types/querier_test.go rename to modules/light-clients/08-wasm/keeper/querier_test.go index 6912efc904d..49b032ba4dd 100644 --- a/modules/light-clients/08-wasm/types/querier_test.go +++ b/modules/light-clients/08-wasm/keeper/querier_test.go @@ -1,4 +1,4 @@ -package types_test +package keeper_test import ( "encoding/hex" @@ -12,6 +12,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" + wasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper" wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" @@ -44,7 +45,7 @@ func MockCustomQuerier() func(sdk.Context, json.RawMessage) ([]byte, error) { } } -func (suite *TypesTestSuite) TestCustomQuery() { +func (suite *KeeperTestSuite) TestCustomQuery() { testCases := []struct { name string malleate func() @@ -52,7 +53,7 @@ func (suite *TypesTestSuite) TestCustomQuery() { { "success: custom query", func() { - querierPlugin := types.QueryPlugins{ + querierPlugin := wasmkeeper.QueryPlugins{ Custom: MockCustomQuerier(), } ibcwasm.SetQueryPlugins(&querierPlugin) @@ -100,6 +101,7 @@ func (suite *TypesTestSuite) TestCustomQuery() { for _, tc := range testCases { suite.Run(tc.name, func() { suite.SetupWasmWithMockVM() + _ = storeWasmCode(suite, wasmtesting.Code) endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) err := endpoint.CreateClient() @@ -119,16 +121,17 @@ func (suite *TypesTestSuite) TestCustomQuery() { // clientState.Status(suite.chainA.GetContext(), clientStore, suite.chainA.App.AppCodec()) // reset query plugins after each test - ibcwasm.SetQueryPlugins(types.NewDefaultQueryPlugins()) + ibcwasm.SetQueryPlugins(wasmkeeper.NewDefaultQueryPlugins()) }) } } -func (suite *TypesTestSuite) TestStargateQuery() { +func (suite *KeeperTestSuite) TestStargateQuery() { typeURL := "/ibc.lightclients.wasm.v1.Query/Checksums" var ( endpoint *wasmtesting.WasmEndpoint + checksum []byte expDiscardedState = false proofKey = []byte("mock-key") testKey = []byte("test-key") @@ -143,8 +146,8 @@ func (suite *TypesTestSuite) TestStargateQuery() { { "success: custom query", func() { - querierPlugin := types.QueryPlugins{ - Stargate: types.AcceptListStargateQuerier([]string{typeURL}), + querierPlugin := wasmkeeper.QueryPlugins{ + Stargate: wasmkeeper.AcceptListStargateQuerier([]string{typeURL}), } ibcwasm.SetQueryPlugins(&querierPlugin) @@ -166,7 +169,7 @@ func (suite *TypesTestSuite) TestStargateQuery() { err = respData.Unmarshal(resp) suite.Require().NoError(err) - expChecksum := hex.EncodeToString(suite.checksum) + expChecksum := hex.EncodeToString(checksum) suite.Require().Len(respData.Checksums, 1) suite.Require().Equal(expChecksum, respData.Checksums[0]) @@ -190,8 +193,8 @@ func (suite *TypesTestSuite) TestStargateQuery() { // Furthermore we write a test key and assert that the state changes made by this handler were discarded by the cachedCtx at the grpc handler. "success: verify membership query", func() { - querierPlugin := types.QueryPlugins{ - Stargate: types.AcceptListStargateQuerier([]string{""}), + querierPlugin := wasmkeeper.QueryPlugins{ + Stargate: wasmkeeper.AcceptListStargateQuerier([]string{""}), } ibcwasm.SetQueryPlugins(&querierPlugin) @@ -294,6 +297,7 @@ func (suite *TypesTestSuite) TestStargateQuery() { suite.Run(tc.name, func() { expDiscardedState = false suite.SetupWasmWithMockVM() + checksum = storeWasmCode(suite, wasmtesting.Code) endpoint = wasmtesting.NewWasmEndpoint(suite.chainA) err := endpoint.CreateClient() @@ -325,7 +329,7 @@ func (suite *TypesTestSuite) TestStargateQuery() { } // reset query plugins after each test - ibcwasm.SetQueryPlugins(types.NewDefaultQueryPlugins()) + ibcwasm.SetQueryPlugins(wasmkeeper.NewDefaultQueryPlugins()) }) } } diff --git a/modules/light-clients/08-wasm/types/events.go b/modules/light-clients/08-wasm/types/events.go index d2d76f706a1..c8290fe8579 100644 --- a/modules/light-clients/08-wasm/types/events.go +++ b/modules/light-clients/08-wasm/types/events.go @@ -1,13 +1,5 @@ package types -import ( - "cosmossdk.io/log" - - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/cosmos/ibc-go/v8/modules/core/exported" -) - // IBC 08-wasm events const ( // EventTypeStoreWasmCode defines the event type for bytecode storage @@ -24,8 +16,3 @@ const ( AttributeValueCategory = ModuleName ) - -// Logger returns a module-specific logger. -func Logger(ctx sdk.Context) log.Logger { - return ctx.Logger().With("module", "x/"+exported.ModuleName+"-"+ModuleName) -} From 57c223538cb57a2c128a26387fc34629691767e2 Mon Sep 17 00:00:00 2001 From: DimitrisJim Date: Mon, 8 Apr 2024 04:25:07 +0300 Subject: [PATCH 12/24] lint: you'll never fix me. --- .../light-clients/08-wasm/keeper/keeper.go | 7 +++---- .../08-wasm/keeper/options_test.go | 19 +++++++++---------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/modules/light-clients/08-wasm/keeper/keeper.go b/modules/light-clients/08-wasm/keeper/keeper.go index aef3ec3f733..bd3a66b631d 100644 --- a/modules/light-clients/08-wasm/keeper/keeper.go +++ b/modules/light-clients/08-wasm/keeper/keeper.go @@ -5,13 +5,12 @@ import ( "context" "encoding/hex" - "cosmossdk.io/log" - wasmvm "github.com/CosmWasm/wasmvm/v2" "cosmossdk.io/collections" "cosmossdk.io/core/store" errorsmod "cosmossdk.io/errors" + "cosmossdk.io/log" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" @@ -51,7 +50,7 @@ func (k Keeper) GetAuthority() string { } // Logger returns a module-specific logger. -func (k Keeper) Logger(ctx sdk.Context) log.Logger { +func (Keeper) Logger(ctx sdk.Context) log.Logger { return moduleLogger(ctx) } @@ -68,7 +67,7 @@ func (k Keeper) GetChecksums() collections.KeySet[[]byte] { return k.checksums } -func (k Keeper) newQueryHandler(ctx sdk.Context, callerID string) *QueryHandler { +func (Keeper) newQueryHandler(ctx sdk.Context, callerID string) *QueryHandler { return NewQueryHandler(ctx, callerID) } diff --git a/modules/light-clients/08-wasm/keeper/options_test.go b/modules/light-clients/08-wasm/keeper/options_test.go index 6b784ba9d26..efae5f1a498 100644 --- a/modules/light-clients/08-wasm/keeper/options_test.go +++ b/modules/light-clients/08-wasm/keeper/options_test.go @@ -11,7 +11,6 @@ import ( "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper" - wasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" ) @@ -47,7 +46,7 @@ func (suite *KeeperTestSuite) TestNewKeeperWithOptions() { ) }, func(k keeper.Keeper) { - plugins := ibcwasm.GetQueryPlugins().(*wasmkeeper.QueryPlugins) + plugins := ibcwasm.GetQueryPlugins().(*keeper.QueryPlugins) _, err := plugins.Custom(sdk.Context{}, nil) suite.Require().ErrorIs(err, wasmvmtypes.UnsupportedRequest{Kind: "Custom queries are not allowed"}) @@ -59,7 +58,7 @@ func (suite *KeeperTestSuite) TestNewKeeperWithOptions() { { "success: custom querier", func() { - querierOption := keeper.WithQueryPlugins(&wasmkeeper.QueryPlugins{ + querierOption := keeper.WithQueryPlugins(&keeper.QueryPlugins{ Custom: MockErrorCustomQuerier(), }) k = keeper.NewKeeperWithVM( @@ -73,7 +72,7 @@ func (suite *KeeperTestSuite) TestNewKeeperWithOptions() { ) }, func(k keeper.Keeper) { - plugins := ibcwasm.GetQueryPlugins().(*wasmkeeper.QueryPlugins) + plugins := ibcwasm.GetQueryPlugins().(*keeper.QueryPlugins) _, err := plugins.Custom(sdk.Context{}, nil) suite.Require().ErrorContains(err, "custom querier error for TestNewKeeperWithOptions") @@ -85,7 +84,7 @@ func (suite *KeeperTestSuite) TestNewKeeperWithOptions() { { "success: stargate querier", func() { - querierOption := keeper.WithQueryPlugins(&wasmkeeper.QueryPlugins{ + querierOption := keeper.WithQueryPlugins(&keeper.QueryPlugins{ Stargate: MockErrorStargateQuerier(), }) k = keeper.NewKeeperWithVM( @@ -99,7 +98,7 @@ func (suite *KeeperTestSuite) TestNewKeeperWithOptions() { ) }, func(k keeper.Keeper) { - plugins := ibcwasm.GetQueryPlugins().(*wasmkeeper.QueryPlugins) + plugins := ibcwasm.GetQueryPlugins().(*keeper.QueryPlugins) _, err := plugins.Custom(sdk.Context{}, nil) suite.Require().ErrorIs(err, wasmvmtypes.UnsupportedRequest{Kind: "Custom queries are not allowed"}) @@ -111,7 +110,7 @@ func (suite *KeeperTestSuite) TestNewKeeperWithOptions() { { "success: both queriers", func() { - querierOption := keeper.WithQueryPlugins(&wasmkeeper.QueryPlugins{ + querierOption := keeper.WithQueryPlugins(&keeper.QueryPlugins{ Custom: MockErrorCustomQuerier(), Stargate: MockErrorStargateQuerier(), }) @@ -126,7 +125,7 @@ func (suite *KeeperTestSuite) TestNewKeeperWithOptions() { ) }, func(k keeper.Keeper) { - plugins := ibcwasm.GetQueryPlugins().(*wasmkeeper.QueryPlugins) + plugins := ibcwasm.GetQueryPlugins().(*keeper.QueryPlugins) _, err := plugins.Custom(sdk.Context{}, nil) suite.Require().ErrorContains(err, "custom querier error for TestNewKeeperWithOptions") @@ -143,13 +142,13 @@ func (suite *KeeperTestSuite) TestNewKeeperWithOptions() { suite.Run(tc.name, func() { // make sure the default query plugins are set - ibcwasm.SetQueryPlugins(wasmkeeper.NewDefaultQueryPlugins()) + ibcwasm.SetQueryPlugins(keeper.NewDefaultQueryPlugins()) tc.malleate() tc.verifyFn(k) // reset query plugins after each test - ibcwasm.SetQueryPlugins(wasmkeeper.NewDefaultQueryPlugins()) + ibcwasm.SetQueryPlugins(keeper.NewDefaultQueryPlugins()) }) } } From 12c34cbd6d7803b47a14b7bb1ed559753ece7892 Mon Sep 17 00:00:00 2001 From: DimitrisJim Date: Mon, 8 Apr 2024 04:41:23 +0300 Subject: [PATCH 13/24] refactor: remove queryRouter global variable - Move into the func for creating new plugins, pass it to acceptStargateQuery directly. This is probably what wasmd did (see they don't accept a query router now?). Since we don't use the get elsewhere, this was the smallest diff for a globa. - Preemptively ran make lint-fix, gotcha linting crybaby. --- .../08-wasm/internal/ibcwasm/wasm.go | 19 +------------------ .../light-clients/08-wasm/keeper/keeper_vm.go | 8 +++++--- .../08-wasm/keeper/options_test.go | 4 ++-- .../light-clients/08-wasm/keeper/querier.go | 8 ++++---- .../08-wasm/keeper/querier_test.go | 8 ++++---- 5 files changed, 16 insertions(+), 31 deletions(-) diff --git a/modules/light-clients/08-wasm/internal/ibcwasm/wasm.go b/modules/light-clients/08-wasm/internal/ibcwasm/wasm.go index 64243047858..27d0cb1b3c0 100644 --- a/modules/light-clients/08-wasm/internal/ibcwasm/wasm.go +++ b/modules/light-clients/08-wasm/internal/ibcwasm/wasm.go @@ -4,24 +4,7 @@ import ( "errors" ) -var ( - queryRouter QueryRouter - queryPlugins QueryPluginsI -) - -// SetQueryRouter sets the custom wasm query router for the 08-wasm module. -// Panics if the queryRouter is nil. -func SetQueryRouter(router QueryRouter) { - if router == nil { - panic(errors.New("query router must not be nil")) - } - queryRouter = router -} - -// GetQueryRouter returns the custom wasm query router for the 08-wasm module. -func GetQueryRouter() QueryRouter { - return queryRouter -} +var queryPlugins QueryPluginsI // SetQueryPlugins sets the current query plugins func SetQueryPlugins(plugins QueryPluginsI) { diff --git a/modules/light-clients/08-wasm/keeper/keeper_vm.go b/modules/light-clients/08-wasm/keeper/keeper_vm.go index 4ed63c920a3..780008f4b37 100644 --- a/modules/light-clients/08-wasm/keeper/keeper_vm.go +++ b/modules/light-clients/08-wasm/keeper/keeper_vm.go @@ -34,6 +34,10 @@ func NewKeeperWithVM( panic(errors.New("client keeper must not be nil")) } + if queryRouter == nil { + panic(errors.New("query router must not be nil")) + } + if vm == nil { panic(errors.New("wasm VM must not be nil")) } @@ -66,13 +70,11 @@ func NewKeeperWithVM( // set query plugins to ensure there is a non-nil query plugin // regardless of what options the user provides - ibcwasm.SetQueryPlugins(NewDefaultQueryPlugins()) + ibcwasm.SetQueryPlugins(NewDefaultQueryPlugins(queryRouter)) for _, opt := range opts { opt.apply(keeper) } - ibcwasm.SetQueryRouter(queryRouter) - return *keeper } diff --git a/modules/light-clients/08-wasm/keeper/options_test.go b/modules/light-clients/08-wasm/keeper/options_test.go index efae5f1a498..f6376157d3c 100644 --- a/modules/light-clients/08-wasm/keeper/options_test.go +++ b/modules/light-clients/08-wasm/keeper/options_test.go @@ -142,13 +142,13 @@ func (suite *KeeperTestSuite) TestNewKeeperWithOptions() { suite.Run(tc.name, func() { // make sure the default query plugins are set - ibcwasm.SetQueryPlugins(keeper.NewDefaultQueryPlugins()) + ibcwasm.SetQueryPlugins(keeper.NewDefaultQueryPlugins(GetSimApp(suite.chainA).GRPCQueryRouter())) tc.malleate() tc.verifyFn(k) // reset query plugins after each test - ibcwasm.SetQueryPlugins(keeper.NewDefaultQueryPlugins()) + ibcwasm.SetQueryPlugins(keeper.NewDefaultQueryPlugins(GetSimApp(suite.chainA).GRPCQueryRouter())) }) } } diff --git a/modules/light-clients/08-wasm/keeper/querier.go b/modules/light-clients/08-wasm/keeper/querier.go index 4faff662b43..e1c10c70f9d 100644 --- a/modules/light-clients/08-wasm/keeper/querier.go +++ b/modules/light-clients/08-wasm/keeper/querier.go @@ -124,16 +124,16 @@ func (e QueryPlugins) HandleQuery(ctx sdk.Context, caller string, request wasmvm } // NewDefaultQueryPlugins returns the default set of query plugins -func NewDefaultQueryPlugins() *QueryPlugins { +func NewDefaultQueryPlugins(queryRouter ibcwasm.QueryRouter) *QueryPlugins { return &QueryPlugins{ Custom: RejectCustomQuerier(), - Stargate: AcceptListStargateQuerier([]string{}), + Stargate: AcceptListStargateQuerier([]string{}, queryRouter), } } // AcceptListStargateQuerier allows all queries that are in the provided accept list. // This function returns protobuf encoded responses in bytes. -func AcceptListStargateQuerier(acceptedQueries []string) func(sdk.Context, *wasmvmtypes.StargateQuery) ([]byte, error) { +func AcceptListStargateQuerier(acceptedQueries []string, queryRouter ibcwasm.QueryRouter) func(sdk.Context, *wasmvmtypes.StargateQuery) ([]byte, error) { return func(ctx sdk.Context, request *wasmvmtypes.StargateQuery) ([]byte, error) { // append user defined accepted queries to default list defined above. acceptedQueries = append(defaultAcceptList, acceptedQueries...) @@ -143,7 +143,7 @@ func AcceptListStargateQuerier(acceptedQueries []string) func(sdk.Context, *wasm return nil, wasmvmtypes.UnsupportedRequest{Kind: fmt.Sprintf("'%s' path is not allowed from the contract", request.Path)} } - route := ibcwasm.GetQueryRouter().Route(request.Path) + route := queryRouter.Route(request.Path) if route == nil { return nil, wasmvmtypes.UnsupportedRequest{Kind: fmt.Sprintf("No route to query '%s'", request.Path)} } diff --git a/modules/light-clients/08-wasm/keeper/querier_test.go b/modules/light-clients/08-wasm/keeper/querier_test.go index 49b032ba4dd..c21d61bec13 100644 --- a/modules/light-clients/08-wasm/keeper/querier_test.go +++ b/modules/light-clients/08-wasm/keeper/querier_test.go @@ -121,7 +121,7 @@ func (suite *KeeperTestSuite) TestCustomQuery() { // clientState.Status(suite.chainA.GetContext(), clientStore, suite.chainA.App.AppCodec()) // reset query plugins after each test - ibcwasm.SetQueryPlugins(wasmkeeper.NewDefaultQueryPlugins()) + ibcwasm.SetQueryPlugins(wasmkeeper.NewDefaultQueryPlugins(GetSimApp(suite.chainA).GRPCQueryRouter())) }) } } @@ -147,7 +147,7 @@ func (suite *KeeperTestSuite) TestStargateQuery() { "success: custom query", func() { querierPlugin := wasmkeeper.QueryPlugins{ - Stargate: wasmkeeper.AcceptListStargateQuerier([]string{typeURL}), + Stargate: wasmkeeper.AcceptListStargateQuerier([]string{typeURL}, GetSimApp(suite.chainA).GRPCQueryRouter()), } ibcwasm.SetQueryPlugins(&querierPlugin) @@ -194,7 +194,7 @@ func (suite *KeeperTestSuite) TestStargateQuery() { "success: verify membership query", func() { querierPlugin := wasmkeeper.QueryPlugins{ - Stargate: wasmkeeper.AcceptListStargateQuerier([]string{""}), + Stargate: wasmkeeper.AcceptListStargateQuerier([]string{""}, GetSimApp(suite.chainA).GRPCQueryRouter()), } ibcwasm.SetQueryPlugins(&querierPlugin) @@ -329,7 +329,7 @@ func (suite *KeeperTestSuite) TestStargateQuery() { } // reset query plugins after each test - ibcwasm.SetQueryPlugins(wasmkeeper.NewDefaultQueryPlugins()) + ibcwasm.SetQueryPlugins(wasmkeeper.NewDefaultQueryPlugins(GetSimApp(suite.chainA).GRPCQueryRouter())) }) } } From 151a546db5a0defed4f43d8082ccd607a7e154e8 Mon Sep 17 00:00:00 2001 From: DimitrisJim Date: Mon, 8 Apr 2024 06:07:19 +0300 Subject: [PATCH 14/24] refactor: remove global for query plugins. --- .../08-wasm/internal/ibcwasm/wasm.go | 20 ----- .../light-clients/08-wasm/keeper/keeper.go | 20 ++++- .../light-clients/08-wasm/keeper/keeper_vm.go | 5 +- .../light-clients/08-wasm/keeper/options.go | 8 +- .../08-wasm/keeper/options_test.go | 13 ++- .../light-clients/08-wasm/keeper/querier.go | 10 ++- .../08-wasm/keeper/querier_test.go | 81 +++++++++++-------- 7 files changed, 86 insertions(+), 71 deletions(-) delete mode 100644 modules/light-clients/08-wasm/internal/ibcwasm/wasm.go diff --git a/modules/light-clients/08-wasm/internal/ibcwasm/wasm.go b/modules/light-clients/08-wasm/internal/ibcwasm/wasm.go deleted file mode 100644 index 27d0cb1b3c0..00000000000 --- a/modules/light-clients/08-wasm/internal/ibcwasm/wasm.go +++ /dev/null @@ -1,20 +0,0 @@ -package ibcwasm - -import ( - "errors" -) - -var queryPlugins QueryPluginsI - -// SetQueryPlugins sets the current query plugins -func SetQueryPlugins(plugins QueryPluginsI) { - if plugins == nil { - panic(errors.New("query plugins must not be nil")) - } - queryPlugins = plugins -} - -// GetQueryPlugins returns the current query plugins -func GetQueryPlugins() QueryPluginsI { - return queryPlugins -} diff --git a/modules/light-clients/08-wasm/keeper/keeper.go b/modules/light-clients/08-wasm/keeper/keeper.go index bd3a66b631d..3b46522e7c0 100644 --- a/modules/light-clients/08-wasm/keeper/keeper.go +++ b/modules/light-clients/08-wasm/keeper/keeper.go @@ -34,6 +34,10 @@ type Keeper struct { checksums collections.KeySet[[]byte] storeService store.KVStoreService + // handling queries + // TODO(jim): We had a reason we didn't call this interface QueryHanlder or something? Probably rename var to queryPlugins + queryHandler ibcwasm.QueryPluginsI + clientKeeper types.ClientKeeper authority string @@ -63,12 +67,24 @@ func (k Keeper) GetVM() ibcwasm.WasmEngine { return k.vm } +// TODO(jim): Docu! func (k Keeper) GetChecksums() collections.KeySet[[]byte] { return k.checksums } -func (Keeper) newQueryHandler(ctx sdk.Context, callerID string) *QueryHandler { - return NewQueryHandler(ctx, callerID) +// TODO(jim): Docu! +func (k Keeper) GetQueryPlugins() ibcwasm.QueryPluginsI { + return k.queryHandler +} + +// TODO(jim): Docu! +func (k *Keeper) SetQueryPlugins(plugins ibcwasm.QueryPluginsI) { + k.queryHandler = plugins +} + +// TODO(jim): Probably make query handler private? Can we do that? +func (k Keeper) newQueryHandler(ctx sdk.Context, callerID string) *QueryHandler { + return NewQueryHandler(ctx, k.GetQueryPlugins(), callerID) } func (k Keeper) storeWasmCode(ctx sdk.Context, code []byte, storeFn func(code wasmvm.WasmCode, gasLimit uint64) (wasmvm.Checksum, uint64, error)) ([]byte, error) { diff --git a/modules/light-clients/08-wasm/keeper/keeper_vm.go b/modules/light-clients/08-wasm/keeper/keeper_vm.go index 780008f4b37..8307d6e6738 100644 --- a/modules/light-clients/08-wasm/keeper/keeper_vm.go +++ b/modules/light-clients/08-wasm/keeper/keeper_vm.go @@ -66,11 +66,14 @@ func NewKeeperWithVM( panic(err) } + // TODO(jim): Is this needed? wasmd doesn't hold ref, sdk docs/code do. keeper.schema = schema // set query plugins to ensure there is a non-nil query plugin // regardless of what options the user provides - ibcwasm.SetQueryPlugins(NewDefaultQueryPlugins(queryRouter)) + handler := NewDefaultQueryPlugins(queryRouter) + + keeper.SetQueryPlugins(handler) for _, opt := range opts { opt.apply(keeper) } diff --git a/modules/light-clients/08-wasm/keeper/options.go b/modules/light-clients/08-wasm/keeper/options.go index 7b9f0c8a97a..cdbd0ca55a9 100644 --- a/modules/light-clients/08-wasm/keeper/options.go +++ b/modules/light-clients/08-wasm/keeper/options.go @@ -2,8 +2,6 @@ package keeper import ( "errors" - - "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" ) // Option is an extension point to instantiate keeper with non default values @@ -20,12 +18,12 @@ func (f optsFn) apply(keeper *Keeper) { // WithQueryPlugins is an optional constructor parameter to pass custom query plugins for wasmVM requests. // Missing fields will be filled with default queriers. func WithQueryPlugins(plugins *QueryPlugins) Option { - return optsFn(func(_ *Keeper) { - currentPlugins, ok := ibcwasm.GetQueryPlugins().(*QueryPlugins) + return optsFn(func(k *Keeper) { + currentPlugins, ok := k.GetQueryPlugins().(QueryPlugins) if !ok { panic(errors.New("invalid query plugins type")) } newPlugins := currentPlugins.Merge(plugins) - ibcwasm.SetQueryPlugins(&newPlugins) + k.SetQueryPlugins(newPlugins) }) } diff --git a/modules/light-clients/08-wasm/keeper/options_test.go b/modules/light-clients/08-wasm/keeper/options_test.go index f6376157d3c..25f7aefeda7 100644 --- a/modules/light-clients/08-wasm/keeper/options_test.go +++ b/modules/light-clients/08-wasm/keeper/options_test.go @@ -9,7 +9,6 @@ import ( "github.com/cosmos/cosmos-sdk/runtime" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" ) @@ -46,7 +45,7 @@ func (suite *KeeperTestSuite) TestNewKeeperWithOptions() { ) }, func(k keeper.Keeper) { - plugins := ibcwasm.GetQueryPlugins().(*keeper.QueryPlugins) + plugins := k.GetQueryPlugins().(keeper.QueryPlugins) _, err := plugins.Custom(sdk.Context{}, nil) suite.Require().ErrorIs(err, wasmvmtypes.UnsupportedRequest{Kind: "Custom queries are not allowed"}) @@ -72,7 +71,7 @@ func (suite *KeeperTestSuite) TestNewKeeperWithOptions() { ) }, func(k keeper.Keeper) { - plugins := ibcwasm.GetQueryPlugins().(*keeper.QueryPlugins) + plugins := k.GetQueryPlugins().(keeper.QueryPlugins) _, err := plugins.Custom(sdk.Context{}, nil) suite.Require().ErrorContains(err, "custom querier error for TestNewKeeperWithOptions") @@ -98,7 +97,7 @@ func (suite *KeeperTestSuite) TestNewKeeperWithOptions() { ) }, func(k keeper.Keeper) { - plugins := ibcwasm.GetQueryPlugins().(*keeper.QueryPlugins) + plugins := k.GetQueryPlugins().(keeper.QueryPlugins) _, err := plugins.Custom(sdk.Context{}, nil) suite.Require().ErrorIs(err, wasmvmtypes.UnsupportedRequest{Kind: "Custom queries are not allowed"}) @@ -125,7 +124,7 @@ func (suite *KeeperTestSuite) TestNewKeeperWithOptions() { ) }, func(k keeper.Keeper) { - plugins := ibcwasm.GetQueryPlugins().(*keeper.QueryPlugins) + plugins := k.GetQueryPlugins().(keeper.QueryPlugins) _, err := plugins.Custom(sdk.Context{}, nil) suite.Require().ErrorContains(err, "custom querier error for TestNewKeeperWithOptions") @@ -142,13 +141,13 @@ func (suite *KeeperTestSuite) TestNewKeeperWithOptions() { suite.Run(tc.name, func() { // make sure the default query plugins are set - ibcwasm.SetQueryPlugins(keeper.NewDefaultQueryPlugins(GetSimApp(suite.chainA).GRPCQueryRouter())) + k.SetQueryPlugins(keeper.NewDefaultQueryPlugins(GetSimApp(suite.chainA).GRPCQueryRouter())) tc.malleate() tc.verifyFn(k) // reset query plugins after each test - ibcwasm.SetQueryPlugins(keeper.NewDefaultQueryPlugins(GetSimApp(suite.chainA).GRPCQueryRouter())) + k.SetQueryPlugins(keeper.NewDefaultQueryPlugins(GetSimApp(suite.chainA).GRPCQueryRouter())) }) } } diff --git a/modules/light-clients/08-wasm/keeper/querier.go b/modules/light-clients/08-wasm/keeper/querier.go index e1c10c70f9d..3e069b22b45 100644 --- a/modules/light-clients/08-wasm/keeper/querier.go +++ b/modules/light-clients/08-wasm/keeper/querier.go @@ -43,14 +43,16 @@ var defaultAcceptList = []string{ // into the query plugins. type QueryHandler struct { Ctx sdk.Context + Plugins ibcwasm.QueryPluginsI CallerID string } // NewQueryHandler returns a default querier that can be used in the contract. // TODO(jim): Make private and use export_test? -func NewQueryHandler(ctx sdk.Context, callerID string) *QueryHandler { +func NewQueryHandler(ctx sdk.Context, plugins ibcwasm.QueryPluginsI, callerID string) *QueryHandler { return &QueryHandler{ Ctx: ctx, + Plugins: plugins, CallerID: callerID, } } @@ -72,7 +74,7 @@ func (q *QueryHandler) Query(request wasmvmtypes.QueryRequest, gasLimit uint64) q.Ctx.GasMeter().ConsumeGas(subCtx.GasMeter().GasConsumed(), "contract sub-query") }() - res, err := ibcwasm.GetQueryPlugins().HandleQuery(subCtx, q.CallerID, request) + res, err := q.Plugins.HandleQuery(subCtx, q.CallerID, request) if err == nil { return res, nil } @@ -124,8 +126,8 @@ func (e QueryPlugins) HandleQuery(ctx sdk.Context, caller string, request wasmvm } // NewDefaultQueryPlugins returns the default set of query plugins -func NewDefaultQueryPlugins(queryRouter ibcwasm.QueryRouter) *QueryPlugins { - return &QueryPlugins{ +func NewDefaultQueryPlugins(queryRouter ibcwasm.QueryRouter) QueryPlugins { + return QueryPlugins{ Custom: RejectCustomQuerier(), Stargate: AcceptListStargateQuerier([]string{}, queryRouter), } diff --git a/modules/light-clients/08-wasm/keeper/querier_test.go b/modules/light-clients/08-wasm/keeper/querier_test.go index c21d61bec13..62c2d2e7c48 100644 --- a/modules/light-clients/08-wasm/keeper/querier_test.go +++ b/modules/light-clients/08-wasm/keeper/querier_test.go @@ -11,8 +11,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" - wasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper" wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" @@ -48,16 +47,17 @@ func MockCustomQuerier() func(sdk.Context, json.RawMessage) ([]byte, error) { func (suite *KeeperTestSuite) TestCustomQuery() { testCases := []struct { name string - malleate func() + malleate func(k *keeper.Keeper) + expError error }{ { "success: custom query", - func() { - querierPlugin := wasmkeeper.QueryPlugins{ + func(k *keeper.Keeper) { + querierPlugin := keeper.QueryPlugins{ Custom: MockCustomQuerier(), } - ibcwasm.SetQueryPlugins(&querierPlugin) + k.SetQueryPlugins(querierPlugin) suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, querier wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { echo := CustomQuery{ Echo: &QueryEcho{ @@ -83,10 +83,11 @@ func (suite *KeeperTestSuite) TestCustomQuery() { return &wasmvmtypes.QueryResult{Ok: resp}, wasmtesting.DefaultGasUsed, nil }) }, + nil, }, { "failure: default query", - func() { + func(k *keeper.Keeper) { suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, querier wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { resp, err := querier.Query(wasmvmtypes.QueryRequest{Custom: json.RawMessage("{}")}, math.MaxUint64) suite.Require().ErrorIs(err, wasmvmtypes.UnsupportedRequest{Kind: "Custom queries are not allowed"}) @@ -95,6 +96,7 @@ func (suite *KeeperTestSuite) TestCustomQuery() { return nil, wasmtesting.DefaultGasUsed, err }) }, + types.ErrVMError, }, } @@ -107,21 +109,27 @@ func (suite *KeeperTestSuite) TestCustomQuery() { err := endpoint.CreateClient() suite.Require().NoError(err) - tc.malleate() + wasmKeeper := GetSimApp(suite.chainA).WasmClientKeeper - clientModule, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(endpoint.ClientID) - suite.Require().True(found) + tc.malleate(&wasmKeeper) - // clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpoint.ClientID) - // clientState, ok := endpoint.GetClientState().(*types.ClientState) - // suite.Require().True(ok) + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpoint.ClientID) + clientState, ok := endpoint.GetClientState().(*types.ClientState) + suite.Require().True(ok) - clientModule.Status(suite.chainA.GetContext(), endpoint.ClientID) + res, err := keeper.WasmQuery[types.StatusResult](suite.chainA.GetContext(), wasmKeeper, endpoint.ClientID, clientStore, clientState, types.QueryMsg{Status: &types.StatusMsg{}}) - // clientState.Status(suite.chainA.GetContext(), clientStore, suite.chainA.App.AppCodec()) + expPass := tc.expError == nil + if expPass { + suite.Require().Nil(err) + suite.Require().NotNil(res) + } else { + suite.Require().Equal(res.Status, "") + suite.Require().ErrorIs(err, tc.expError) + } // reset query plugins after each test - ibcwasm.SetQueryPlugins(wasmkeeper.NewDefaultQueryPlugins(GetSimApp(suite.chainA).GRPCQueryRouter())) + wasmKeeper.SetQueryPlugins(keeper.NewDefaultQueryPlugins(GetSimApp(suite.chainA).GRPCQueryRouter())) }) } } @@ -140,17 +148,17 @@ func (suite *KeeperTestSuite) TestStargateQuery() { testCases := []struct { name string - malleate func() + malleate func(k *keeper.Keeper) expError error }{ { "success: custom query", - func() { - querierPlugin := wasmkeeper.QueryPlugins{ - Stargate: wasmkeeper.AcceptListStargateQuerier([]string{typeURL}, GetSimApp(suite.chainA).GRPCQueryRouter()), + func(k *keeper.Keeper) { + querierPlugin := keeper.QueryPlugins{ + Stargate: keeper.AcceptListStargateQuerier([]string{typeURL}, GetSimApp(suite.chainA).GRPCQueryRouter()), } - ibcwasm.SetQueryPlugins(&querierPlugin) + k.SetQueryPlugins(&querierPlugin) suite.mockVM.RegisterQueryCallback(types.TimestampAtHeightMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, querier wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { queryRequest := types.QueryChecksumsRequest{} @@ -192,12 +200,12 @@ func (suite *KeeperTestSuite) TestStargateQuery() { // This exercises the full flow through the grpc handler and into the light client for verification, handling encoding and routing. // Furthermore we write a test key and assert that the state changes made by this handler were discarded by the cachedCtx at the grpc handler. "success: verify membership query", - func() { - querierPlugin := wasmkeeper.QueryPlugins{ - Stargate: wasmkeeper.AcceptListStargateQuerier([]string{""}, GetSimApp(suite.chainA).GRPCQueryRouter()), + func(k *keeper.Keeper) { + querierPlugin := keeper.QueryPlugins{ + Stargate: keeper.AcceptListStargateQuerier([]string{""}, GetSimApp(suite.chainA).GRPCQueryRouter()), } - ibcwasm.SetQueryPlugins(&querierPlugin) + k.SetQueryPlugins(&querierPlugin) store := suite.chainA.GetContext().KVStore(GetSimApp(suite.chainA).GetKey(exported.StoreKey)) store.Set(proofKey, value) @@ -269,7 +277,7 @@ func (suite *KeeperTestSuite) TestStargateQuery() { }, { "failure: default querier", - func() { + func(k *keeper.Keeper) { suite.mockVM.RegisterQueryCallback(types.TimestampAtHeightMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, querier wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { queryRequest := types.QueryChecksumsRequest{} bz, err := queryRequest.Marshal() @@ -303,15 +311,24 @@ func (suite *KeeperTestSuite) TestStargateQuery() { err := endpoint.CreateClient() suite.Require().NoError(err) - tc.malleate() + wasmKeeper := GetSimApp(suite.chainA).WasmClientKeeper + + tc.malleate(&wasmKeeper) + + payload := types.QueryMsg{ + TimestampAtHeight: &types.TimestampAtHeightMsg{ + Height: clienttypes.NewHeight(1, 100), + }, + } - clientModule, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(endpoint.ClientID) - suite.Require().True(found) + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpoint.ClientID) + clientState, ok := endpoint.GetClientState().(*types.ClientState) + suite.Require().True(ok) // NOTE: we register query callbacks against: types.TimestampAtHeightMsg{} // in practise, this can against any client state msg, however registering against types.StatusMsg{} introduces recursive loops // due to test case: "success: verify membership query" - _, err = clientModule.TimestampAtHeight(suite.chainA.GetContext(), endpoint.ClientID, clienttypes.NewHeight(1, 100)) + _, err = keeper.WasmQuery[types.TimestampAtHeightResult](suite.chainA.GetContext(), wasmKeeper, endpoint.ClientID, clientStore, clientState, payload) expPass := tc.expError == nil if expPass { @@ -321,7 +338,7 @@ func (suite *KeeperTestSuite) TestStargateQuery() { suite.Require().ErrorContains(err, tc.expError.Error()) } - clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpoint.ClientID) + clientStore = suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpoint.ClientID) if expDiscardedState { suite.Require().False(clientStore.Has(testKey)) } else { @@ -329,7 +346,7 @@ func (suite *KeeperTestSuite) TestStargateQuery() { } // reset query plugins after each test - ibcwasm.SetQueryPlugins(wasmkeeper.NewDefaultQueryPlugins(GetSimApp(suite.chainA).GRPCQueryRouter())) + wasmKeeper.SetQueryPlugins(keeper.NewDefaultQueryPlugins(GetSimApp(suite.chainA).GRPCQueryRouter())) }) } } From f679ccf00d97a64bf18767d4885cb18ccf721d19 Mon Sep 17 00:00:00 2001 From: DimitrisJim Date: Mon, 8 Apr 2024 11:13:22 +0300 Subject: [PATCH 15/24] nit: rename to queryPlugins. --- modules/light-clients/08-wasm/keeper/keeper.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/light-clients/08-wasm/keeper/keeper.go b/modules/light-clients/08-wasm/keeper/keeper.go index 3b46522e7c0..49dcf58b1d1 100644 --- a/modules/light-clients/08-wasm/keeper/keeper.go +++ b/modules/light-clients/08-wasm/keeper/keeper.go @@ -35,8 +35,8 @@ type Keeper struct { storeService store.KVStoreService // handling queries - // TODO(jim): We had a reason we didn't call this interface QueryHanlder or something? Probably rename var to queryPlugins - queryHandler ibcwasm.QueryPluginsI + // TODO(jim): We had a reason we didn't call this interface QueryHanlder or something? Should we rename it to QueryHandler? + queryPlugins ibcwasm.QueryPluginsI clientKeeper types.ClientKeeper @@ -74,12 +74,12 @@ func (k Keeper) GetChecksums() collections.KeySet[[]byte] { // TODO(jim): Docu! func (k Keeper) GetQueryPlugins() ibcwasm.QueryPluginsI { - return k.queryHandler + return k.queryPlugins } // TODO(jim): Docu! func (k *Keeper) SetQueryPlugins(plugins ibcwasm.QueryPluginsI) { - k.queryHandler = plugins + k.queryPlugins = plugins } // TODO(jim): Probably make query handler private? Can we do that? From e726ce9789f69af85a732984d66c673318268a41 Mon Sep 17 00:00:00 2001 From: DimitrisJim Date: Mon, 8 Apr 2024 14:21:00 +0300 Subject: [PATCH 16/24] refactor: remove QueryPluginsI interface. --- .../08-wasm/internal/ibcwasm/expected_interfaces.go | 6 ------ modules/light-clients/08-wasm/keeper/keeper.go | 6 +++--- modules/light-clients/08-wasm/keeper/options.go | 10 ++-------- modules/light-clients/08-wasm/keeper/options_test.go | 8 ++++---- modules/light-clients/08-wasm/keeper/querier.go | 9 +++------ modules/light-clients/08-wasm/keeper/querier_test.go | 4 ++-- 6 files changed, 14 insertions(+), 29 deletions(-) diff --git a/modules/light-clients/08-wasm/internal/ibcwasm/expected_interfaces.go b/modules/light-clients/08-wasm/internal/ibcwasm/expected_interfaces.go index e03c156071a..ea000c4f26b 100644 --- a/modules/light-clients/08-wasm/internal/ibcwasm/expected_interfaces.go +++ b/modules/light-clients/08-wasm/internal/ibcwasm/expected_interfaces.go @@ -5,7 +5,6 @@ import ( wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" "github.com/cosmos/cosmos-sdk/baseapp" - sdk "github.com/cosmos/cosmos-sdk/types" ) type WasmEngine interface { @@ -123,8 +122,3 @@ type QueryRouter interface { // if not found Route(path string) baseapp.GRPCQueryHandler } - -type QueryPluginsI interface { - // HandleQuery will route the query to the correct plugin and return the result - HandleQuery(ctx sdk.Context, caller string, request wasmvmtypes.QueryRequest) ([]byte, error) -} diff --git a/modules/light-clients/08-wasm/keeper/keeper.go b/modules/light-clients/08-wasm/keeper/keeper.go index 49dcf58b1d1..e2830bede75 100644 --- a/modules/light-clients/08-wasm/keeper/keeper.go +++ b/modules/light-clients/08-wasm/keeper/keeper.go @@ -36,7 +36,7 @@ type Keeper struct { // handling queries // TODO(jim): We had a reason we didn't call this interface QueryHanlder or something? Should we rename it to QueryHandler? - queryPlugins ibcwasm.QueryPluginsI + queryPlugins QueryPlugins clientKeeper types.ClientKeeper @@ -73,12 +73,12 @@ func (k Keeper) GetChecksums() collections.KeySet[[]byte] { } // TODO(jim): Docu! -func (k Keeper) GetQueryPlugins() ibcwasm.QueryPluginsI { +func (k Keeper) GetQueryPlugins() QueryPlugins { return k.queryPlugins } // TODO(jim): Docu! -func (k *Keeper) SetQueryPlugins(plugins ibcwasm.QueryPluginsI) { +func (k *Keeper) SetQueryPlugins(plugins QueryPlugins) { k.queryPlugins = plugins } diff --git a/modules/light-clients/08-wasm/keeper/options.go b/modules/light-clients/08-wasm/keeper/options.go index cdbd0ca55a9..320f37be351 100644 --- a/modules/light-clients/08-wasm/keeper/options.go +++ b/modules/light-clients/08-wasm/keeper/options.go @@ -1,9 +1,5 @@ package keeper -import ( - "errors" -) - // Option is an extension point to instantiate keeper with non default values type Option interface { apply(*Keeper) @@ -19,11 +15,9 @@ func (f optsFn) apply(keeper *Keeper) { // Missing fields will be filled with default queriers. func WithQueryPlugins(plugins *QueryPlugins) Option { return optsFn(func(k *Keeper) { - currentPlugins, ok := k.GetQueryPlugins().(QueryPlugins) - if !ok { - panic(errors.New("invalid query plugins type")) - } + currentPlugins := k.GetQueryPlugins() newPlugins := currentPlugins.Merge(plugins) + k.SetQueryPlugins(newPlugins) }) } diff --git a/modules/light-clients/08-wasm/keeper/options_test.go b/modules/light-clients/08-wasm/keeper/options_test.go index 25f7aefeda7..8ebcde99352 100644 --- a/modules/light-clients/08-wasm/keeper/options_test.go +++ b/modules/light-clients/08-wasm/keeper/options_test.go @@ -45,7 +45,7 @@ func (suite *KeeperTestSuite) TestNewKeeperWithOptions() { ) }, func(k keeper.Keeper) { - plugins := k.GetQueryPlugins().(keeper.QueryPlugins) + plugins := k.GetQueryPlugins() _, err := plugins.Custom(sdk.Context{}, nil) suite.Require().ErrorIs(err, wasmvmtypes.UnsupportedRequest{Kind: "Custom queries are not allowed"}) @@ -71,7 +71,7 @@ func (suite *KeeperTestSuite) TestNewKeeperWithOptions() { ) }, func(k keeper.Keeper) { - plugins := k.GetQueryPlugins().(keeper.QueryPlugins) + plugins := k.GetQueryPlugins() _, err := plugins.Custom(sdk.Context{}, nil) suite.Require().ErrorContains(err, "custom querier error for TestNewKeeperWithOptions") @@ -97,7 +97,7 @@ func (suite *KeeperTestSuite) TestNewKeeperWithOptions() { ) }, func(k keeper.Keeper) { - plugins := k.GetQueryPlugins().(keeper.QueryPlugins) + plugins := k.GetQueryPlugins() _, err := plugins.Custom(sdk.Context{}, nil) suite.Require().ErrorIs(err, wasmvmtypes.UnsupportedRequest{Kind: "Custom queries are not allowed"}) @@ -124,7 +124,7 @@ func (suite *KeeperTestSuite) TestNewKeeperWithOptions() { ) }, func(k keeper.Keeper) { - plugins := k.GetQueryPlugins().(keeper.QueryPlugins) + plugins := k.GetQueryPlugins() _, err := plugins.Custom(sdk.Context{}, nil) suite.Require().ErrorContains(err, "custom querier error for TestNewKeeperWithOptions") diff --git a/modules/light-clients/08-wasm/keeper/querier.go b/modules/light-clients/08-wasm/keeper/querier.go index 3e069b22b45..49dacfde7d0 100644 --- a/modules/light-clients/08-wasm/keeper/querier.go +++ b/modules/light-clients/08-wasm/keeper/querier.go @@ -29,10 +29,7 @@ to `baseapp.GRPCQueryRouter`. This design is based on wasmd's (v0.50.0) querier plugin design. */ -var ( - _ wasmvmtypes.Querier = (*QueryHandler)(nil) - _ ibcwasm.QueryPluginsI = (*QueryPlugins)(nil) -) +var _ wasmvmtypes.Querier = (*QueryHandler)(nil) // defaultAcceptList defines a set of default allowed queries made available to the Querier. var defaultAcceptList = []string{ @@ -43,13 +40,13 @@ var defaultAcceptList = []string{ // into the query plugins. type QueryHandler struct { Ctx sdk.Context - Plugins ibcwasm.QueryPluginsI + Plugins QueryPlugins CallerID string } // NewQueryHandler returns a default querier that can be used in the contract. // TODO(jim): Make private and use export_test? -func NewQueryHandler(ctx sdk.Context, plugins ibcwasm.QueryPluginsI, callerID string) *QueryHandler { +func NewQueryHandler(ctx sdk.Context, plugins QueryPlugins, callerID string) *QueryHandler { return &QueryHandler{ Ctx: ctx, Plugins: plugins, diff --git a/modules/light-clients/08-wasm/keeper/querier_test.go b/modules/light-clients/08-wasm/keeper/querier_test.go index 62c2d2e7c48..95991c03d58 100644 --- a/modules/light-clients/08-wasm/keeper/querier_test.go +++ b/modules/light-clients/08-wasm/keeper/querier_test.go @@ -158,7 +158,7 @@ func (suite *KeeperTestSuite) TestStargateQuery() { Stargate: keeper.AcceptListStargateQuerier([]string{typeURL}, GetSimApp(suite.chainA).GRPCQueryRouter()), } - k.SetQueryPlugins(&querierPlugin) + k.SetQueryPlugins(querierPlugin) suite.mockVM.RegisterQueryCallback(types.TimestampAtHeightMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, querier wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { queryRequest := types.QueryChecksumsRequest{} @@ -205,7 +205,7 @@ func (suite *KeeperTestSuite) TestStargateQuery() { Stargate: keeper.AcceptListStargateQuerier([]string{""}, GetSimApp(suite.chainA).GRPCQueryRouter()), } - k.SetQueryPlugins(&querierPlugin) + k.SetQueryPlugins(querierPlugin) store := suite.chainA.GetContext().KVStore(GetSimApp(suite.chainA).GetKey(exported.StoreKey)) store.Set(proofKey, value) From 480fcf8dccca8610741e4f5548b1275d979ba491 Mon Sep 17 00:00:00 2001 From: DimitrisJim Date: Mon, 8 Apr 2024 14:41:08 +0300 Subject: [PATCH 17/24] refactor: make queryHandler a private type. --- modules/light-clients/08-wasm/keeper/keeper.go | 12 ++++++------ modules/light-clients/08-wasm/keeper/querier.go | 16 ++++++++-------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/modules/light-clients/08-wasm/keeper/keeper.go b/modules/light-clients/08-wasm/keeper/keeper.go index e2830bede75..040ccc5c0a2 100644 --- a/modules/light-clients/08-wasm/keeper/keeper.go +++ b/modules/light-clients/08-wasm/keeper/keeper.go @@ -67,24 +67,24 @@ func (k Keeper) GetVM() ibcwasm.WasmEngine { return k.vm } -// TODO(jim): Docu! +// GetChecksums returns the stored checksums. +// TODO(jim): Need a better name here, this + grpc query meth + GetAllChecksums are all too similar. func (k Keeper) GetChecksums() collections.KeySet[[]byte] { return k.checksums } -// TODO(jim): Docu! +// GetQueryPlugins returns the set query plugins. func (k Keeper) GetQueryPlugins() QueryPlugins { return k.queryPlugins } -// TODO(jim): Docu! +// SetQueryPlugins sets the plugins. func (k *Keeper) SetQueryPlugins(plugins QueryPlugins) { k.queryPlugins = plugins } -// TODO(jim): Probably make query handler private? Can we do that? -func (k Keeper) newQueryHandler(ctx sdk.Context, callerID string) *QueryHandler { - return NewQueryHandler(ctx, k.GetQueryPlugins(), callerID) +func (k Keeper) newQueryHandler(ctx sdk.Context, callerID string) *queryHandler { + return newQueryHandler(ctx, k.GetQueryPlugins(), callerID) } func (k Keeper) storeWasmCode(ctx sdk.Context, code []byte, storeFn func(code wasmvm.WasmCode, gasLimit uint64) (wasmvm.Checksum, uint64, error)) ([]byte, error) { diff --git a/modules/light-clients/08-wasm/keeper/querier.go b/modules/light-clients/08-wasm/keeper/querier.go index 49dacfde7d0..6d932ee57a0 100644 --- a/modules/light-clients/08-wasm/keeper/querier.go +++ b/modules/light-clients/08-wasm/keeper/querier.go @@ -29,25 +29,25 @@ to `baseapp.GRPCQueryRouter`. This design is based on wasmd's (v0.50.0) querier plugin design. */ -var _ wasmvmtypes.Querier = (*QueryHandler)(nil) +var _ wasmvmtypes.Querier = (*queryHandler)(nil) // defaultAcceptList defines a set of default allowed queries made available to the Querier. var defaultAcceptList = []string{ "/ibc.core.client.v1.Query/VerifyMembership", } -// QueryHandler is a wrapper around the sdk.Context and the CallerID that calls +// queryHandler is a wrapper around the sdk.Context and the CallerID that calls // into the query plugins. -type QueryHandler struct { +type queryHandler struct { Ctx sdk.Context Plugins QueryPlugins CallerID string } -// NewQueryHandler returns a default querier that can be used in the contract. +// newQueryHandler returns a default querier that can be used in the contract. // TODO(jim): Make private and use export_test? -func NewQueryHandler(ctx sdk.Context, plugins QueryPlugins, callerID string) *QueryHandler { - return &QueryHandler{ +func newQueryHandler(ctx sdk.Context, plugins QueryPlugins, callerID string) *queryHandler { + return &queryHandler{ Ctx: ctx, Plugins: plugins, CallerID: callerID, @@ -55,12 +55,12 @@ func NewQueryHandler(ctx sdk.Context, plugins QueryPlugins, callerID string) *Qu } // GasConsumed implements the wasmvmtypes.Querier interface. -func (q *QueryHandler) GasConsumed() uint64 { +func (q *queryHandler) GasConsumed() uint64 { return VMGasRegister.ToWasmVMGas(q.Ctx.GasMeter().GasConsumed()) } // Query implements the wasmvmtypes.Querier interface. -func (q *QueryHandler) Query(request wasmvmtypes.QueryRequest, gasLimit uint64) ([]byte, error) { +func (q *queryHandler) Query(request wasmvmtypes.QueryRequest, gasLimit uint64) ([]byte, error) { sdkGas := VMGasRegister.FromWasmVMGas(gasLimit) // discard all changes/events in subCtx by not committing the cached context From d614a9314453e5de371acf88f97cfabc11efd2eb Mon Sep 17 00:00:00 2001 From: DimitrisJim Date: Mon, 8 Apr 2024 14:50:34 +0300 Subject: [PATCH 18/24] Make migrateContractCode a private function, clean up uneeded export in export_test.go for types/. --- .../light-clients/08-wasm/keeper/export_test.go | 8 ++++++++ modules/light-clients/08-wasm/keeper/keeper.go | 3 +-- modules/light-clients/08-wasm/keeper/msg_server.go | 2 +- modules/light-clients/08-wasm/types/export_test.go | 7 ------- modules/light-clients/08-wasm/types/store_test.go | 14 +++++++------- 5 files changed, 17 insertions(+), 17 deletions(-) create mode 100644 modules/light-clients/08-wasm/keeper/export_test.go diff --git a/modules/light-clients/08-wasm/keeper/export_test.go b/modules/light-clients/08-wasm/keeper/export_test.go new file mode 100644 index 00000000000..a1e992b7f8c --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/export_test.go @@ -0,0 +1,8 @@ +package keeper + +import sdk "github.com/cosmos/cosmos-sdk/types" + +// MigrateContractCode is a wrapper around k.migrateContractCode to allow the method to be directly called in tests. +func (k Keeper) MigrateContractCode(ctx sdk.Context, clientID string, newChecksum, migrateMsg []byte) error { + return k.migrateContractCode(ctx, clientID, newChecksum, migrateMsg) +} diff --git a/modules/light-clients/08-wasm/keeper/keeper.go b/modules/light-clients/08-wasm/keeper/keeper.go index 040ccc5c0a2..c28d28ece75 100644 --- a/modules/light-clients/08-wasm/keeper/keeper.go +++ b/modules/light-clients/08-wasm/keeper/keeper.go @@ -139,8 +139,7 @@ func (k Keeper) storeWasmCode(ctx sdk.Context, code []byte, storeFn func(code wa return checksum, nil } -// TODO(jim): Use an export_test.go here too and make private again. -func (k Keeper) MigrateContractCode(ctx sdk.Context, clientID string, newChecksum, migrateMsg []byte) error { +func (k Keeper) migrateContractCode(ctx sdk.Context, clientID string, newChecksum, migrateMsg []byte) error { wasmClientState, err := k.GetWasmClientState(ctx, clientID) if err != nil { return errorsmod.Wrap(err, "failed to retrieve wasm client state") diff --git a/modules/light-clients/08-wasm/keeper/msg_server.go b/modules/light-clients/08-wasm/keeper/msg_server.go index 19357b27d75..606baaa9fad 100644 --- a/modules/light-clients/08-wasm/keeper/msg_server.go +++ b/modules/light-clients/08-wasm/keeper/msg_server.go @@ -64,7 +64,7 @@ func (k Keeper) MigrateContract(goCtx context.Context, msg *types.MsgMigrateCont ctx := sdk.UnwrapSDKContext(goCtx) - err := k.MigrateContractCode(ctx, msg.ClientId, msg.Checksum, msg.Msg) + err := k.migrateContractCode(ctx, msg.ClientId, msg.Checksum, msg.Msg) if err != nil { return nil, errorsmod.Wrap(err, "failed to migrate contract") } diff --git a/modules/light-clients/08-wasm/types/export_test.go b/modules/light-clients/08-wasm/types/export_test.go index 0dd12853582..cc0ed8aa270 100644 --- a/modules/light-clients/08-wasm/types/export_test.go +++ b/modules/light-clients/08-wasm/types/export_test.go @@ -11,13 +11,6 @@ import ( // MaxWasmSize is the maximum size of a wasm code in bytes. const MaxWasmSize = maxWasmSize -// NewMigrateProposalWrappedStore is a wrapper around newMigrateProposalWrappedStore to allow the function to be directly called in tests. -// -//nolint:revive // Returning unexported type for testing purposes. -func NewMigrateProposalWrappedStore(subjectStore, substituteStore storetypes.KVStore) MigrateClientWrappedStore { - return NewMigrateClientWrappedStore(subjectStore, substituteStore) -} - // GetStore is a wrapper around getStore to allow the function to be directly called in tests. func (ws MigrateClientWrappedStore) GetStore(key []byte) (storetypes.KVStore, bool) { return ws.getStore(key) diff --git a/modules/light-clients/08-wasm/types/store_test.go b/modules/light-clients/08-wasm/types/store_test.go index 81d0ef212fe..0633caf5747 100644 --- a/modules/light-clients/08-wasm/types/store_test.go +++ b/modules/light-clients/08-wasm/types/store_test.go @@ -45,7 +45,7 @@ func (suite *TypesTestSuite) TestMigrateClientWrappedStoreGetStore() { for _, tc := range testCases { tc := tc suite.Run(tc.name, func() { - wrappedStore := types.NewMigrateProposalWrappedStore(subjectStore, substituteStore) + wrappedStore := types.NewMigrateClientWrappedStore(subjectStore, substituteStore) store, found := wrappedStore.GetStore(tc.prefix) @@ -137,7 +137,7 @@ func (suite *TypesTestSuite) TestMigrateClientWrappedStoreGet() { for _, tc := range testCases { tc := tc suite.Run(tc.name, func() { - wrappedStore := types.NewMigrateProposalWrappedStore(subjectStore, substituteStore) + wrappedStore := types.NewMigrateClientWrappedStore(subjectStore, substituteStore) prefixedKey := tc.prefix prefixedKey = append(prefixedKey, tc.key...) @@ -182,7 +182,7 @@ func (suite *TypesTestSuite) TestMigrateClientWrappedStoreSet() { suite.Run(tc.name, func() { // calls suite.SetupWasmWithMockVM() and creates two clients with their respective stores subjectStore, substituteStore := suite.GetSubjectAndSubstituteStore() - wrappedStore := types.NewMigrateProposalWrappedStore(subjectStore, substituteStore) + wrappedStore := types.NewMigrateClientWrappedStore(subjectStore, substituteStore) prefixedKey := tc.prefix prefixedKey = append(prefixedKey, tc.key...) @@ -233,7 +233,7 @@ func (suite *TypesTestSuite) TestMigrateClientWrappedStoreDelete() { suite.Run(tc.name, func() { // calls suite.SetupWasmWithMockVM() and creates two clients with their respective stores subjectStore, substituteStore := suite.GetSubjectAndSubstituteStore() - wrappedStore := types.NewMigrateProposalWrappedStore(subjectStore, substituteStore) + wrappedStore := types.NewMigrateClientWrappedStore(subjectStore, substituteStore) prefixedKey := tc.prefix prefixedKey = append(prefixedKey, tc.key...) @@ -305,7 +305,7 @@ func (suite *TypesTestSuite) TestMigrateClientWrappedStoreIterators() { for _, tc := range testCases { tc := tc suite.Run(tc.name, func() { - wrappedStore := types.NewMigrateProposalWrappedStore(subjectStore, substituteStore) + wrappedStore := types.NewMigrateClientWrappedStore(subjectStore, substituteStore) prefixedKeyStart := tc.prefixStart prefixedKeyStart = append(prefixedKeyStart, tc.start...) @@ -362,11 +362,11 @@ func (suite *TypesTestSuite) TestNewMigrateClientWrappedStore() { expPass := !tc.expPanic if expPass { suite.Require().NotPanics(func() { - types.NewMigrateProposalWrappedStore(subjectStore, substituteStore) + types.NewMigrateClientWrappedStore(subjectStore, substituteStore) }) } else { suite.Require().Panics(func() { - types.NewMigrateProposalWrappedStore(subjectStore, substituteStore) + types.NewMigrateClientWrappedStore(subjectStore, substituteStore) }) } }) From 8e6b56726b31c06f865be88ae36afd0b2e3043ab Mon Sep 17 00:00:00 2001 From: DimitrisJim Date: Mon, 8 Apr 2024 16:17:19 +0300 Subject: [PATCH 19/24] refactor: Move vm entrypoints to keeper. - Remove generic types and do unmarshalling in light client methods. - Move all functions onto keeper and adjust call sites. --- .../08-wasm/keeper/contract_keeper.go | 48 +++++++----------- .../08-wasm/keeper/contract_keeper_test.go | 49 ++++++++++--------- .../light-clients/08-wasm/keeper/keeper.go | 2 +- .../08-wasm/keeper/querier_test.go | 7 +-- .../08-wasm/light_client_module.go | 43 +++++++++++----- 5 files changed, 83 insertions(+), 66 deletions(-) diff --git a/modules/light-clients/08-wasm/keeper/contract_keeper.go b/modules/light-clients/08-wasm/keeper/contract_keeper.go index 32fe7a3bb2a..77fbeba0e44 100644 --- a/modules/light-clients/08-wasm/keeper/contract_keeper.go +++ b/modules/light-clients/08-wasm/keeper/contract_keeper.go @@ -38,27 +38,21 @@ var ( // - the payload cannot be marshaled to JSON // - the contract query returns an error // - the data bytes of the response cannot be unmarshal into the result type -func WasmQuery[T types.ContractResult](ctx sdk.Context, k Keeper, clientID string, clientStore storetypes.KVStore, cs *types.ClientState, payload types.QueryMsg) (T, error) { - var result T - +func (k Keeper) WasmQuery(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, cs *types.ClientState, payload types.QueryMsg) ([]byte, error) { encodedData, err := json.Marshal(payload) if err != nil { - return result, errorsmod.Wrap(err, "failed to marshal payload for wasm query") + return nil, errorsmod.Wrap(err, "failed to marshal payload for wasm query") } res, err := k.queryContract(ctx, clientID, clientStore, cs.Checksum, encodedData) if err != nil { - return result, errorsmod.Wrap(types.ErrVMError, err.Error()) + return nil, errorsmod.Wrap(types.ErrVMError, err.Error()) } if res.Err != "" { - return result, errorsmod.Wrap(types.ErrWasmContractCallFailed, res.Err) - } - - if err := json.Unmarshal(res.Ok, &result); err != nil { - return result, errorsmod.Wrapf(types.ErrWasmInvalidResponseData, "failed to unmarshal result of wasm query: %v", err) + return nil, errorsmod.Wrap(types.ErrWasmContractCallFailed, res.Err) } - return result, nil + return res.Ok, nil } // WasmSudo calls the contract with the given payload and returns the result. @@ -69,46 +63,40 @@ func WasmQuery[T types.ContractResult](ctx sdk.Context, k Keeper, clientID strin // - the response of the contract call contains non-empty events // - the response of the contract call contains non-empty attributes // - the data bytes of the response cannot be unmarshaled into the result type -func WasmSudo[T types.ContractResult](ctx sdk.Context, k Keeper, clientID string, clientStore storetypes.KVStore, cs *types.ClientState, payload types.SudoMsg) (T, error) { - var result T - +func (k Keeper) WasmSudo(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, cs *types.ClientState, payload types.SudoMsg) ([]byte, error) { encodedData, err := json.Marshal(payload) if err != nil { - return result, errorsmod.Wrap(err, "failed to marshal payload for wasm execution") + return nil, errorsmod.Wrap(err, "failed to marshal payload for wasm execution") } checksum := cs.Checksum res, err := k.callContract(ctx, clientID, clientStore, checksum, encodedData) if err != nil { - return result, errorsmod.Wrap(types.ErrVMError, err.Error()) + return nil, errorsmod.Wrap(types.ErrVMError, err.Error()) } if res.Err != "" { - return result, errorsmod.Wrap(types.ErrWasmContractCallFailed, res.Err) + return nil, errorsmod.Wrap(types.ErrWasmContractCallFailed, res.Err) } if err = checkResponse(res.Ok); err != nil { - return result, errorsmod.Wrapf(err, "checksum (%s)", hex.EncodeToString(cs.Checksum)) - } - - if err := json.Unmarshal(res.Ok.Data, &result); err != nil { - return result, errorsmod.Wrap(types.ErrWasmInvalidResponseData, err.Error()) + return nil, errorsmod.Wrapf(err, "checksum (%s)", hex.EncodeToString(cs.Checksum)) } newClientState, err := validatePostExecutionClientState(clientStore, k.Codec()) if err != nil { - return result, err + return nil, err } // Checksum should only be able to be modified during migration. if !bytes.Equal(checksum, newClientState.Checksum) { - return result, errorsmod.Wrapf(types.ErrWasmInvalidContractModification, "expected checksum %s, got %s", hex.EncodeToString(checksum), hex.EncodeToString(newClientState.Checksum)) + return nil, errorsmod.Wrapf(types.ErrWasmInvalidContractModification, "expected checksum %s, got %s", hex.EncodeToString(checksum), hex.EncodeToString(newClientState.Checksum)) } - return result, nil + return res.Ok.Data, nil } // WasmInstantiate accepts a message to instantiate a wasm contract, JSON encodes it and calls instantiateContract. -func WasmInstantiate(ctx sdk.Context, k Keeper, clientID string, clientStore storetypes.KVStore, cs *types.ClientState, payload types.InstantiateMessage) error { +func (k Keeper) WasmInstantiate(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, cs *types.ClientState, payload types.InstantiateMessage) error { encodedData, err := json.Marshal(payload) if err != nil { return errorsmod.Wrap(err, "failed to marshal payload for wasm contract instantiation") @@ -143,8 +131,8 @@ func WasmInstantiate(ctx sdk.Context, k Keeper, clientID string, clientStore sto // WasmMigrate migrate calls the migrate entry point of the contract with the given payload and returns the result. // WasmMigrate returns an error if: // - the contract migration returns an error -func WasmMigrate(ctx sdk.Context, keeper Keeper, clientStore storetypes.KVStore, cs *types.ClientState, clientID string, payload []byte) error { - res, err := keeper.migrateContract(ctx, clientID, clientStore, cs.Checksum, payload) +func (k Keeper) WasmMigrate(ctx sdk.Context, clientStore storetypes.KVStore, cs *types.ClientState, clientID string, payload []byte) error { + res, err := k.migrateContract(ctx, clientID, clientStore, cs.Checksum, payload) if err != nil { return errorsmod.Wrap(types.ErrVMError, err.Error()) } @@ -156,7 +144,7 @@ func WasmMigrate(ctx sdk.Context, keeper Keeper, clientStore storetypes.KVStore, return errorsmod.Wrapf(err, "checksum (%s)", hex.EncodeToString(cs.Checksum)) } - _, err = validatePostExecutionClientState(clientStore, keeper.cdc) + _, err = validatePostExecutionClientState(clientStore, k.cdc) return err } @@ -171,6 +159,7 @@ func (k Keeper) migrateContract(ctx sdk.Context, clientID string, clientStore st ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: migrate") resp, gasUsed, err := k.GetVM().Migrate(checksum, env, msg, types.NewStoreAdapter(clientStore), wasmvmAPI, k.newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization) VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) + return resp, err } @@ -185,6 +174,7 @@ func (k Keeper) queryContract(ctx sdk.Context, clientID string, clientStore stor ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: query") resp, gasUsed, err := k.GetVM().Query(checksum, env, msg, types.NewStoreAdapter(clientStore), wasmvmAPI, k.newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization) VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) + return resp, err } diff --git a/modules/light-clients/08-wasm/keeper/contract_keeper_test.go b/modules/light-clients/08-wasm/keeper/contract_keeper_test.go index 754a035c092..f13d10c349d 100644 --- a/modules/light-clients/08-wasm/keeper/contract_keeper_test.go +++ b/modules/light-clients/08-wasm/keeper/contract_keeper_test.go @@ -6,7 +6,6 @@ import ( wasmvm "github.com/CosmWasm/wasmvm/v2" wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" - wasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper" wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" @@ -178,7 +177,7 @@ func (suite *KeeperTestSuite) TestWasmInstantiate() { clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), defaultWasmClientID) wasmClientKeeper := GetSimApp(suite.chainA).WasmClientKeeper - err := wasmkeeper.WasmInstantiate(suite.chainA.GetContext(), wasmClientKeeper, defaultWasmClientID, clientStore, &types.ClientState{Checksum: checksum}, initMsg) + err := wasmClientKeeper.WasmInstantiate(suite.chainA.GetContext(), defaultWasmClientID, clientStore, &types.ClientState{Checksum: checksum}, initMsg) expPass := tc.expError == nil if expPass { @@ -318,7 +317,7 @@ func (suite *KeeperTestSuite) TestWasmMigrate() { clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), defaultWasmClientID) wasmClientKeeper := GetSimApp(suite.chainA).WasmClientKeeper - err = wasmkeeper.WasmMigrate(suite.chainA.GetContext(), wasmClientKeeper, clientStore, &types.ClientState{}, defaultWasmClientID, []byte("{}")) + err = wasmClientKeeper.WasmMigrate(suite.chainA.GetContext(), clientStore, &types.ClientState{}, defaultWasmClientID, []byte("{}")) expPass := tc.expError == nil if expPass { @@ -373,15 +372,18 @@ func (suite *KeeperTestSuite) TestWasmQuery() { }, types.ErrWasmContractCallFailed, }, - { - "failure: response fails to unmarshal", - func() { - suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { - return &wasmvmtypes.QueryResult{Ok: []byte("invalid json")}, wasmtesting.DefaultGasUsed, nil - }) - }, - types.ErrWasmInvalidResponseData, - }, + // TODO(jim): Moved to light client module. + /* + { + "failure: response fails to unmarshal", + func() { + suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + return &wasmvmtypes.QueryResult{Ok: []byte("invalid json")}, wasmtesting.DefaultGasUsed, nil + }) + }, + types.ErrWasmInvalidResponseData, + }, + */ } for _, tc := range testCases { @@ -405,7 +407,7 @@ func (suite *KeeperTestSuite) TestWasmQuery() { tc.malleate() wasmClientKeeper := GetSimApp(suite.chainA).WasmClientKeeper - res, err := wasmkeeper.WasmQuery[types.StatusResult](suite.chainA.GetContext(), wasmClientKeeper, endpoint.ClientID, clientStore, wasmClientState, payload) + res, err := wasmClientKeeper.WasmQuery(suite.chainA.GetContext(), endpoint.ClientID, clientStore, wasmClientState, payload) expPass := tc.expError == nil if expPass { @@ -494,15 +496,18 @@ func (suite *KeeperTestSuite) TestWasmSudo() { }, types.ErrWasmAttributesNotAllowed, }, - { - "failure: response fails to unmarshal", - func() { - suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: []byte("invalid json")}}, wasmtesting.DefaultGasUsed, nil - }) + // TODO(jim): Moved to light client module. + /* + { + "failure: response fails to unmarshal", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: []byte("invalid json")}}, wasmtesting.DefaultGasUsed, nil + }) + }, + types.ErrWasmInvalidResponseData, }, - types.ErrWasmInvalidResponseData, - }, + */ { "failure: invalid clientstate type", func() { @@ -585,7 +590,7 @@ func (suite *KeeperTestSuite) TestWasmSudo() { tc.malleate() wasmClientKeeper := GetSimApp(suite.chainA).WasmClientKeeper - res, err := wasmkeeper.WasmSudo[types.UpdateStateResult](suite.chainA.GetContext(), wasmClientKeeper, endpoint.ClientID, clientStore, wasmClientState, payload) + res, err := wasmClientKeeper.WasmSudo(suite.chainA.GetContext(), endpoint.ClientID, clientStore, wasmClientState, payload) expPass := tc.expError == nil if expPass { diff --git a/modules/light-clients/08-wasm/keeper/keeper.go b/modules/light-clients/08-wasm/keeper/keeper.go index c28d28ece75..b6cdea9cf04 100644 --- a/modules/light-clients/08-wasm/keeper/keeper.go +++ b/modules/light-clients/08-wasm/keeper/keeper.go @@ -165,7 +165,7 @@ func (k Keeper) migrateContractCode(ctx sdk.Context, clientID string, newChecksu // persisted to the client store. clientState.Checksum = newChecksum - err = WasmMigrate(ctx, k, clientStore, clientState, clientID, migrateMsg) + err = k.WasmMigrate(ctx, clientStore, clientState, clientID, migrateMsg) if err != nil { return err } diff --git a/modules/light-clients/08-wasm/keeper/querier_test.go b/modules/light-clients/08-wasm/keeper/querier_test.go index 95991c03d58..f909c12f996 100644 --- a/modules/light-clients/08-wasm/keeper/querier_test.go +++ b/modules/light-clients/08-wasm/keeper/querier_test.go @@ -117,14 +117,14 @@ func (suite *KeeperTestSuite) TestCustomQuery() { clientState, ok := endpoint.GetClientState().(*types.ClientState) suite.Require().True(ok) - res, err := keeper.WasmQuery[types.StatusResult](suite.chainA.GetContext(), wasmKeeper, endpoint.ClientID, clientStore, clientState, types.QueryMsg{Status: &types.StatusMsg{}}) + res, err := wasmKeeper.WasmQuery(suite.chainA.GetContext(), endpoint.ClientID, clientStore, clientState, types.QueryMsg{Status: &types.StatusMsg{}}) expPass := tc.expError == nil if expPass { suite.Require().Nil(err) suite.Require().NotNil(res) } else { - suite.Require().Equal(res.Status, "") + suite.Require().Nil(res) suite.Require().ErrorIs(err, tc.expError) } @@ -328,7 +328,8 @@ func (suite *KeeperTestSuite) TestStargateQuery() { // NOTE: we register query callbacks against: types.TimestampAtHeightMsg{} // in practise, this can against any client state msg, however registering against types.StatusMsg{} introduces recursive loops // due to test case: "success: verify membership query" - _, err = keeper.WasmQuery[types.TimestampAtHeightResult](suite.chainA.GetContext(), wasmKeeper, endpoint.ClientID, clientStore, clientState, payload) + // TODO(Jim): Sanity check that it unmarshals correctly? + _, err = wasmKeeper.WasmQuery(suite.chainA.GetContext(), endpoint.ClientID, clientStore, clientState, payload) expPass := tc.expError == nil if expPass { diff --git a/modules/light-clients/08-wasm/light_client_module.go b/modules/light-clients/08-wasm/light_client_module.go index fea19ac1a14..7e31029d541 100644 --- a/modules/light-clients/08-wasm/light_client_module.go +++ b/modules/light-clients/08-wasm/light_client_module.go @@ -3,6 +3,7 @@ package wasm import ( "bytes" "encoding/hex" + "encoding/json" "fmt" errorsmod "cosmossdk.io/errors" @@ -76,7 +77,7 @@ func (l LightClientModule) Initialize(ctx sdk.Context, clientID string, clientSt Checksum: clientState.Checksum, } - return wasmkeeper.WasmInstantiate(ctx, l.keeper, clientID, clientStore, &clientState, payload) + return l.keeper.WasmInstantiate(ctx, clientID, clientStore, &clientState, payload) } // VerifyClientMessage obtains the client state associated with the client identifier, it then must verify the ClientMessage. @@ -103,7 +104,7 @@ func (l LightClientModule) VerifyClientMessage(ctx sdk.Context, clientID string, payload := types.QueryMsg{ VerifyClientMessage: &types.VerifyClientMessageMsg{ClientMessage: clientMessage.Data}, } - _, err := wasmkeeper.WasmQuery[types.EmptyResult](ctx, l.keeper, clientID, clientStore, clientState, payload) + _, err := l.keeper.WasmQuery(ctx, clientID, clientStore, clientState, payload) return err } @@ -129,11 +130,16 @@ func (l LightClientModule) CheckForMisbehaviour(ctx sdk.Context, clientID string CheckForMisbehaviour: &types.CheckForMisbehaviourMsg{ClientMessage: clientMessage.Data}, } - result, err := wasmkeeper.WasmQuery[types.CheckForMisbehaviourResult](ctx, l.keeper, clientID, clientStore, clientState, payload) + res, err := l.keeper.WasmQuery(ctx, clientID, clientStore, clientState, payload) if err != nil { return false } + var result types.CheckForMisbehaviourResult + if err := json.Unmarshal(res, &result); err != nil { + return false + } + return result.FoundMisbehaviour } @@ -160,7 +166,7 @@ func (l LightClientModule) UpdateStateOnMisbehaviour(ctx sdk.Context, clientID s UpdateStateOnMisbehaviour: &types.UpdateStateOnMisbehaviourMsg{ClientMessage: clientMessage.Data}, } - _, err := wasmkeeper.WasmSudo[types.EmptyResult](ctx, l.keeper, clientID, clientStore, clientState, payload) + _, err := l.keeper.WasmSudo(ctx, clientID, clientStore, clientState, payload) if err != nil { panic(err) } @@ -188,11 +194,16 @@ func (l LightClientModule) UpdateState(ctx sdk.Context, clientID string, clientM UpdateState: &types.UpdateStateMsg{ClientMessage: clientMessage.Data}, } - result, err := wasmkeeper.WasmSudo[types.UpdateStateResult](ctx, l.keeper, clientID, clientStore, clientState, payload) + res, err := l.keeper.WasmSudo(ctx, clientID, clientStore, clientState, payload) if err != nil { panic(err) } + var result types.UpdateStateResult + if err := json.Unmarshal(res, &result); err != nil { + panic(errorsmod.Wrap(types.ErrWasmInvalidResponseData, err.Error())) + } + heights := []exported.Height{} for _, height := range result.Heights { heights = append(heights, height) @@ -252,7 +263,7 @@ func (l LightClientModule) VerifyMembership( Value: value, }, } - _, err := wasmkeeper.WasmSudo[types.EmptyResult](ctx, l.keeper, clientID, clientStore, clientState, payload) + _, err := l.keeper.WasmSudo(ctx, clientID, clientStore, clientState, payload) return err } @@ -305,7 +316,7 @@ func (l LightClientModule) VerifyNonMembership( Path: merklePath, }, } - _, err := wasmkeeper.WasmSudo[types.EmptyResult](ctx, l.keeper, clientID, clientStore, clientState, payload) + _, err := l.keeper.WasmSudo(ctx, clientID, clientStore, clientState, payload) return err } @@ -336,11 +347,16 @@ func (l LightClientModule) Status(ctx sdk.Context, clientID string) exported.Sta } payload := types.QueryMsg{Status: &types.StatusMsg{}} - result, err := wasmkeeper.WasmQuery[types.StatusResult](ctx, l.keeper, clientID, clientStore, clientState, payload) + res, err := l.keeper.WasmQuery(ctx, clientID, clientStore, clientState, payload) if err != nil { return exported.Unknown } + var result types.StatusResult + if err := json.Unmarshal(res, &result); err != nil { + return exported.Unknown + } + return exported.Status(result.Status) } @@ -384,11 +400,16 @@ func (l LightClientModule) TimestampAtHeight(ctx sdk.Context, clientID string, h }, } - result, err := wasmkeeper.WasmQuery[types.TimestampAtHeightResult](ctx, l.keeper, clientID, clientStore, clientState, payload) + res, err := l.keeper.WasmQuery(ctx, clientID, clientStore, clientState, payload) if err != nil { return 0, errorsmod.Wrapf(err, "height (%s)", height) } + var result types.TimestampAtHeightResult + if err := json.Unmarshal(res, &result); err != nil { + return 0, errorsmod.Wrapf(types.ErrWasmInvalidResponseData, "failed to unmarshal result of wasm query: %v", err) + } + return result.Timestamp, nil } @@ -436,7 +457,7 @@ func (l LightClientModule) RecoverClient(ctx sdk.Context, clientID, substituteCl MigrateClientStore: &types.MigrateClientStoreMsg{}, } - _, err = wasmkeeper.WasmSudo[types.EmptyResult](ctx, l.keeper, clientID, store, clientState, payload) + _, err = l.keeper.WasmSudo(ctx, clientID, store, clientState, payload) return err } @@ -486,6 +507,6 @@ func (l LightClientModule) VerifyUpgradeAndUpdateState( }, } - _, err := wasmkeeper.WasmSudo[types.EmptyResult](ctx, l.keeper, clientID, clientStore, clientState, payload) + _, err := l.keeper.WasmSudo(ctx, clientID, clientStore, clientState, payload) return err } From 2aea36fc8fcff90af09e9923130524abd8ae9263 Mon Sep 17 00:00:00 2001 From: DimitrisJim Date: Mon, 8 Apr 2024 18:32:43 +0300 Subject: [PATCH 20/24] chore: address some testing todos. Move testing unmarshal failure case to light client tests. --- docs/architecture/adr-027-ibc-wasm.md | 2 +- .../08-wasm/keeper/contract_keeper.go | 2 - .../08-wasm/keeper/contract_keeper_test.go | 24 ------------ .../08-wasm/light_client_module_test.go | 38 +++++++++++++++++++ 4 files changed, 39 insertions(+), 27 deletions(-) diff --git a/docs/architecture/adr-027-ibc-wasm.md b/docs/architecture/adr-027-ibc-wasm.md index 3c81796d675..1a2a1e8a8a9 100644 --- a/docs/architecture/adr-027-ibc-wasm.md +++ b/docs/architecture/adr-027-ibc-wasm.md @@ -139,7 +139,7 @@ func (cs ClientState) VerifyClientMessage( payload := QueryMsg{ VerifyClientMessage: &VerifyClientMessageMsg{ClientMessage: clientMessage.Data}, } - _, err := WasmQuery[EmptyResult](ctx, clientStore, &cs, payload) + _, err := wasmQuery[EmptyResult](ctx, clientStore, &cs, payload) return err } ``` diff --git a/modules/light-clients/08-wasm/keeper/contract_keeper.go b/modules/light-clients/08-wasm/keeper/contract_keeper.go index 77fbeba0e44..e9503fd80d3 100644 --- a/modules/light-clients/08-wasm/keeper/contract_keeper.go +++ b/modules/light-clients/08-wasm/keeper/contract_keeper.go @@ -35,7 +35,6 @@ var ( // WasmQuery queries the contract with the given payload and returns the result. // WasmQuery returns an error if: -// - the payload cannot be marshaled to JSON // - the contract query returns an error // - the data bytes of the response cannot be unmarshal into the result type func (k Keeper) WasmQuery(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, cs *types.ClientState, payload types.QueryMsg) ([]byte, error) { @@ -57,7 +56,6 @@ func (k Keeper) WasmQuery(ctx sdk.Context, clientID string, clientStore storetyp // WasmSudo calls the contract with the given payload and returns the result. // WasmSudo returns an error if: -// - the payload cannot be marshaled to JSON // - the contract call returns an error // - the response of the contract call contains non-empty messages // - the response of the contract call contains non-empty events diff --git a/modules/light-clients/08-wasm/keeper/contract_keeper_test.go b/modules/light-clients/08-wasm/keeper/contract_keeper_test.go index f13d10c349d..0727842b1a8 100644 --- a/modules/light-clients/08-wasm/keeper/contract_keeper_test.go +++ b/modules/light-clients/08-wasm/keeper/contract_keeper_test.go @@ -372,18 +372,6 @@ func (suite *KeeperTestSuite) TestWasmQuery() { }, types.ErrWasmContractCallFailed, }, - // TODO(jim): Moved to light client module. - /* - { - "failure: response fails to unmarshal", - func() { - suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { - return &wasmvmtypes.QueryResult{Ok: []byte("invalid json")}, wasmtesting.DefaultGasUsed, nil - }) - }, - types.ErrWasmInvalidResponseData, - }, - */ } for _, tc := range testCases { @@ -496,18 +484,6 @@ func (suite *KeeperTestSuite) TestWasmSudo() { }, types.ErrWasmAttributesNotAllowed, }, - // TODO(jim): Moved to light client module. - /* - { - "failure: response fails to unmarshal", - func() { - suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { - return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: []byte("invalid json")}}, wasmtesting.DefaultGasUsed, nil - }) - }, - types.ErrWasmInvalidResponseData, - }, - */ { "failure: invalid clientstate type", func() { diff --git a/modules/light-clients/08-wasm/light_client_module_test.go b/modules/light-clients/08-wasm/light_client_module_test.go index 4b6f00628bc..50c9b8a4dc7 100644 --- a/modules/light-clients/08-wasm/light_client_module_test.go +++ b/modules/light-clients/08-wasm/light_client_module_test.go @@ -91,6 +91,15 @@ func (suite *WasmTestSuite) TestStatus() { }, exported.Unknown, }, + { + "failure: response fails to unmarshal", + func() { + suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + return &wasmvmtypes.QueryResult{Ok: []byte("invalid json")}, wasmtesting.DefaultGasUsed, nil + }) + }, + exported.Unknown, + }, } for _, tc := range testCases { @@ -180,6 +189,15 @@ func (suite *WasmTestSuite) TestTimestampAtHeight() { }, clienttypes.ErrClientNotFound, }, + { + "failure: response fails to unmarshal", + func() { + suite.mockVM.RegisterQueryCallback(types.TimestampAtHeightMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + return &wasmvmtypes.QueryResult{Ok: []byte("invalid json")}, wasmtesting.DefaultGasUsed, nil + }) + }, + types.ErrWasmInvalidResponseData, + }, } for _, tc := range testCases { @@ -1009,6 +1027,16 @@ func (suite *WasmTestSuite) TestCheckForMisbehaviour() { false, // not applicable fmt.Errorf("%s: %s", unusedWasmClientID, clienttypes.ErrClientNotFound), }, + { + "failure: response fails to unmarshal", + func() { + suite.mockVM.RegisterQueryCallback(types.CheckForMisbehaviourMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + return &wasmvmtypes.QueryResult{Ok: []byte("invalid json")}, wasmtesting.DefaultGasUsed, nil + }) + }, + false, + nil, + }, } for _, tc := range testCases { @@ -1151,6 +1179,16 @@ func (suite *WasmTestSuite) TestUpdateState() { errorsmod.Wrap(types.ErrVMError, wasmtesting.ErrMockVM.Error()), nil, }, + { + "failure: response fails to unmarshal", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: []byte("invalid json")}}, wasmtesting.DefaultGasUsed, nil + }) + }, + fmt.Errorf("invalid character 'i' looking for beginning of value: %s", types.ErrWasmInvalidResponseData), + nil, + }, { "failure: callbackFn returns error", func() { From 23decca51e3d7a889c4e56564570fcbdc50b120e Mon Sep 17 00:00:00 2001 From: DimitrisJim Date: Mon, 8 Apr 2024 18:56:25 +0300 Subject: [PATCH 21/24] chore: additional tiny nits. - Consistently use wasmClientKeeper as local var name. - Reorg keeper fields slightly. - Rm 'vm.go' which was already empty. --- modules/light-clients/08-wasm/keeper/keeper.go | 12 ++++-------- .../08-wasm/keeper/keeper_test.go | 18 +++++++++--------- .../light-clients/08-wasm/keeper/keeper_vm.go | 3 +-- .../08-wasm/keeper/migrations_test.go | 6 +++--- .../08-wasm/keeper/querier_test.go | 16 ++++++++-------- .../08-wasm/light_client_module_test.go | 4 ++-- modules/light-clients/08-wasm/types/vm.go | 1 - 7 files changed, 27 insertions(+), 33 deletions(-) delete mode 100644 modules/light-clients/08-wasm/types/vm.go diff --git a/modules/light-clients/08-wasm/keeper/keeper.go b/modules/light-clients/08-wasm/keeper/keeper.go index b6cdea9cf04..be5861bcaab 100644 --- a/modules/light-clients/08-wasm/keeper/keeper.go +++ b/modules/light-clients/08-wasm/keeper/keeper.go @@ -25,22 +25,18 @@ import ( type Keeper struct { // implements gRPC QueryServer interface types.QueryServer - // vm contains an implementation of the WasmEngine that's invoked. - vm ibcwasm.WasmEngine - cdc codec.BinaryCodec - + // vm contains an implementation of the WasmEngine + vm ibcwasm.WasmEngine // state management schema collections.Schema checksums collections.KeySet[[]byte] storeService store.KVStoreService - // handling queries // TODO(jim): We had a reason we didn't call this interface QueryHanlder or something? Should we rename it to QueryHandler? queryPlugins QueryPlugins - + cdc codec.BinaryCodec clientKeeper types.ClientKeeper - - authority string + authority string } // Codec returns the 08-wasm module's codec. diff --git a/modules/light-clients/08-wasm/keeper/keeper_test.go b/modules/light-clients/08-wasm/keeper/keeper_test.go index bdea3fc8841..90f6d2b5223 100644 --- a/modules/light-clients/08-wasm/keeper/keeper_test.go +++ b/modules/light-clients/08-wasm/keeper/keeper_test.go @@ -410,8 +410,8 @@ func (suite *KeeperTestSuite) TestMigrateContract() { newHash, err = types.CreateChecksum(wasmtesting.CreateMockContract([]byte{1, 2, 3})) suite.Require().NoError(err) - wasmKeeper := GetSimApp(suite.chainA).WasmClientKeeper - err = wasmKeeper.GetChecksums().Set(suite.chainA.GetContext(), newHash) + wasmClientKeeper := GetSimApp(suite.chainA).WasmClientKeeper + err = wasmClientKeeper.GetChecksums().Set(suite.chainA.GetContext(), newHash) suite.Require().NoError(err) endpointA := wasmtesting.NewWasmEndpoint(suite.chainA) @@ -425,7 +425,7 @@ func (suite *KeeperTestSuite) TestMigrateContract() { tc.malleate() - err = wasmKeeper.MigrateContractCode(suite.chainA.GetContext(), endpointA.ClientID, newHash, payload) + err = wasmClientKeeper.MigrateContractCode(suite.chainA.GetContext(), endpointA.ClientID, newHash, payload) // updated client state clientState, ok = endpointA.GetClientState().(*types.ClientState) @@ -500,25 +500,25 @@ func (suite *KeeperTestSuite) TestAddChecksum() { suite.SetupWasmWithMockVM() storeWasmCode(suite, wasmtesting.Code) - wasmKeeper := GetSimApp(suite.chainA).WasmClientKeeper + wasmClientKeeper := GetSimApp(suite.chainA).WasmClientKeeper - checksums, err := wasmKeeper.GetAllChecksums(suite.chainA.GetContext()) + checksums, err := wasmClientKeeper.GetAllChecksums(suite.chainA.GetContext()) suite.Require().NoError(err) // default mock vm contract is stored suite.Require().Len(checksums, 1) checksum1 := types.Checksum("checksum1") checksum2 := types.Checksum("checksum2") - err = wasmKeeper.GetChecksums().Set(suite.chainA.GetContext(), checksum1) + err = wasmClientKeeper.GetChecksums().Set(suite.chainA.GetContext(), checksum1) suite.Require().NoError(err) - err = wasmKeeper.GetChecksums().Set(suite.chainA.GetContext(), checksum2) + err = wasmClientKeeper.GetChecksums().Set(suite.chainA.GetContext(), checksum2) suite.Require().NoError(err) // Test adding the same checksum twice - err = wasmKeeper.GetChecksums().Set(suite.chainA.GetContext(), checksum1) + err = wasmClientKeeper.GetChecksums().Set(suite.chainA.GetContext(), checksum1) suite.Require().NoError(err) - checksums, err = wasmKeeper.GetAllChecksums(suite.chainA.GetContext()) + checksums, err = wasmClientKeeper.GetAllChecksums(suite.chainA.GetContext()) suite.Require().NoError(err) suite.Require().Len(checksums, 3) suite.Require().Contains(checksums, checksum1) diff --git a/modules/light-clients/08-wasm/keeper/keeper_vm.go b/modules/light-clients/08-wasm/keeper/keeper_vm.go index 8307d6e6738..16829c24b64 100644 --- a/modules/light-clients/08-wasm/keeper/keeper_vm.go +++ b/modules/light-clients/08-wasm/keeper/keeper_vm.go @@ -71,9 +71,8 @@ func NewKeeperWithVM( // set query plugins to ensure there is a non-nil query plugin // regardless of what options the user provides - handler := NewDefaultQueryPlugins(queryRouter) + keeper.SetQueryPlugins(NewDefaultQueryPlugins(queryRouter)) - keeper.SetQueryPlugins(handler) for _, opt := range opts { opt.apply(keeper) } diff --git a/modules/light-clients/08-wasm/keeper/migrations_test.go b/modules/light-clients/08-wasm/keeper/migrations_test.go index 27f16495a86..8735b6bb11e 100644 --- a/modules/light-clients/08-wasm/keeper/migrations_test.go +++ b/modules/light-clients/08-wasm/keeper/migrations_test.go @@ -28,15 +28,15 @@ func (suite *KeeperTestSuite) TestMigrateWasmStore() { suite.storeChecksums(tc.checksums) // run the migration - wasmKeeper := GetSimApp(suite.chainA).WasmClientKeeper - m := keeper.NewMigrator(wasmKeeper) + wasmClientKeeper := GetSimApp(suite.chainA).WasmClientKeeper + m := keeper.NewMigrator(wasmClientKeeper) err := m.MigrateChecksums(suite.chainA.GetContext()) suite.Require().NoError(err) // check that they were stored in KeySet for _, hash := range tc.checksums { - suite.Require().True(wasmKeeper.GetChecksums().Has(suite.chainA.GetContext(), hash)) + suite.Require().True(wasmClientKeeper.GetChecksums().Has(suite.chainA.GetContext(), hash)) } // check that the data under the old key was deleted diff --git a/modules/light-clients/08-wasm/keeper/querier_test.go b/modules/light-clients/08-wasm/keeper/querier_test.go index f909c12f996..41a69b77e82 100644 --- a/modules/light-clients/08-wasm/keeper/querier_test.go +++ b/modules/light-clients/08-wasm/keeper/querier_test.go @@ -109,15 +109,15 @@ func (suite *KeeperTestSuite) TestCustomQuery() { err := endpoint.CreateClient() suite.Require().NoError(err) - wasmKeeper := GetSimApp(suite.chainA).WasmClientKeeper + wasmClientKeeper := GetSimApp(suite.chainA).WasmClientKeeper - tc.malleate(&wasmKeeper) + tc.malleate(&wasmClientKeeper) clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpoint.ClientID) clientState, ok := endpoint.GetClientState().(*types.ClientState) suite.Require().True(ok) - res, err := wasmKeeper.WasmQuery(suite.chainA.GetContext(), endpoint.ClientID, clientStore, clientState, types.QueryMsg{Status: &types.StatusMsg{}}) + res, err := wasmClientKeeper.WasmQuery(suite.chainA.GetContext(), endpoint.ClientID, clientStore, clientState, types.QueryMsg{Status: &types.StatusMsg{}}) expPass := tc.expError == nil if expPass { @@ -129,7 +129,7 @@ func (suite *KeeperTestSuite) TestCustomQuery() { } // reset query plugins after each test - wasmKeeper.SetQueryPlugins(keeper.NewDefaultQueryPlugins(GetSimApp(suite.chainA).GRPCQueryRouter())) + wasmClientKeeper.SetQueryPlugins(keeper.NewDefaultQueryPlugins(GetSimApp(suite.chainA).GRPCQueryRouter())) }) } } @@ -311,9 +311,9 @@ func (suite *KeeperTestSuite) TestStargateQuery() { err := endpoint.CreateClient() suite.Require().NoError(err) - wasmKeeper := GetSimApp(suite.chainA).WasmClientKeeper + wasmClientKeeper := GetSimApp(suite.chainA).WasmClientKeeper - tc.malleate(&wasmKeeper) + tc.malleate(&wasmClientKeeper) payload := types.QueryMsg{ TimestampAtHeight: &types.TimestampAtHeightMsg{ @@ -329,7 +329,7 @@ func (suite *KeeperTestSuite) TestStargateQuery() { // in practise, this can against any client state msg, however registering against types.StatusMsg{} introduces recursive loops // due to test case: "success: verify membership query" // TODO(Jim): Sanity check that it unmarshals correctly? - _, err = wasmKeeper.WasmQuery(suite.chainA.GetContext(), endpoint.ClientID, clientStore, clientState, payload) + _, err = wasmClientKeeper.WasmQuery(suite.chainA.GetContext(), endpoint.ClientID, clientStore, clientState, payload) expPass := tc.expError == nil if expPass { @@ -347,7 +347,7 @@ func (suite *KeeperTestSuite) TestStargateQuery() { } // reset query plugins after each test - wasmKeeper.SetQueryPlugins(keeper.NewDefaultQueryPlugins(GetSimApp(suite.chainA).GRPCQueryRouter())) + wasmClientKeeper.SetQueryPlugins(keeper.NewDefaultQueryPlugins(GetSimApp(suite.chainA).GRPCQueryRouter())) }) } } diff --git a/modules/light-clients/08-wasm/light_client_module_test.go b/modules/light-clients/08-wasm/light_client_module_test.go index 50c9b8a4dc7..12b75d007a1 100644 --- a/modules/light-clients/08-wasm/light_client_module_test.go +++ b/modules/light-clients/08-wasm/light_client_module_test.go @@ -78,8 +78,8 @@ func (suite *WasmTestSuite) TestStatus() { { "client status is unauthorized: checksum is not stored", func() { - keeper := GetSimApp(suite.chainA).WasmClientKeeper - err := keeper.GetChecksums().Remove(suite.chainA.GetContext(), suite.checksum) + wasmClientKeeper := GetSimApp(suite.chainA).WasmClientKeeper + err := wasmClientKeeper.GetChecksums().Remove(suite.chainA.GetContext(), suite.checksum) suite.Require().NoError(err) }, exported.Unauthorized, diff --git a/modules/light-clients/08-wasm/types/vm.go b/modules/light-clients/08-wasm/types/vm.go deleted file mode 100644 index ab1254f4c2b..00000000000 --- a/modules/light-clients/08-wasm/types/vm.go +++ /dev/null @@ -1 +0,0 @@ -package types From f23bc7c9d85831f1074da2ea99a26c1ffad1ff17 Mon Sep 17 00:00:00 2001 From: DimitrisJim Date: Tue, 9 Apr 2024 11:11:17 +0300 Subject: [PATCH 22/24] chore: restructure definitions to make diff more readable. --- .../08-wasm/keeper/contract_keeper.go | 200 +++++++++--------- .../light-clients/08-wasm/keeper/keeper.go | 1 - 2 files changed, 100 insertions(+), 101 deletions(-) diff --git a/modules/light-clients/08-wasm/keeper/contract_keeper.go b/modules/light-clients/08-wasm/keeper/contract_keeper.go index e9503fd80d3..0c861b1a77f 100644 --- a/modules/light-clients/08-wasm/keeper/contract_keeper.go +++ b/modules/light-clients/08-wasm/keeper/contract_keeper.go @@ -33,97 +33,139 @@ var ( } ) -// WasmQuery queries the contract with the given payload and returns the result. -// WasmQuery returns an error if: -// - the contract query returns an error -// - the data bytes of the response cannot be unmarshal into the result type -func (k Keeper) WasmQuery(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, cs *types.ClientState, payload types.QueryMsg) ([]byte, error) { - encodedData, err := json.Marshal(payload) - if err != nil { - return nil, errorsmod.Wrap(err, "failed to marshal payload for wasm query") - } +// instantiateContract calls vm.Instantiate with appropriate arguments. +func (k Keeper) instantiateContract(ctx sdk.Context, vm ibcwasm.WasmEngine, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) { + sdkGasMeter := ctx.GasMeter() + multipliedGasMeter := types.NewMultipliedGasMeter(sdkGasMeter, types.VMGasRegister) + gasLimit := VMGasRegister.RuntimeGasForContract(ctx) - res, err := k.queryContract(ctx, clientID, clientStore, cs.Checksum, encodedData) - if err != nil { - return nil, errorsmod.Wrap(types.ErrVMError, err.Error()) - } - if res.Err != "" { - return nil, errorsmod.Wrap(types.ErrWasmContractCallFailed, res.Err) + env := getEnv(ctx, clientID) + + msgInfo := wasmvmtypes.MessageInfo{ + Sender: "", + Funds: nil, } - return res.Ok, nil + ctx.GasMeter().ConsumeGas(types.VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: instantiate") + resp, gasUsed, err := vm.Instantiate(checksum, env, msgInfo, msg, types.NewStoreAdapter(clientStore), wasmvmAPI, k.newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization) + types.VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) + return resp, err } -// WasmSudo calls the contract with the given payload and returns the result. -// WasmSudo returns an error if: -// - the contract call returns an error -// - the response of the contract call contains non-empty messages -// - the response of the contract call contains non-empty events -// - the response of the contract call contains non-empty attributes -// - the data bytes of the response cannot be unmarshaled into the result type -func (k Keeper) WasmSudo(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, cs *types.ClientState, payload types.SudoMsg) ([]byte, error) { +// callContract calls vm.Sudo with internally constructed gas meter and environment. +func (k Keeper) callContract(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) { + sdkGasMeter := ctx.GasMeter() + multipliedGasMeter := types.NewMultipliedGasMeter(sdkGasMeter, VMGasRegister) + gasLimit := VMGasRegister.RuntimeGasForContract(ctx) + + env := getEnv(ctx, clientID) + + ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: sudo") + resp, gasUsed, err := k.GetVM().Sudo(checksum, env, msg, types.NewStoreAdapter(clientStore), wasmvmAPI, k.newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization) + VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) + return resp, err +} + +// queryContract calls vm.Query. +func (k Keeper) queryContract(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.QueryResult, error) { + sdkGasMeter := ctx.GasMeter() + multipliedGasMeter := types.NewMultipliedGasMeter(sdkGasMeter, VMGasRegister) + gasLimit := VMGasRegister.RuntimeGasForContract(ctx) + + env := getEnv(ctx, clientID) + + ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: query") + resp, gasUsed, err := k.GetVM().Query(checksum, env, msg, types.NewStoreAdapter(clientStore), wasmvmAPI, k.newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization) + VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) + + return resp, err +} + +// migrateContract calls vm.Migrate with internally constructed gas meter and environment. +func (k Keeper) migrateContract(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) { + sdkGasMeter := ctx.GasMeter() + multipliedGasMeter := types.NewMultipliedGasMeter(sdkGasMeter, VMGasRegister) + gasLimit := VMGasRegister.RuntimeGasForContract(ctx) + + env := getEnv(ctx, clientID) + + ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: migrate") + resp, gasUsed, err := k.GetVM().Migrate(checksum, env, msg, types.NewStoreAdapter(clientStore), wasmvmAPI, k.newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization) + VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) + + return resp, err +} + +// WasmInstantiate accepts a message to instantiate a wasm contract, JSON encodes it and calls instantiateContract. +func (k Keeper) WasmInstantiate(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, cs *types.ClientState, payload types.InstantiateMessage) error { encodedData, err := json.Marshal(payload) if err != nil { - return nil, errorsmod.Wrap(err, "failed to marshal payload for wasm execution") + return errorsmod.Wrap(err, "failed to marshal payload for wasm contract instantiation") } checksum := cs.Checksum - res, err := k.callContract(ctx, clientID, clientStore, checksum, encodedData) + res, err := k.instantiateContract(ctx, k.GetVM(), clientID, clientStore, checksum, encodedData) if err != nil { - return nil, errorsmod.Wrap(types.ErrVMError, err.Error()) + return errorsmod.Wrap(types.ErrVMError, err.Error()) } if res.Err != "" { - return nil, errorsmod.Wrap(types.ErrWasmContractCallFailed, res.Err) + return errorsmod.Wrap(types.ErrWasmContractCallFailed, res.Err) } if err = checkResponse(res.Ok); err != nil { - return nil, errorsmod.Wrapf(err, "checksum (%s)", hex.EncodeToString(cs.Checksum)) + return errorsmod.Wrapf(err, "checksum (%s)", hex.EncodeToString(cs.Checksum)) } newClientState, err := validatePostExecutionClientState(clientStore, k.Codec()) if err != nil { - return nil, err + return err } // Checksum should only be able to be modified during migration. if !bytes.Equal(checksum, newClientState.Checksum) { - return nil, errorsmod.Wrapf(types.ErrWasmInvalidContractModification, "expected checksum %s, got %s", hex.EncodeToString(checksum), hex.EncodeToString(newClientState.Checksum)) + return errorsmod.Wrapf(types.ErrWasmInvalidContractModification, "expected checksum %s, got %s", hex.EncodeToString(checksum), hex.EncodeToString(newClientState.Checksum)) } - return res.Ok.Data, nil + return nil } -// WasmInstantiate accepts a message to instantiate a wasm contract, JSON encodes it and calls instantiateContract. -func (k Keeper) WasmInstantiate(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, cs *types.ClientState, payload types.InstantiateMessage) error { +// WasmSudo calls the contract with the given payload and returns the result. +// WasmSudo returns an error if: +// - the contract call returns an error +// - the response of the contract call contains non-empty messages +// - the response of the contract call contains non-empty events +// - the response of the contract call contains non-empty attributes +// - the data bytes of the response cannot be unmarshaled into the result type +func (k Keeper) WasmSudo(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, cs *types.ClientState, payload types.SudoMsg) ([]byte, error) { encodedData, err := json.Marshal(payload) if err != nil { - return errorsmod.Wrap(err, "failed to marshal payload for wasm contract instantiation") + return nil, errorsmod.Wrap(err, "failed to marshal payload for wasm execution") } checksum := cs.Checksum - res, err := k.instantiateContract(ctx, k.GetVM(), clientID, clientStore, checksum, encodedData) + res, err := k.callContract(ctx, clientID, clientStore, checksum, encodedData) if err != nil { - return errorsmod.Wrap(types.ErrVMError, err.Error()) + return nil, errorsmod.Wrap(types.ErrVMError, err.Error()) } if res.Err != "" { - return errorsmod.Wrap(types.ErrWasmContractCallFailed, res.Err) + return nil, errorsmod.Wrap(types.ErrWasmContractCallFailed, res.Err) } if err = checkResponse(res.Ok); err != nil { - return errorsmod.Wrapf(err, "checksum (%s)", hex.EncodeToString(cs.Checksum)) + return nil, errorsmod.Wrapf(err, "checksum (%s)", hex.EncodeToString(cs.Checksum)) } newClientState, err := validatePostExecutionClientState(clientStore, k.Codec()) if err != nil { - return err + return nil, err } // Checksum should only be able to be modified during migration. if !bytes.Equal(checksum, newClientState.Checksum) { - return errorsmod.Wrapf(types.ErrWasmInvalidContractModification, "expected checksum %s, got %s", hex.EncodeToString(checksum), hex.EncodeToString(newClientState.Checksum)) + return nil, errorsmod.Wrapf(types.ErrWasmInvalidContractModification, "expected checksum %s, got %s", hex.EncodeToString(checksum), hex.EncodeToString(newClientState.Checksum)) } - return nil + return res.Ok.Data, nil } // WasmMigrate migrate calls the migrate entry point of the contract with the given payload and returns the result. @@ -146,67 +188,25 @@ func (k Keeper) WasmMigrate(ctx sdk.Context, clientStore storetypes.KVStore, cs return err } -// migrateContract calls vm.Migrate with internally constructed gas meter and environment. -func (k Keeper) migrateContract(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) { - sdkGasMeter := ctx.GasMeter() - multipliedGasMeter := types.NewMultipliedGasMeter(sdkGasMeter, VMGasRegister) - gasLimit := VMGasRegister.RuntimeGasForContract(ctx) - - env := getEnv(ctx, clientID) - - ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: migrate") - resp, gasUsed, err := k.GetVM().Migrate(checksum, env, msg, types.NewStoreAdapter(clientStore), wasmvmAPI, k.newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization) - VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) - - return resp, err -} - -// queryContract calls vm.Query. -func (k Keeper) queryContract(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.QueryResult, error) { - sdkGasMeter := ctx.GasMeter() - multipliedGasMeter := types.NewMultipliedGasMeter(sdkGasMeter, VMGasRegister) - gasLimit := VMGasRegister.RuntimeGasForContract(ctx) - - env := getEnv(ctx, clientID) - - ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: query") - resp, gasUsed, err := k.GetVM().Query(checksum, env, msg, types.NewStoreAdapter(clientStore), wasmvmAPI, k.newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization) - VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) - - return resp, err -} - -// callContract calls vm.Sudo with internally constructed gas meter and environment. -func (k Keeper) callContract(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) { - sdkGasMeter := ctx.GasMeter() - multipliedGasMeter := types.NewMultipliedGasMeter(sdkGasMeter, VMGasRegister) - gasLimit := VMGasRegister.RuntimeGasForContract(ctx) - - env := getEnv(ctx, clientID) - - ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: sudo") - resp, gasUsed, err := k.GetVM().Sudo(checksum, env, msg, types.NewStoreAdapter(clientStore), wasmvmAPI, k.newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization) - VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) - return resp, err -} - -// instantiateContract calls vm.Instantiate with appropriate arguments. -func (k Keeper) instantiateContract(ctx sdk.Context, vm ibcwasm.WasmEngine, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) { - sdkGasMeter := ctx.GasMeter() - multipliedGasMeter := types.NewMultipliedGasMeter(sdkGasMeter, types.VMGasRegister) - gasLimit := VMGasRegister.RuntimeGasForContract(ctx) - - env := getEnv(ctx, clientID) +// WasmQuery queries the contract with the given payload and returns the result. +// WasmQuery returns an error if: +// - the contract query returns an error +// - the data bytes of the response cannot be unmarshal into the result type +func (k Keeper) WasmQuery(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, cs *types.ClientState, payload types.QueryMsg) ([]byte, error) { + encodedData, err := json.Marshal(payload) + if err != nil { + return nil, errorsmod.Wrap(err, "failed to marshal payload for wasm query") + } - msgInfo := wasmvmtypes.MessageInfo{ - Sender: "", - Funds: nil, + res, err := k.queryContract(ctx, clientID, clientStore, cs.Checksum, encodedData) + if err != nil { + return nil, errorsmod.Wrap(types.ErrVMError, err.Error()) + } + if res.Err != "" { + return nil, errorsmod.Wrap(types.ErrWasmContractCallFailed, res.Err) } - ctx.GasMeter().ConsumeGas(types.VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: instantiate") - resp, gasUsed, err := vm.Instantiate(checksum, env, msgInfo, msg, types.NewStoreAdapter(clientStore), wasmvmAPI, k.newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization) - types.VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) - return resp, err + return res.Ok, nil } // validatePostExecutionClientState validates that the contract has not many any invalid modifications diff --git a/modules/light-clients/08-wasm/keeper/keeper.go b/modules/light-clients/08-wasm/keeper/keeper.go index be5861bcaab..6fc2d51a7ea 100644 --- a/modules/light-clients/08-wasm/keeper/keeper.go +++ b/modules/light-clients/08-wasm/keeper/keeper.go @@ -32,7 +32,6 @@ type Keeper struct { checksums collections.KeySet[[]byte] storeService store.KVStoreService // handling queries - // TODO(jim): We had a reason we didn't call this interface QueryHanlder or something? Should we rename it to QueryHandler? queryPlugins QueryPlugins cdc codec.BinaryCodec clientKeeper types.ClientKeeper From 28360732bdd80a1ad5acd2f4598a5ea377383c76 Mon Sep 17 00:00:00 2001 From: DimitrisJim Date: Tue, 9 Apr 2024 11:19:01 +0300 Subject: [PATCH 23/24] d'oh: rm vm arg from instantiate. --- .../light-clients/08-wasm/keeper/contract_keeper.go | 7 +++---- modules/light-clients/08-wasm/keeper/options_test.go | 12 ++++++------ modules/light-clients/08-wasm/keeper/querier_test.go | 4 ++-- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/modules/light-clients/08-wasm/keeper/contract_keeper.go b/modules/light-clients/08-wasm/keeper/contract_keeper.go index 0c861b1a77f..9fd16dff6e3 100644 --- a/modules/light-clients/08-wasm/keeper/contract_keeper.go +++ b/modules/light-clients/08-wasm/keeper/contract_keeper.go @@ -15,7 +15,6 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" host "github.com/cosmos/ibc-go/v8/modules/core/24-host" @@ -34,7 +33,7 @@ var ( ) // instantiateContract calls vm.Instantiate with appropriate arguments. -func (k Keeper) instantiateContract(ctx sdk.Context, vm ibcwasm.WasmEngine, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) { +func (k Keeper) instantiateContract(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) { sdkGasMeter := ctx.GasMeter() multipliedGasMeter := types.NewMultipliedGasMeter(sdkGasMeter, types.VMGasRegister) gasLimit := VMGasRegister.RuntimeGasForContract(ctx) @@ -47,7 +46,7 @@ func (k Keeper) instantiateContract(ctx sdk.Context, vm ibcwasm.WasmEngine, clie } ctx.GasMeter().ConsumeGas(types.VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: instantiate") - resp, gasUsed, err := vm.Instantiate(checksum, env, msgInfo, msg, types.NewStoreAdapter(clientStore), wasmvmAPI, k.newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization) + resp, gasUsed, err := k.GetVM().Instantiate(checksum, env, msgInfo, msg, types.NewStoreAdapter(clientStore), wasmvmAPI, k.newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization) types.VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) return resp, err } @@ -104,7 +103,7 @@ func (k Keeper) WasmInstantiate(ctx sdk.Context, clientID string, clientStore st } checksum := cs.Checksum - res, err := k.instantiateContract(ctx, k.GetVM(), clientID, clientStore, checksum, encodedData) + res, err := k.instantiateContract(ctx, clientID, clientStore, checksum, encodedData) if err != nil { return errorsmod.Wrap(types.ErrVMError, err.Error()) } diff --git a/modules/light-clients/08-wasm/keeper/options_test.go b/modules/light-clients/08-wasm/keeper/options_test.go index 8ebcde99352..bec7233df21 100644 --- a/modules/light-clients/08-wasm/keeper/options_test.go +++ b/modules/light-clients/08-wasm/keeper/options_test.go @@ -13,13 +13,13 @@ import ( "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" ) -func MockErrorCustomQuerier() func(sdk.Context, json.RawMessage) ([]byte, error) { +func mockErrorCustomQuerier() func(sdk.Context, json.RawMessage) ([]byte, error) { return func(_ sdk.Context, _ json.RawMessage) ([]byte, error) { return nil, errors.New("custom querier error for TestNewKeeperWithOptions") } } -func MockErrorStargateQuerier() func(sdk.Context, *wasmvmtypes.StargateQuery) ([]byte, error) { +func mockErrorStargateQuerier() func(sdk.Context, *wasmvmtypes.StargateQuery) ([]byte, error) { return func(_ sdk.Context, _ *wasmvmtypes.StargateQuery) ([]byte, error) { return nil, errors.New("stargate querier error for TestNewKeeperWithOptions") } @@ -58,7 +58,7 @@ func (suite *KeeperTestSuite) TestNewKeeperWithOptions() { "success: custom querier", func() { querierOption := keeper.WithQueryPlugins(&keeper.QueryPlugins{ - Custom: MockErrorCustomQuerier(), + Custom: mockErrorCustomQuerier(), }) k = keeper.NewKeeperWithVM( GetSimApp(suite.chainA).AppCodec(), @@ -84,7 +84,7 @@ func (suite *KeeperTestSuite) TestNewKeeperWithOptions() { "success: stargate querier", func() { querierOption := keeper.WithQueryPlugins(&keeper.QueryPlugins{ - Stargate: MockErrorStargateQuerier(), + Stargate: mockErrorStargateQuerier(), }) k = keeper.NewKeeperWithVM( GetSimApp(suite.chainA).AppCodec(), @@ -110,8 +110,8 @@ func (suite *KeeperTestSuite) TestNewKeeperWithOptions() { "success: both queriers", func() { querierOption := keeper.WithQueryPlugins(&keeper.QueryPlugins{ - Custom: MockErrorCustomQuerier(), - Stargate: MockErrorStargateQuerier(), + Custom: mockErrorCustomQuerier(), + Stargate: mockErrorStargateQuerier(), }) k = keeper.NewKeeperWithVM( GetSimApp(suite.chainA).AppCodec(), diff --git a/modules/light-clients/08-wasm/keeper/querier_test.go b/modules/light-clients/08-wasm/keeper/querier_test.go index 41a69b77e82..4af9074c12e 100644 --- a/modules/light-clients/08-wasm/keeper/querier_test.go +++ b/modules/light-clients/08-wasm/keeper/querier_test.go @@ -27,7 +27,7 @@ type QueryEcho struct { Data string `json:"data"` } -func MockCustomQuerier() func(sdk.Context, json.RawMessage) ([]byte, error) { +func mockCustomQuerier() func(sdk.Context, json.RawMessage) ([]byte, error) { return func(ctx sdk.Context, request json.RawMessage) ([]byte, error) { var customQuery CustomQuery err := json.Unmarshal([]byte(request), &customQuery) @@ -54,7 +54,7 @@ func (suite *KeeperTestSuite) TestCustomQuery() { "success: custom query", func(k *keeper.Keeper) { querierPlugin := keeper.QueryPlugins{ - Custom: MockCustomQuerier(), + Custom: mockCustomQuerier(), } k.SetQueryPlugins(querierPlugin) From 76d8055c069345a08a1a516061dd167a78a40f90 Mon Sep 17 00:00:00 2001 From: DimitrisJim Date: Wed, 10 Apr 2024 19:15:34 +0300 Subject: [PATCH 24/24] nit: Space out keeper fields. Remove TODO for method name. --- modules/light-clients/08-wasm/keeper/keeper.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/modules/light-clients/08-wasm/keeper/keeper.go b/modules/light-clients/08-wasm/keeper/keeper.go index 6fc2d51a7ea..70787b21386 100644 --- a/modules/light-clients/08-wasm/keeper/keeper.go +++ b/modules/light-clients/08-wasm/keeper/keeper.go @@ -25,17 +25,19 @@ import ( type Keeper struct { // implements gRPC QueryServer interface types.QueryServer - // vm contains an implementation of the WasmEngine + + cdc codec.BinaryCodec + clientKeeper types.ClientKeeper + vm ibcwasm.WasmEngine - // state management + schema collections.Schema checksums collections.KeySet[[]byte] storeService store.KVStoreService - // handling queries + queryPlugins QueryPlugins - cdc codec.BinaryCodec - clientKeeper types.ClientKeeper - authority string + + authority string } // Codec returns the 08-wasm module's codec. @@ -63,7 +65,6 @@ func (k Keeper) GetVM() ibcwasm.WasmEngine { } // GetChecksums returns the stored checksums. -// TODO(jim): Need a better name here, this + grpc query meth + GetAllChecksums are all too similar. func (k Keeper) GetChecksums() collections.KeySet[[]byte] { return k.checksums }