diff --git a/modules/light-clients/08-wasm/go.mod b/modules/light-clients/08-wasm/go.mod index 0fa6e333618..5e5bcd82ffe 100644 --- a/modules/light-clients/08-wasm/go.mod +++ b/modules/light-clients/08-wasm/go.mod @@ -21,7 +21,7 @@ require ( cosmossdk.io/x/feegrant v0.1.0 cosmossdk.io/x/tx v0.13.1 cosmossdk.io/x/upgrade v0.1.1 - github.com/CosmWasm/wasmvm v1.5.2 + github.com/CosmWasm/wasmvm/v2 v2.0.0-rc.2 github.com/cometbft/cometbft v0.38.5 github.com/cosmos/cosmos-db v1.0.2 github.com/cosmos/cosmos-sdk v0.50.4 diff --git a/modules/light-clients/08-wasm/go.sum b/modules/light-clients/08-wasm/go.sum index 37a18305286..b5506ad75c1 100644 --- a/modules/light-clients/08-wasm/go.sum +++ b/modules/light-clients/08-wasm/go.sum @@ -225,8 +225,8 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25 github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/CosmWasm/wasmvm v1.5.2 h1:+pKB1Mz9GZVt1vadxB+EDdD1FOz3dMNjIKq/58/lrag= -github.com/CosmWasm/wasmvm v1.5.2/go.mod h1:Q0bSEtlktzh7W2hhEaifrFp1Erx11ckQZmjq8FLCyys= +github.com/CosmWasm/wasmvm/v2 v2.0.0-rc.2 h1:2rAO0MpNmKHKsrX/lsyo7lLjKoGDOWwMk+40hTNVfdE= +github.com/CosmWasm/wasmvm/v2 v2.0.0-rc.2/go.mod h1:su9lg5qLr7adV95eOfzjZWkGiky8WNaNIHDr7Fpu7Ck= github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= 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 d67b40ac7fb..8009c98e1d9 100644 --- a/modules/light-clients/08-wasm/internal/ibcwasm/expected_interfaces.go +++ b/modules/light-clients/08-wasm/internal/ibcwasm/expected_interfaces.go @@ -1,8 +1,8 @@ package ibcwasm import ( - wasmvm "github.com/CosmWasm/wasmvm" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" + wasmvm "github.com/CosmWasm/wasmvm/v2" + wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" @@ -16,7 +16,7 @@ type WasmEngine interface { // This must be done one time for given code, after which it can be // instantiated many times, and each instance called many times. // It does the same as StoreCodeUnchecked plus the static checks. - StoreCode(code wasmvm.WasmCode) (wasmvm.Checksum, error) + StoreCode(code wasmvm.WasmCode, gasLimit uint64) (wasmvm.Checksum, uint64, error) // StoreCodeUnchecked will compile the wasm code, and store the resulting pre-compile // as well as the original code. Both can be referenced later via checksum @@ -45,7 +45,7 @@ type WasmEngine interface { gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction, - ) (*wasmvmtypes.Response, uint64, error) + ) (*wasmvmtypes.ContractResult, uint64, error) // Query allows a client to execute a contract-specific query. If the result is not empty, it should be // valid json-encoded data to return to the client. @@ -60,7 +60,7 @@ type WasmEngine interface { gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction, - ) ([]byte, uint64, error) + ) (*wasmvmtypes.QueryResult, uint64, error) // Migrate migrates an existing contract to a new code binary. // This takes storage of the data from the original contract and the checksum of the new contract that should @@ -78,13 +78,13 @@ type WasmEngine interface { gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction, - ) (*wasmvmtypes.Response, uint64, error) + ) (*wasmvmtypes.ContractResult, uint64, error) - // Sudo allows native Go modules to make privileged (sudo) calls on the contract. + // Sudo allows native Go modules to make priviledged (sudo) calls on the contract. // The contract can expose entry points that cannot be triggered by any transaction, but only via // native Go modules, and delegate the access control to the system. // - // These work much like Migrate (same scenario) but allows custom apps to extend the privileged entry points + // These work much like Migrate (same scenario) but allows custom apps to extend the priviledged entry points // without forking cosmwasm-vm. Sudo( checksum wasmvm.Checksum, @@ -96,7 +96,7 @@ type WasmEngine interface { gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction, - ) (*wasmvmtypes.Response, uint64, error) + ) (*wasmvmtypes.ContractResult, uint64, error) // GetCode will load the original wasm code for the given checksum. // This will only succeed if that checksum was previously returned from diff --git a/modules/light-clients/08-wasm/keeper/genesis.go b/modules/light-clients/08-wasm/keeper/genesis.go index 6ab642097fb..909fa7ccef1 100644 --- a/modules/light-clients/08-wasm/keeper/genesis.go +++ b/modules/light-clients/08-wasm/keeper/genesis.go @@ -11,7 +11,7 @@ import ( // state. func (k Keeper) InitGenesis(ctx sdk.Context, gs types.GenesisState) error { for _, contract := range gs.Contracts { - _, err := k.storeWasmCode(ctx, contract.CodeBytes, ibcwasm.GetVM().StoreCodeUnchecked) + _, err := k.storeWasmCodeUncheck(ctx, contract.CodeBytes, ibcwasm.GetVM().StoreCodeUnchecked) if err != nil { return err } diff --git a/modules/light-clients/08-wasm/keeper/keeper.go b/modules/light-clients/08-wasm/keeper/keeper.go index 8676601a230..0331636c43e 100644 --- a/modules/light-clients/08-wasm/keeper/keeper.go +++ b/modules/light-clients/08-wasm/keeper/keeper.go @@ -7,7 +7,7 @@ import ( "fmt" "strings" - wasmvm "github.com/CosmWasm/wasmvm" + wasmvm "github.com/CosmWasm/wasmvm/v2" storetypes "cosmossdk.io/core/store" errorsmod "cosmossdk.io/errors" @@ -94,7 +94,7 @@ func NewKeeperWithConfig( queryRouter ibcwasm.QueryRouter, opts ...Option, ) Keeper { - vm, err := wasmvm.NewVM(wasmConfig.DataDir, wasmConfig.SupportedCapabilities, types.ContractMemoryLimit, wasmConfig.ContractDebugMode, types.MemoryCacheSize) + vm, err := wasmvm.NewVM(wasmConfig.DataDir, strings.Split(wasmConfig.SupportedCapabilities, ","), types.ContractMemoryLimit, wasmConfig.ContractDebugMode, types.MemoryCacheSize) if err != nil { panic(fmt.Errorf("failed to instantiate new Wasm VM instance: %v", err)) } @@ -107,7 +107,59 @@ func (k Keeper) GetAuthority() string { return k.authority } -func (Keeper) storeWasmCode(ctx sdk.Context, code []byte, storeFn func(code wasmvm.WasmCode) (wasmvm.Checksum, error)) ([]byte, error) { +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) { + ctx.GasMeter().ConsumeGas(types.VMGasRegister.UncompressCosts(len(code)), "Uncompress gzip bytecode") + code, err = types.Uncompress(code, types.MaxWasmByteSize()) + if err != nil { + return nil, errorsmod.Wrap(err, "failed to store contract") + } + } + + // run the code through the wasm light client validation process + if err := types.ValidateWasmCode(code); err != nil { + return nil, errorsmod.Wrap(err, "wasm bytecode validation failed") + } + + // Check to see if store already has checksum. + checksum, err := types.CreateChecksum(code) + if err != nil { + return nil, errorsmod.Wrap(err, "wasm bytecode checksum failed") + } + + if types.HasChecksum(ctx, checksum) { + return nil, types.ErrWasmCodeExists + } + + // create the code in the vm + ctx.GasMeter().ConsumeGas(types.VMGasRegister.CompileCosts(len(code)), "Compiling wasm bytecode") + gasLimit := ctx.GasMeter().GasConsumedToLimit() + vmChecksum, _, err := storeFn(code, gasLimit) + if err != nil { + return nil, errorsmod.Wrap(err, "failed to store contract") + } + + // SANITY: We've checked our store, additional safety check to assert that the checksum returned by WasmVM equals checksum generated by us. + if !bytes.Equal(vmChecksum, checksum) { + return nil, errorsmod.Wrapf(types.ErrInvalidChecksum, "expected %s, got %s", hex.EncodeToString(checksum), hex.EncodeToString(vmChecksum)) + } + + // pin the code to the vm in-memory cache + if err := ibcwasm.GetVM().Pin(vmChecksum); err != nil { + return nil, errorsmod.Wrapf(err, "failed to pin contract with checksum (%s) to vm cache", hex.EncodeToString(vmChecksum)) + } + + // store the checksum + err = ibcwasm.Checksums.Set(ctx, checksum) + if err != nil { + return nil, errorsmod.Wrap(err, "failed to store checksum") + } + + return checksum, nil +} + +func (Keeper) storeWasmCodeUncheck(ctx sdk.Context, code []byte, storeFn func(code wasmvm.WasmCode) (wasmvm.Checksum, error)) ([]byte, error) { var err error if types.IsGzip(code) { ctx.GasMeter().ConsumeGas(types.VMGasRegister.UncompressCosts(len(code)), "Uncompress gzip bytecode") diff --git a/modules/light-clients/08-wasm/keeper/keeper_test.go b/modules/light-clients/08-wasm/keeper/keeper_test.go index 73810c74b42..31ae74e3e01 100644 --- a/modules/light-clients/08-wasm/keeper/keeper_test.go +++ b/modules/light-clients/08-wasm/keeper/keeper_test.go @@ -5,8 +5,8 @@ import ( "errors" "testing" - wasmvm "github.com/CosmWasm/wasmvm" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" + wasmvm "github.com/CosmWasm/wasmvm/v2" + wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" dbm "github.com/cosmos/cosmos-db" testifysuite "github.com/stretchr/testify/suite" @@ -79,6 +79,8 @@ func (suite *KeeperTestSuite) SetupWasmWithMockVM() { suite.coordinator = ibctesting.NewCoordinator(suite.T(), 1) suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + + wasmtesting.AllowWasmClients(suite.chainA) } func (suite *KeeperTestSuite) setupWasmWithMockVM() (ibctesting.TestingApp, map[string]json.RawMessage) { 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 ad2b2dce326..e7adeab2482 100644 --- a/modules/light-clients/08-wasm/keeper/msg_server_test.go +++ b/modules/light-clients/08-wasm/keeper/msg_server_test.go @@ -5,8 +5,8 @@ import ( "encoding/json" "errors" - wasmvm "github.com/CosmWasm/wasmvm" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" + wasmvm "github.com/CosmWasm/wasmvm/v2" + wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -65,6 +65,20 @@ func (suite *KeeperTestSuite) TestMsgStoreCode() { }, errors.New("Wasm bytes do not not start with Wasm magic number"), }, + { + "fails with wasm code too large", + func() { + msg = types.NewMsgStoreCode(signer, append(wasmtesting.WasmMagicNumber, []byte(ibctesting.GenerateString(uint(types.MaxWasmByteSize())))...)) + }, + types.ErrWasmCodeTooLarge, + }, + { + "fails with checksum", + func() { + msg = types.NewMsgStoreCode(signer, []byte{0, 1, 3, 4}) + }, + errors.New("Wasm bytes do not not start with Wasm magic number"), + }, { "fails with wasm code too large", func() { @@ -140,7 +154,6 @@ func (suite *KeeperTestSuite) TestMsgMigrateContract() { suite.Require().NoError(err) newByteCode := wasmtesting.CreateMockContract([]byte("MockByteCode-TestMsgMigrateContract")) - govAcc := authtypes.NewModuleAddress(govtypes.ModuleName).String() var ( diff --git a/modules/light-clients/08-wasm/keeper/options_test.go b/modules/light-clients/08-wasm/keeper/options_test.go index 3f4680491ca..61b686927ee 100644 --- a/modules/light-clients/08-wasm/keeper/options_test.go +++ b/modules/light-clients/08-wasm/keeper/options_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "errors" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" + wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" "github.com/cosmos/cosmos-sdk/runtime" sdk "github.com/cosmos/cosmos-sdk/types" diff --git a/modules/light-clients/08-wasm/testing/mock/mock.go b/modules/light-clients/08-wasm/testing/mock/mock.go new file mode 100644 index 00000000000..1a9c6370e46 --- /dev/null +++ b/modules/light-clients/08-wasm/testing/mock/mock.go @@ -0,0 +1,27 @@ +package mock + +import ( + "github.com/cosmos/ibc-go/v8/modules/core/exported" +) + +var _ exported.Path = KeyPath{} + +// KeyPath defines a placeholder struct which implements the exported.Path interface +type KeyPath struct{} + +// String implements the exported.Path interface +func (KeyPath) String() string { + return "" +} + +// Empty implements the exported.Path interface +func (KeyPath) Empty() bool { + return false +} + +var _ exported.Height = Height{} + +// Height defines a placeholder struct which implements the exported.Height interface +type Height struct { + exported.Height +} diff --git a/modules/light-clients/08-wasm/testing/mock_engine.go b/modules/light-clients/08-wasm/testing/mock_engine.go index 4754a907cfa..9a0144fb6c9 100644 --- a/modules/light-clients/08-wasm/testing/mock_engine.go +++ b/modules/light-clients/08-wasm/testing/mock_engine.go @@ -7,8 +7,8 @@ import ( "fmt" "reflect" - wasmvm "github.com/CosmWasm/wasmvm" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" + 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" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" @@ -20,7 +20,7 @@ var ( _ ibcwasm.WasmEngine = (*MockWasmEngine)(nil) // queryTypes contains all the possible query message types. - queryTypes = [...]any{types.StatusMsg{}, types.TimestampAtHeightMsg{}, types.VerifyClientMessageMsg{}, types.CheckForMisbehaviourMsg{}} + queryTypes = [...]any{types.StatusMsg{}, types.ExportMetadataMsg{}, types.TimestampAtHeightMsg{}, types.VerifyClientMessageMsg{}, types.CheckForMisbehaviourMsg{}} // sudoTypes contains all the possible sudo message types. sudoTypes = [...]any{types.UpdateStateMsg{}, types.UpdateStateOnMisbehaviourMsg{}, types.VerifyUpgradeAndUpdateStateMsg{}, types.VerifyMembershipMsg{}, types.VerifyNonMembershipMsg{}, types.MigrateClientStoreMsg{}} @@ -38,7 +38,7 @@ type ( // Without a stub function a panic is thrown. // ref: https://github.com/CosmWasm/wasmd/blob/v0.42.0/x/wasm/keeper/wasmtesting/mock_engine.go#L19 type MockWasmEngine struct { - StoreCodeFn func(code wasmvm.WasmCode) (wasmvm.Checksum, error) + StoreCodeFn func(code wasmvm.WasmCode, gasLimit uint64) (wasmvm.Checksum, uint64, error) StoreCodeUncheckedFn func(code wasmvm.WasmCode) (wasmvm.Checksum, error) InstantiateFn func(checksum wasmvm.Checksum, env wasmvmtypes.Env, info wasmvmtypes.MessageInfo, initMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) MigrateFn func(checksum wasmvm.Checksum, env wasmvmtypes.Env, migrateMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) @@ -81,11 +81,11 @@ func NewMockWasmEngine() *MockWasmEngine { } // Set up default behavior for Store/Pin/Get - m.StoreCodeFn = func(code wasmvm.WasmCode) (wasmvm.Checksum, error) { + m.StoreCodeFn = func(code wasmvm.WasmCode, gasLimit uint64) (wasmvm.Checksum, uint64, error) { checkSum, _ := types.CreateChecksum(code) m.storedContracts[binary.LittleEndian.Uint32(checkSum)] = code - return checkSum, nil + return checkSum, gasLimit, nil } m.StoreCodeUncheckedFn = func(code wasmvm.WasmCode) (wasmvm.Checksum, error) { @@ -133,14 +133,14 @@ func (m *MockWasmEngine) RegisterSudoCallback(sudoMessage any, fn sudoFn) { } // StoreCode implements the WasmEngine interface. -func (m *MockWasmEngine) StoreCode(code wasmvm.WasmCode) (wasmvm.Checksum, error) { +func (m *MockWasmEngine) StoreCode(code wasmvm.WasmCode, gasLimit uint64) (wasmvm.Checksum, uint64, error) { if m.StoreCodeFn == nil { panic(errors.New("mock engine is not properly initialized: StoreCodeFn is nil")) } - return m.StoreCodeFn(code) + return m.StoreCodeFn(code, gasLimit) } -// StoreCodeUnchecked implements the WasmEngine interface. +// StoreCode implements the WasmEngine interface. func (m *MockWasmEngine) StoreCodeUnchecked(code wasmvm.WasmCode) (wasmvm.Checksum, error) { if m.StoreCodeUncheckedFn == nil { panic(errors.New("mock engine is not properly initialized: StoreCodeUncheckedFn is nil")) @@ -149,15 +149,20 @@ func (m *MockWasmEngine) StoreCodeUnchecked(code wasmvm.WasmCode) (wasmvm.Checks } // Instantiate implements the WasmEngine interface. -func (m *MockWasmEngine) Instantiate(checksum wasmvm.Checksum, env wasmvmtypes.Env, info wasmvmtypes.MessageInfo, initMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { +func (m *MockWasmEngine) Instantiate(checksum wasmvm.Checksum, env wasmvmtypes.Env, info wasmvmtypes.MessageInfo, initMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { if m.InstantiateFn == nil { panic(errors.New("mock engine is not properly initialized: InstantiateFn is nil")) } - return m.InstantiateFn(checksum, env, info, initMsg, store, goapi, querier, gasMeter, gasLimit, deserCost) + res, number, err := m.InstantiateFn(checksum, env, info, initMsg, store, goapi, querier, gasMeter, gasLimit, deserCost) + contractResult := wasmvmtypes.ContractResult{ + Ok: res, + Err: err.Error(), + } + return &contractResult, number, err } // Query implements the WasmEngine interface. -func (m *MockWasmEngine) Query(checksum wasmvm.Checksum, env wasmvmtypes.Env, queryMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) ([]byte, uint64, error) { +func (m *MockWasmEngine) Query(checksum wasmvm.Checksum, env wasmvmtypes.Env, queryMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { msgTypeName := getQueryMsgPayloadTypeName(queryMsg) callbackFn, ok := m.queryCallbacks[msgTypeName] @@ -165,19 +170,29 @@ func (m *MockWasmEngine) Query(checksum wasmvm.Checksum, env wasmvmtypes.Env, qu panic(fmt.Errorf("mock engine is not properly initialized: no callback specified for %s", msgTypeName)) } - return callbackFn(checksum, env, queryMsg, store, goapi, querier, gasMeter, gasLimit, deserCost) + res, number, err := callbackFn(checksum, env, queryMsg, store, goapi, querier, gasMeter, gasLimit, deserCost) + contractResult := wasmvmtypes.QueryResult{ + Ok: res, + Err: err.Error(), + } + return &contractResult, number, err } // Migrate implements the WasmEngine interface. -func (m *MockWasmEngine) Migrate(checksum wasmvm.Checksum, env wasmvmtypes.Env, migrateMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { +func (m *MockWasmEngine) Migrate(checksum wasmvm.Checksum, env wasmvmtypes.Env, migrateMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { if m.MigrateFn == nil { panic(errors.New("mock engine is not properly initialized: MigrateFn is nil")) } - return m.MigrateFn(checksum, env, migrateMsg, store, goapi, querier, gasMeter, gasLimit, deserCost) + res, number, err := m.MigrateFn(checksum, env, migrateMsg, store, goapi, querier, gasMeter, gasLimit, deserCost) + contractResult := wasmvmtypes.ContractResult{ + Ok: res, + Err: err.Error(), + } + return &contractResult, number, err } // Sudo implements the WasmEngine interface. -func (m *MockWasmEngine) Sudo(checksum wasmvm.Checksum, env wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { +func (m *MockWasmEngine) Sudo(checksum wasmvm.Checksum, env wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { msgTypeName := getSudoMsgPayloadTypeName(sudoMsg) sudoFn, ok := m.sudoCallbacks[msgTypeName] @@ -185,7 +200,12 @@ func (m *MockWasmEngine) Sudo(checksum wasmvm.Checksum, env wasmvmtypes.Env, sud panic(fmt.Errorf("mock engine is not properly initialized: no callback specified for %s", msgTypeName)) } - return sudoFn(checksum, env, sudoMsg, store, goapi, querier, gasMeter, gasLimit, deserCost) + res, number, err := sudoFn(checksum, env, sudoMsg, store, goapi, querier, gasMeter, gasLimit, deserCost) + contractResult := wasmvmtypes.ContractResult{ + Ok: res, + Err: err.Error(), + } + return &contractResult, number, err } // GetCode implements the WasmEngine interface. @@ -229,6 +249,10 @@ func getQueryMsgPayloadTypeName(queryMsgBz []byte) string { payloadField = *payload.CheckForMisbehaviour } + if payload.ExportMetadata != nil { + payloadField = *payload.ExportMetadata + } + if payload.TimestampAtHeight != nil { payloadField = *payload.TimestampAtHeight } diff --git a/modules/light-clients/08-wasm/testing/simapp/simd/cmd/root.go b/modules/light-clients/08-wasm/testing/simapp/simd/cmd/root.go index 615728f7baf..95699b1865c 100644 --- a/modules/light-clients/08-wasm/testing/simapp/simd/cmd/root.go +++ b/modules/light-clients/08-wasm/testing/simapp/simd/cmd/root.go @@ -8,7 +8,7 @@ import ( "runtime/debug" "strings" - wasmvm "github.com/CosmWasm/wasmvm" + wasmvm "github.com/CosmWasm/wasmvm/v2" dbm "github.com/cosmos/cosmos-db" "github.com/spf13/cobra" "github.com/spf13/viper" diff --git a/modules/light-clients/08-wasm/testing/wasm_endpoint.go b/modules/light-clients/08-wasm/testing/wasm_endpoint.go index 6d126ea35c9..5d391e547a8 100644 --- a/modules/light-clients/08-wasm/testing/wasm_endpoint.go +++ b/modules/light-clients/08-wasm/testing/wasm_endpoint.go @@ -50,3 +50,12 @@ func (endpoint *WasmEndpoint) CreateClient() error { return nil } + +// AllowWasmClients adds 08-wasm to the list of allowed clients +func AllowWasmClients(chain *ibctesting.TestChain) { + ctx := chain.GetContext() + clientKeeper := chain.App.GetIBCKeeper().ClientKeeper + params := clientKeeper.GetParams(ctx) + params.AllowedClients = append(params.AllowedClients, types.Wasm) + clientKeeper.SetParams(ctx, params) +} diff --git a/modules/light-clients/08-wasm/types/client_state.go b/modules/light-clients/08-wasm/types/client_state.go index cced48113d1..1764df16d5a 100644 --- a/modules/light-clients/08-wasm/types/client_state.go +++ b/modules/light-clients/08-wasm/types/client_state.go @@ -73,6 +73,12 @@ func (cs ClientState) Status(ctx sdk.Context, clientStore storetypes.KVStore, _ return exported.Status(result.Status) } +// ZeroCustomFields returns a ClientState that is a copy of the current ClientState +// with all client customizable fields zeroed out +func (cs ClientState) ZeroCustomFields() exported.ClientState { + return &cs +} + // GetTimestampAtHeight returns the timestamp in nanoseconds of the consensus state at the given height. func (cs ClientState) GetTimestampAtHeight( ctx sdk.Context, diff --git a/modules/light-clients/08-wasm/types/client_state_test.go b/modules/light-clients/08-wasm/types/client_state_test.go index 5b7f4d94d3a..7084f0a7eaf 100644 --- a/modules/light-clients/08-wasm/types/client_state_test.go +++ b/modules/light-clients/08-wasm/types/client_state_test.go @@ -4,13 +4,14 @@ import ( "encoding/json" "time" - wasmvm "github.com/CosmWasm/wasmvm" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" + wasmvm "github.com/CosmWasm/wasmvm/v2" + wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" 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" + mock "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing/mock" "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" @@ -19,7 +20,6 @@ import ( "github.com/cosmos/ibc-go/v8/modules/core/exported" solomachine "github.com/cosmos/ibc-go/v8/modules/light-clients/06-solomachine" ibctesting "github.com/cosmos/ibc-go/v8/testing" - ibcmock "github.com/cosmos/ibc-go/v8/testing/mock" ) func (suite *TypesTestSuite) TestStatus() { @@ -114,6 +114,7 @@ func (suite *TypesTestSuite) TestGetTimestampAtHeight() { suite.Require().NotNil(payload.TimestampAtHeight) suite.Require().Nil(payload.CheckForMisbehaviour) suite.Require().Nil(payload.Status) + suite.Require().Nil(payload.ExportMetadata) suite.Require().Nil(payload.VerifyClientMessage) resp, err := json.Marshal(types.TimestampAtHeightResult{Timestamp: expectedTimestamp}) @@ -136,7 +137,7 @@ func (suite *TypesTestSuite) TestGetTimestampAtHeight() { { "error: invalid height", func() { - height = ibcmock.Height{} + height = mock.Height{} }, ibcerrors.ErrInvalidType, }, @@ -437,14 +438,14 @@ func (suite *TypesTestSuite) TestVerifyMembership() { { "invalid path argument", func() { - path = ibcmock.KeyPath{} + path = mock.KeyPath{} }, ibcerrors.ErrInvalidType, }, { "proof height is invalid type", func() { - proofHeight = ibcmock.Height{} + proofHeight = mock.Height{} }, ibcerrors.ErrInvalidType, }, @@ -578,14 +579,14 @@ func (suite *TypesTestSuite) TestVerifyNonMembership() { { "invalid path argument", func() { - path = ibcmock.KeyPath{} + path = mock.KeyPath{} }, ibcerrors.ErrInvalidType, }, { "proof height is invalid type", func() { - proofHeight = ibcmock.Height{} + proofHeight = mock.Height{} }, ibcerrors.ErrInvalidType, }, diff --git a/modules/light-clients/08-wasm/types/contract_api.go b/modules/light-clients/08-wasm/types/contract_api.go index 217818d439e..20b39c3f0d8 100644 --- a/modules/light-clients/08-wasm/types/contract_api.go +++ b/modules/light-clients/08-wasm/types/contract_api.go @@ -18,6 +18,7 @@ type InstantiateMessage struct { // Only one field should be set at a time. type QueryMsg struct { Status *StatusMsg `json:"status,omitempty"` + ExportMetadata *ExportMetadataMsg `json:"export_metadata,omitempty"` TimestampAtHeight *TimestampAtHeightMsg `json:"timestamp_at_height,omitempty"` VerifyClientMessage *VerifyClientMessageMsg `json:"verify_client_message,omitempty"` CheckForMisbehaviour *CheckForMisbehaviourMsg `json:"check_for_misbehaviour,omitempty"` @@ -26,6 +27,9 @@ type QueryMsg struct { // StatusMsg is a queryMsg sent to the contract to query the status of the wasm client. type StatusMsg struct{} +// ExportMetadataMsg is a queryMsg sent to the contract to query the exported metadata of the wasm client. +type ExportMetadataMsg struct{} + // TimestampAtHeightMsg is a queryMsg sent to the contract to query the timestamp at a given height. type TimestampAtHeightMsg struct { Height clienttypes.Height `json:"height"` @@ -91,12 +95,12 @@ type VerifyUpgradeAndUpdateStateMsg struct { ProofUpgradeConsensusState []byte `json:"proof_upgrade_consensus_state"` } -// MigrateClientStoreMsg is a sudoMsg sent to the contract to verify a given substitute client and update to its state. +// MigrateClientStore is a sudoMsg sent to the contract to verify a given substitute client and update to its state. type MigrateClientStoreMsg struct{} // ContractResult is a type constraint that defines the expected results that can be returned by a contract call/query. type ContractResult interface { - EmptyResult | StatusResult | TimestampAtHeightResult | CheckForMisbehaviourResult | UpdateStateResult + EmptyResult | StatusResult | ExportMetadataResult | TimestampAtHeightResult | CheckForMisbehaviourResult | UpdateStateResult } // EmptyResult is the default return type of any contract call that does not require a custom return type. @@ -107,6 +111,11 @@ type StatusResult struct { Status string `json:"status"` } +// ExportMetadataResult is the expected return type of the exportMetadataMsg query. It returns the exported metadata of the wasm client. +type ExportMetadataResult struct { + GenesisMetadata []clienttypes.GenesisMetadata `json:"genesis_metadata"` +} + // TimestampAtHeightResult is the expected return type of the timestampAtHeightMsg query. It returns the timestamp for a light client // at a given height. type TimestampAtHeightResult struct { diff --git a/modules/light-clients/08-wasm/types/gas_register.go b/modules/light-clients/08-wasm/types/gas_register.go index 4ccbcf401af..4ac8d0163b6 100644 --- a/modules/light-clients/08-wasm/types/gas_register.go +++ b/modules/light-clients/08-wasm/types/gas_register.go @@ -1,7 +1,7 @@ package types import ( - wasmvmtypes "github.com/CosmWasm/wasmvm/types" + wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" errorsmod "cosmossdk.io/errors" sdkmath "cosmossdk.io/math" @@ -80,7 +80,7 @@ type GasRegister interface { // ReplyCosts costs to to handle a message reply ReplyCosts(pinned bool, reply wasmvmtypes.Reply) storetypes.Gas // EventCosts costs to persist an event - EventCosts(attrs []wasmvmtypes.EventAttribute, events wasmvmtypes.Events) storetypes.Gas + EventCosts(attrs []wasmvmtypes.EventAttribute, events wasmvmtypes.Event) storetypes.Gas // ToWasmVMGas converts from Cosmos SDK gas units to [CosmWasm gas] (aka. wasmvm gas) // // [CosmWasm gas]: https://github.com/CosmWasm/cosmwasm/blob/v1.3.1/docs/GAS.md @@ -197,21 +197,21 @@ func (g WasmGasRegister) ReplyCosts(pinned bool, reply wasmvmtypes.Reply) storet attrs = append(attrs, e.Attributes...) } // apply free tier on the whole set not per event - eventGas += g.EventCosts(attrs, nil) + eventGas += g.EventCosts(attrs, wasmvmtypes.Event{}) } return eventGas + g.InstantiateContractCosts(pinned, msgLen) } // EventCosts costs to persist an event -func (g WasmGasRegister) EventCosts(attrs []wasmvmtypes.EventAttribute, events wasmvmtypes.Events) storetypes.Gas { +func (g WasmGasRegister) EventCosts(attrs []wasmvmtypes.EventAttribute, event wasmvmtypes.Event) storetypes.Gas { gas, remainingFreeTier := g.eventAttributeCosts(attrs, g.c.EventAttributeDataFreeTier) - for _, e := range events { - gas += g.c.CustomEventCost - gas += storetypes.Gas(len(e.Type)) * g.c.EventAttributeDataCost // no free tier with event type - var attrCost storetypes.Gas - attrCost, remainingFreeTier = g.eventAttributeCosts(e.Attributes, remainingFreeTier) - gas += attrCost - } + // for _, e := range events { + gas += g.c.CustomEventCost + gas += storetypes.Gas(len(event.Type)) * g.c.EventAttributeDataCost // no free tier with event type + var attrCost storetypes.Gas + attrCost, remainingFreeTier = g.eventAttributeCosts(event.Attributes, remainingFreeTier) + gas += attrCost + // } return gas } 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 e5a710f9e8e..bae6287f571 100644 --- a/modules/light-clients/08-wasm/types/gas_register_custom.go +++ b/modules/light-clients/08-wasm/types/gas_register_custom.go @@ -3,8 +3,8 @@ package types import ( "math" - wasmvm "github.com/CosmWasm/wasmvm" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" + wasmvm "github.com/CosmWasm/wasmvm/v2" + wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" storetypes "cosmossdk.io/store/types" diff --git a/modules/light-clients/08-wasm/types/genesis.go b/modules/light-clients/08-wasm/types/genesis.go index 00f2478d535..3cba401a98a 100644 --- a/modules/light-clients/08-wasm/types/genesis.go +++ b/modules/light-clients/08-wasm/types/genesis.go @@ -1,7 +1,16 @@ package types import ( + "time" + errorsmod "cosmossdk.io/errors" + storetypes "cosmossdk.io/store/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + + "github.com/cosmos/ibc-go/v8/modules/core/exported" ) // NewGenesisState creates an 08-wasm GenesisState instance. @@ -20,3 +29,24 @@ func (gs GenesisState) Validate() error { return nil } + +// ExportMetadata exports all the consensus metadata in the client store so they +// can be included in clients genesis and imported by a ClientKeeper +func (cs ClientState) ExportMetadata(store storetypes.KVStore) []exported.GenesisMetadata { + payload := QueryMsg{ + ExportMetadata: &ExportMetadataMsg{}, + } + + ctx := sdk.NewContext(nil, tmproto.Header{Height: 1, Time: time.Now()}, true, nil) // context with infinite gas meter + result, err := wasmQuery[ExportMetadataResult](ctx, store, &cs, payload) + if err != nil { + panic(err) + } + + genesisMetadata := make([]exported.GenesisMetadata, len(result.GenesisMetadata)) + for i, metadata := range result.GenesisMetadata { + genesisMetadata[i] = metadata + } + + return genesisMetadata +} 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 8ab8f0a6c12..8481524af23 100644 --- a/modules/light-clients/08-wasm/types/migrate_contract_test.go +++ b/modules/light-clients/08-wasm/types/migrate_contract_test.go @@ -4,8 +4,8 @@ import ( "encoding/hex" "encoding/json" - wasmvm "github.com/CosmWasm/wasmvm" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" + 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" diff --git a/modules/light-clients/08-wasm/types/misbehaviour_handle_test.go b/modules/light-clients/08-wasm/types/misbehaviour_handle_test.go index 05fd12e7566..3efe7ab41dd 100644 --- a/modules/light-clients/08-wasm/types/misbehaviour_handle_test.go +++ b/modules/light-clients/08-wasm/types/misbehaviour_handle_test.go @@ -4,8 +4,8 @@ import ( "encoding/json" "errors" - wasmvm "github.com/CosmWasm/wasmvm" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" + 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" diff --git a/modules/light-clients/08-wasm/types/proposal_handle_test.go b/modules/light-clients/08-wasm/types/proposal_handle_test.go index 7e4ba10aa96..f2b551f7b65 100644 --- a/modules/light-clients/08-wasm/types/proposal_handle_test.go +++ b/modules/light-clients/08-wasm/types/proposal_handle_test.go @@ -3,8 +3,8 @@ package types_test import ( "encoding/json" - cosmwasm "github.com/CosmWasm/wasmvm" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" + cosmwasm "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" diff --git a/modules/light-clients/08-wasm/types/querier.go b/modules/light-clients/08-wasm/types/querier.go index d9eb1376492..2682d3de3bb 100644 --- a/modules/light-clients/08-wasm/types/querier.go +++ b/modules/light-clients/08-wasm/types/querier.go @@ -5,7 +5,7 @@ import ( "fmt" "slices" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" + wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" errorsmod "cosmossdk.io/errors" storetypes "cosmossdk.io/store/types" @@ -34,11 +34,6 @@ var ( _ ibcwasm.QueryPluginsI = (*QueryPlugins)(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 // into the query plugins. type queryHandler struct { @@ -134,8 +129,8 @@ func NewDefaultQueryPlugins() *QueryPlugins { // This function returns protobuf encoded responses in bytes. func AcceptListStargateQuerier(acceptedQueries []string) 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...) + // A default list of accepted queries can be added here. + // accepted = append(defaultAcceptList, accepted...) isAccepted := slices.Contains(acceptedQueries, request.Path) if !isAccepted { diff --git a/modules/light-clients/08-wasm/types/querier_test.go b/modules/light-clients/08-wasm/types/querier_test.go index f912480864b..639904342e1 100644 --- a/modules/light-clients/08-wasm/types/querier_test.go +++ b/modules/light-clients/08-wasm/types/querier_test.go @@ -6,16 +6,14 @@ import ( "fmt" "math" - wasmvm "github.com/CosmWasm/wasmvm" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" + wasmvm "github.com/CosmWasm/wasmvm/v2" + wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" sdk "github.com/cosmos/cosmos-sdk/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" - commitmenttypes "github.com/cosmos/ibc-go/v8/modules/core/23-commitment/types" "github.com/cosmos/ibc-go/v8/modules/core/exported" ) @@ -120,18 +118,9 @@ func (suite *TypesTestSuite) TestCustomQuery() { func (suite *TypesTestSuite) TestStargateQuery() { typeURL := "/ibc.lightclients.wasm.v1.Query/Checksums" - var ( - endpoint *wasmtesting.WasmEndpoint - expDiscardedState = false - proofKey = []byte("mock-key") - testKey = []byte("test-key") - value = []byte("mock-value") - ) - testCases := []struct { name string malleate func() - expError error }{ { "success: custom query", @@ -142,7 +131,7 @@ func (suite *TypesTestSuite) TestStargateQuery() { ibcwasm.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) ([]byte, uint64, error) { + suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, querier wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) ([]byte, uint64, error) { queryRequest := types.QueryChecksumsRequest{} bz, err := queryRequest.Marshal() suite.Require().NoError(err) @@ -164,103 +153,14 @@ func (suite *TypesTestSuite) TestStargateQuery() { suite.Require().Len(respData.Checksums, 1) suite.Require().Equal(expChecksum, respData.Checksums[0]) - store.Set(testKey, value) - - result, err := json.Marshal(types.TimestampAtHeightResult{}) - suite.Require().NoError(err) - - return result, wasmtesting.DefaultGasUsed, nil - }) - }, - nil, - }, - { - // The following test sets a mock proof key and value in the ibc store and registers a query callback on the Status msg. - // The Status handler will then perform the QueryVerifyMembershipRequest using the wasmvm.Querier. - // As the VerifyMembership query rpc will call directly back into the same client, we also register a callback for VerifyMembership. - // Here we decode the proof and verify the mock proof key and value are set in the ibc store. - // 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 := types.QueryPlugins{ - Stargate: types.AcceptListStargateQuerier([]string{""}), - } - - ibcwasm.SetQueryPlugins(&querierPlugin) - - store := suite.chainA.GetContext().KVStore(GetSimApp(suite.chainA).GetKey(exported.StoreKey)) - store.Set(proofKey, value) - - suite.coordinator.CommitBlock(suite.chainA) - proof, proofHeight := endpoint.QueryProofAtHeight(proofKey, uint64(suite.chainA.GetContext().BlockHeight())) - - merklePath := commitmenttypes.NewMerklePath(string(proofKey)) - merklePath, err := commitmenttypes.ApplyPrefix(suite.chainA.GetPrefix(), merklePath) - suite.Require().NoError(err) - - suite.mockVM.RegisterQueryCallback(types.TimestampAtHeightMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, querier wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) ([]byte, uint64, error) { - queryRequest := clienttypes.QueryVerifyMembershipRequest{ - ClientId: endpoint.ClientID, - Proof: proof, - ProofHeight: proofHeight, - MerklePath: merklePath, - Value: value, - } - - bz, err := queryRequest.Marshal() - suite.Require().NoError(err) - - resp, err := querier.Query(wasmvmtypes.QueryRequest{ - Stargate: &wasmvmtypes.StargateQuery{ - Path: "/ibc.core.client.v1.Query/VerifyMembership", - Data: bz, - }, - }, math.MaxUint64) - suite.Require().NoError(err) - - var respData clienttypes.QueryVerifyMembershipResponse - err = respData.Unmarshal(resp) - suite.Require().NoError(err) - - suite.Require().True(respData.Success) - - result, err := json.Marshal(types.TimestampAtHeightResult{}) - suite.Require().NoError(err) - - return result, wasmtesting.DefaultGasUsed, nil - }) - - suite.mockVM.RegisterSudoCallback(types.VerifyMembershipMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, - _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction, - ) (*wasmvmtypes.Response, uint64, error) { - var payload types.SudoMsg - err := json.Unmarshal(sudoMsg, &payload) - suite.Require().NoError(err) - - var merkleProof commitmenttypes.MerkleProof - err = suite.chainA.Codec.Unmarshal(payload.VerifyMembership.Proof, &merkleProof) - suite.Require().NoError(err) - - root := commitmenttypes.NewMerkleRoot(suite.chainA.App.LastCommitID().Hash) - err = merkleProof.VerifyMembership(commitmenttypes.GetSDKSpecs(), root, merklePath, payload.VerifyMembership.Value) - suite.Require().NoError(err) - - bz, err := json.Marshal(types.EmptyResult{}) - suite.Require().NoError(err) - - expDiscardedState = true - store.Set(testKey, value) - - return &wasmvmtypes.Response{Data: bz}, wasmtesting.DefaultGasUsed, nil + return resp, wasmtesting.DefaultGasUsed, nil }) }, - nil, }, { "failure: default querier", func() { - suite.mockVM.RegisterQueryCallback(types.TimestampAtHeightMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, querier wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) ([]byte, uint64, error) { + suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, querier wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) ([]byte, uint64, error) { queryRequest := types.QueryChecksumsRequest{} bz, err := queryRequest.Marshal() suite.Require().NoError(err) @@ -274,21 +174,17 @@ func (suite *TypesTestSuite) TestStargateQuery() { suite.Require().ErrorIs(err, wasmvmtypes.UnsupportedRequest{Kind: fmt.Sprintf("'%s' path is not allowed from the contract", typeURL)}) suite.Require().Nil(resp) - store.Set(testKey, value) - return nil, wasmtesting.DefaultGasUsed, err }) }, - wasmvmtypes.UnsupportedRequest{Kind: fmt.Sprintf("'%s' path is not allowed from the contract", typeURL)}, }, } for _, tc := range testCases { suite.Run(tc.name, func() { - expDiscardedState = false suite.SetupWasmWithMockVM() - endpoint = wasmtesting.NewWasmEndpoint(suite.chainA) + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) err := endpoint.CreateClient() suite.Require().NoError(err) @@ -296,25 +192,7 @@ func (suite *TypesTestSuite) TestStargateQuery() { clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpoint.ClientID) clientState := endpoint.GetClientState() - - // 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 = clientState.GetTimestampAtHeight(suite.chainA.GetContext(), clientStore, suite.chainA.App.AppCodec(), clienttypes.NewHeight(1, 100)) - - expPass := tc.expError == nil - if expPass { - suite.Require().NoError(err) - } else { - // use error contains as wasmvm errors do not implement errors.Is method - suite.Require().ErrorContains(err, tc.expError.Error()) - } - - if expDiscardedState { - suite.Require().False(clientStore.Has(testKey)) - } else { - suite.Require().True(clientStore.Has(testKey)) - } + 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 9f6cbcb49b7..975bccb8b0c 100644 --- a/modules/light-clients/08-wasm/types/store.go +++ b/modules/light-clients/08-wasm/types/store.go @@ -8,8 +8,8 @@ import ( "reflect" "strings" - wasmvm "github.com/CosmWasm/wasmvm" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" + wasmvm "github.com/CosmWasm/wasmvm/v2" + wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" errorsmod "cosmossdk.io/errors" "cosmossdk.io/store/cachekv" diff --git a/modules/light-clients/08-wasm/types/types_test.go b/modules/light-clients/08-wasm/types/types_test.go index e1729f66997..28f069a5bc1 100644 --- a/modules/light-clients/08-wasm/types/types_test.go +++ b/modules/light-clients/08-wasm/types/types_test.go @@ -5,8 +5,8 @@ import ( "errors" "testing" - wasmvm "github.com/CosmWasm/wasmvm" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" + wasmvm "github.com/CosmWasm/wasmvm/v2" + wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" dbm "github.com/cosmos/cosmos-db" testifysuite "github.com/stretchr/testify/suite" @@ -79,6 +79,8 @@ func (suite *TypesTestSuite) SetupWasmWithMockVM() { suite.coordinator = ibctesting.NewCoordinator(suite.T(), 1) suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) suite.checksum = storeWasmCode(suite, wasmtesting.Code) + + wasmtesting.AllowWasmClients(suite.chainA) } func (suite *TypesTestSuite) setupWasmWithMockVM() (ibctesting.TestingApp, map[string]json.RawMessage) { diff --git a/modules/light-clients/08-wasm/types/update_test.go b/modules/light-clients/08-wasm/types/update_test.go index b1e4a4a9525..c0936b1e937 100644 --- a/modules/light-clients/08-wasm/types/update_test.go +++ b/modules/light-clients/08-wasm/types/update_test.go @@ -4,8 +4,8 @@ import ( "encoding/json" "fmt" - wasmvm "github.com/CosmWasm/wasmvm" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" + wasmvm "github.com/CosmWasm/wasmvm/v2" + wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" errorsmod "cosmossdk.io/errors" storetypes "cosmossdk.io/store/types" diff --git a/modules/light-clients/08-wasm/types/upgrade_test.go b/modules/light-clients/08-wasm/types/upgrade_test.go index 243b6a6fb06..5efa009c219 100644 --- a/modules/light-clients/08-wasm/types/upgrade_test.go +++ b/modules/light-clients/08-wasm/types/upgrade_test.go @@ -4,8 +4,8 @@ import ( "encoding/json" "time" - wasmvm "github.com/CosmWasm/wasmvm" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" + wasmvm "github.com/CosmWasm/wasmvm/v2" + wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" storetypes "cosmossdk.io/store/types" @@ -46,6 +46,7 @@ func (suite *TypesTestSuite) TestVerifyClientMessage() { suite.Require().Nil(msg.Status) suite.Require().Nil(msg.CheckForMisbehaviour) suite.Require().Nil(msg.TimestampAtHeight) + suite.Require().Nil(msg.ExportMetadata) suite.Require().Equal(env.Contract.Address, defaultWasmClientID) @@ -121,10 +122,10 @@ func (suite *TypesTestSuite) TestVerifyClientMessage() { func (suite *TypesTestSuite) TestVerifyUpgradeAndUpdateState() { var ( - upgradedClient exported.ClientState - upgradedConsState exported.ConsensusState - upgradedClientProof []byte - upgradedConsensusStateProof []byte + upgradedClient exported.ClientState + upgradedConsState exported.ConsensusState + proofUpgradedClient []byte + proofUpgradedConsState []byte ) testCases := []struct { @@ -149,8 +150,8 @@ func (suite *TypesTestSuite) TestVerifyUpgradeAndUpdateState() { // verify payload values suite.Require().Equal(expectedUpgradedClient.Data, payload.VerifyUpgradeAndUpdateState.UpgradeClientState) suite.Require().Equal(expectedUpgradedConsensus.Data, payload.VerifyUpgradeAndUpdateState.UpgradeConsensusState) - suite.Require().Equal(upgradedClientProof, payload.VerifyUpgradeAndUpdateState.ProofUpgradeClient) - suite.Require().Equal(upgradedConsensusStateProof, payload.VerifyUpgradeAndUpdateState.ProofUpgradeConsensusState) + suite.Require().Equal(proofUpgradedClient, payload.VerifyUpgradeAndUpdateState.ProofUpgradeClient) + suite.Require().Equal(proofUpgradedConsState, payload.VerifyUpgradeAndUpdateState.ProofUpgradeConsensusState) // verify other Sudo fields are nil suite.Require().Nil(payload.UpdateState) @@ -222,8 +223,8 @@ func (suite *TypesTestSuite) TestVerifyUpgradeAndUpdateState() { clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), defaultWasmClientID) - upgradedClientProof = wasmtesting.MockUpgradedClientStateProofBz - upgradedConsensusStateProof = wasmtesting.MockUpgradedConsensusStateProofBz + proofUpgradedClient = wasmtesting.MockUpgradedClientStateProofBz + proofUpgradedConsState = wasmtesting.MockUpgradedConsensusStateProofBz err = clientState.VerifyUpgradeAndUpdateState( suite.chainA.GetContext(), @@ -231,8 +232,8 @@ func (suite *TypesTestSuite) TestVerifyUpgradeAndUpdateState() { clientStore, upgradedClient, upgradedConsState, - upgradedClientProof, - upgradedConsensusStateProof, + proofUpgradedClient, + proofUpgradedConsState, ) expPass := tc.expErr == nil diff --git a/modules/light-clients/08-wasm/types/vm.go b/modules/light-clients/08-wasm/types/vm.go index 1a894ae0843..111d047c48c 100644 --- a/modules/light-clients/08-wasm/types/vm.go +++ b/modules/light-clients/08-wasm/types/vm.go @@ -6,8 +6,8 @@ import ( "encoding/json" "errors" - wasmvm "github.com/CosmWasm/wasmvm" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" + wasmvm "github.com/CosmWasm/wasmvm/v2" + wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" errorsmod "cosmossdk.io/errors" storetypes "cosmossdk.io/store/types" @@ -26,13 +26,13 @@ var ( // 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{ - HumanAddress: humanAddress, - CanonicalAddress: canonicalAddress, + HumanizeAddress: humanAddress, + CanonicalizeAddress: canonicalAddress, } ) // instantiateContract calls vm.Instantiate with appropriate arguments. -func instantiateContract(ctx sdk.Context, clientStore storetypes.KVStore, checksum Checksum, msg []byte) (*wasmvmtypes.Response, error) { +func instantiateContract(ctx sdk.Context, clientStore storetypes.KVStore, checksum Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) { sdkGasMeter := ctx.GasMeter() multipliedGasMeter := NewMultipliedGasMeter(sdkGasMeter, VMGasRegister) gasLimit := VMGasRegister.runtimeGasForContract(ctx) @@ -55,7 +55,7 @@ func instantiateContract(ctx sdk.Context, clientStore storetypes.KVStore, checks } // callContract calls vm.Sudo with internally constructed gas meter and environment. -func callContract(ctx sdk.Context, clientStore storetypes.KVStore, checksum Checksum, msg []byte) (*wasmvmtypes.Response, error) { +func callContract(ctx sdk.Context, clientStore storetypes.KVStore, checksum Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) { sdkGasMeter := ctx.GasMeter() multipliedGasMeter := NewMultipliedGasMeter(sdkGasMeter, VMGasRegister) gasLimit := VMGasRegister.runtimeGasForContract(ctx) @@ -73,7 +73,7 @@ func callContract(ctx sdk.Context, clientStore storetypes.KVStore, checksum Chec } // 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.Response, error) { +func migrateContract(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, checksum Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) { sdkGasMeter := ctx.GasMeter() multipliedGasMeter := NewMultipliedGasMeter(sdkGasMeter, VMGasRegister) gasLimit := VMGasRegister.runtimeGasForContract(ctx) @@ -87,7 +87,7 @@ func migrateContract(ctx sdk.Context, clientID string, clientStore storetypes.KV } // queryContract calls vm.Query. -func queryContract(ctx sdk.Context, clientStore storetypes.KVStore, checksum Checksum, msg []byte) ([]byte, error) { +func queryContract(ctx sdk.Context, clientStore storetypes.KVStore, checksum Checksum, msg []byte) (*wasmvmtypes.QueryResult, error) { sdkGasMeter := ctx.GasMeter() multipliedGasMeter := NewMultipliedGasMeter(sdkGasMeter, VMGasRegister) gasLimit := VMGasRegister.runtimeGasForContract(ctx) @@ -160,7 +160,7 @@ func wasmSudo[T ContractResult](ctx sdk.Context, cdc codec.BinaryCodec, clientSt return result, errorsmod.Wrapf(err, "checksum (%s)", hex.EncodeToString(cs.Checksum)) } - if err := json.Unmarshal(resp.Data, &result); err != nil { + if err := json.Unmarshal(resp.Ok.Data, &result); err != nil { return result, errorsmod.Wrap(ErrWasmInvalidResponseData, err.Error()) } @@ -212,7 +212,7 @@ func wasmQuery[T ContractResult](ctx sdk.Context, clientStore storetypes.KVStore return result, errorsmod.Wrap(ErrWasmContractCallFailed, err.Error()) } - if err := json.Unmarshal(resp, &result); err != nil { + if err := json.Unmarshal(resp.Ok, &result); err != nil { return result, errorsmod.Wrapf(ErrWasmInvalidResponseData, "failed to unmarshal result of wasm query: %v", err) } @@ -276,7 +276,7 @@ func getEnv(ctx sdk.Context, contractAddr string) wasmvmtypes.Env { env := wasmvmtypes.Env{ Block: wasmvmtypes.BlockInfo{ Height: uint64(height), - Time: uint64(nsec), + Time: wasmvmtypes.Uint64(nsec), ChainID: chainID, }, Contract: wasmvmtypes.ContractInfo{ @@ -297,15 +297,15 @@ func canonicalAddress(human string) ([]byte, uint64, error) { // 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.ContractResult) error { // Only allow Data to flow back to us. SubMessages, Events and Attributes are not allowed. - if len(response.Messages) > 0 { + if len(response.Ok.Messages) > 0 { return ErrWasmSubMessagesNotAllowed } - if len(response.Events) > 0 { + if len(response.Ok.Events) > 0 { return ErrWasmEventsNotAllowed } - if len(response.Attributes) > 0 { + if len(response.Ok.Attributes) > 0 { return ErrWasmAttributesNotAllowed } diff --git a/modules/light-clients/08-wasm/types/vm_test.go b/modules/light-clients/08-wasm/types/vm_test.go index 7b56857a55e..efafd52e506 100644 --- a/modules/light-clients/08-wasm/types/vm_test.go +++ b/modules/light-clients/08-wasm/types/vm_test.go @@ -3,8 +3,8 @@ package types_test import ( "encoding/json" - wasmvm "github.com/CosmWasm/wasmvm" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" + 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" @@ -25,8 +25,6 @@ func (suite *TypesTestSuite) TestWasmInstantiate() { 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.Response, uint64, error) { // Ensure GoAPI is set - suite.Require().NotNil(goapi.CanonicalAddress) - suite.Require().NotNil(goapi.HumanAddress) var payload types.InstantiateMessage err := json.Unmarshal(initMsg, &payload) @@ -187,9 +185,6 @@ func (suite *TypesTestSuite) TestWasmMigrate() { func() { suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, goapi wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { // Ensure GoAPI is set - suite.Require().NotNil(goapi.CanonicalAddress) - suite.Require().NotNil(goapi.HumanAddress) - resp, err := json.Marshal(types.EmptyResult{}) suite.Require().NoError(err) @@ -317,8 +312,6 @@ func (suite *TypesTestSuite) TestWasmQuery() { func() { suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, goapi wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) ([]byte, uint64, error) { // Ensure GoAPI is set - suite.Require().NotNil(goapi.CanonicalAddress) - suite.Require().NotNil(goapi.HumanAddress) resp, err := json.Marshal(types.StatusResult{Status: exported.Frozen.String()}) suite.Require().NoError(err) @@ -393,9 +386,6 @@ func (suite *TypesTestSuite) TestWasmSudo() { 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.Response, uint64, error) { // Ensure GoAPI is set - suite.Require().NotNil(goapi.CanonicalAddress) - suite.Require().NotNil(goapi.HumanAddress) - resp, err := json.Marshal(types.UpdateStateResult{}) suite.Require().NoError(err)