mukan-ibc/modules/light-clients/08-wasm/light_client_module_test.go
Mukan Erkin Törük 6852832fe8
Some checks failed
CodeQL / Analyze (push) Waiting to run
Docker Build & Push Simapp (main) / docker-build (push) Waiting to run
golangci-lint / lint (push) Waiting to run
Tests / Code Coverage / build (amd64) (push) Waiting to run
Tests / Code Coverage / build (arm64) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[additional-args:-tags="test_e2e" name:e2e path:./e2e]) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[name:08-wasm path:./modules/light-clients/08-wasm]) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[name:ibc-go path:.]) (push) Waiting to run
Deploy to GitHub Pages / Deploy to GitHub Pages (push) Has been cancelled
Buf-Push / push (push) Has been cancelled
initial: sovereign Mukan Network fork
2026-05-11 03:18:28 +03:00

1582 lines
55 KiB
Go

package wasm_test
import (
"encoding/json"
"errors"
"fmt"
"time"
wasmvm "github.com/CosmWasm/wasmvm/v2"
wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types"
errorsmod "cosmossdk.io/errors"
internaltypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/internal/types"
wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/testing"
"github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types"
clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types"
commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types"
host "github.com/cosmos/ibc-go/v10/modules/core/24-host"
ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors"
"github.com/cosmos/ibc-go/v10/modules/core/exported"
solomachine "github.com/cosmos/ibc-go/v10/modules/light-clients/06-solomachine"
ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint"
ibctesting "github.com/cosmos/ibc-go/v10/testing"
ibcmock "github.com/cosmos/ibc-go/v10/testing/mock"
)
const (
tmClientID = "07-tendermint-0"
wasmClientID = "08-wasm-0"
// Used for checks where look ups for valid client id should fail.
unusedWasmClientID = "08-wasm-100"
)
func (suite *WasmTestSuite) TestStatus() {
var clientID string
testCases := []struct {
name string
malleate func()
expStatus exported.Status
}{
{
"client is active",
func() {},
exported.Active,
},
{
"client is frozen",
func() {
suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(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) {
resp, err := json.Marshal(types.StatusResult{Status: exported.Frozen.String()})
suite.Require().NoError(err)
return &wasmvmtypes.QueryResult{Ok: resp}, wasmtesting.DefaultGasUsed, nil
})
},
exported.Frozen,
},
{
"client status is expired",
func() {
suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(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) {
resp, err := json.Marshal(types.StatusResult{Status: exported.Expired.String()})
suite.Require().NoError(err)
return &wasmvmtypes.QueryResult{Ok: resp}, wasmtesting.DefaultGasUsed, nil
})
},
exported.Expired,
},
{
"client status is unknown: vm returns an error",
func() {
suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(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) {
return nil, 0, wasmtesting.ErrMockContract
})
},
exported.Unknown,
},
{
"client status is unauthorized: checksum is not stored",
func() {
wasmClientKeeper := GetSimApp(suite.chainA).WasmClientKeeper
err := wasmClientKeeper.GetChecksums().Remove(suite.chainA.GetContext(), suite.checksum)
suite.Require().NoError(err)
},
exported.Unauthorized,
},
{
"failure: cannot find client state",
func() {
clientID = unusedWasmClientID
},
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 {
tc := tc
suite.Run(tc.name, func() {
suite.SetupWasmWithMockVM()
endpoint := wasmtesting.NewWasmEndpoint(suite.chainA)
err := endpoint.CreateClient()
suite.Require().NoError(err)
clientID = endpoint.ClientID
lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID)
suite.Require().NoError(err)
tc.malleate()
status := lightClientModule.Status(suite.chainA.GetContext(), clientID)
suite.Require().Equal(tc.expStatus, status)
})
}
}
func (suite *WasmTestSuite) TestTimestampAtHeight() {
var (
clientID string
height exported.Height
)
expectedTimestamp := uint64(time.Now().UnixNano())
testCases := []struct {
name string
malleate func()
expErr error
}{
{
"success",
func() {
suite.mockVM.RegisterQueryCallback(types.TimestampAtHeightMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, queryMsg []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) {
var payload types.QueryMsg
err := json.Unmarshal(queryMsg, &payload)
suite.Require().NoError(err)
suite.Require().NotNil(payload.TimestampAtHeight)
suite.Require().Nil(payload.CheckForMisbehaviour)
suite.Require().Nil(payload.Status)
suite.Require().Nil(payload.VerifyClientMessage)
resp, err := json.Marshal(types.TimestampAtHeightResult{Timestamp: expectedTimestamp})
suite.Require().NoError(err)
return &wasmvmtypes.QueryResult{Ok: resp}, wasmtesting.DefaultGasUsed, nil
})
},
nil,
},
{
"failure: vm returns error",
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 nil, 0, wasmtesting.ErrMockVM
})
},
types.ErrVMError,
},
{
"failure: contract returns error",
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{Err: wasmtesting.ErrMockContract.Error()}, 0, nil
})
},
types.ErrWasmContractCallFailed,
},
{
"failure: error: invalid height",
func() {
height = ibcmock.Height{}
},
ibcerrors.ErrInvalidType,
},
{
"failure: cannot find client state",
func() {
clientID = unusedWasmClientID
},
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 {
tc := tc
suite.Run(tc.name, func() {
suite.SetupWasmWithMockVM()
endpoint := wasmtesting.NewWasmEndpoint(suite.chainA)
err := endpoint.CreateClient()
suite.Require().NoError(err)
clientID = endpoint.ClientID
clientState, ok := endpoint.GetClientState().(*types.ClientState)
suite.Require().True(ok)
height = clientState.LatestHeight
lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID)
suite.Require().NoError(err)
tc.malleate()
timestamp, err := lightClientModule.TimestampAtHeight(suite.chainA.GetContext(), clientID, height)
if tc.expErr == nil {
suite.Require().NoError(err)
suite.Require().Equal(expectedTimestamp, timestamp)
} else {
suite.Require().ErrorIs(err, tc.expErr)
suite.Require().Equal(uint64(0), timestamp)
}
})
}
}
func (suite *WasmTestSuite) TestInitialize() {
var (
consensusState exported.ConsensusState
clientState exported.ClientState
)
testCases := []struct {
name string
malleate func()
expError error
}{
{
"success: new mock client",
func() {},
nil,
},
{
"success: validate contract address",
func() {
suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, env 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)
suite.Require().Equal(env.Contract.Address, wasmClientID)
wrappedClientState, ok := clienttypes.MustUnmarshalClientState(suite.chainA.App.AppCodec(), payload.ClientState).(*ibctm.ClientState)
suite.Require().True(ok)
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)
resp, err := json.Marshal(types.EmptyResult{})
suite.Require().NoError(err)
return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: resp}}, 0, nil
}
},
nil,
},
{
"failure: cannot unmarshal client state",
func() {
clientState = &solomachine.ClientState{Sequence: 20}
},
errors.New("proto: wrong wireType = 0 for field Data"),
},
{
"failure: client state is invalid",
func() {
clientState = &types.ClientState{}
},
types.ErrInvalidData,
},
{
"failure: invalid consensus state",
func() {
// set upgraded consensus state to solomachine consensus state
consensusState = &solomachine.ConsensusState{}
},
types.ErrInvalidData,
},
{
"failure: checksum has not been stored.",
func() {
clientState = types.NewClientState([]byte{1}, []byte("unknown"), clienttypes.NewHeight(0, 1))
},
types.ErrInvalidChecksum,
},
{
"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,
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
suite.SetupWasmWithMockVM()
wrappedClientStateBz := clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), wasmtesting.MockTendermitClientState)
wrappedClientConsensusStateBz := clienttypes.MustMarshalConsensusState(suite.chainA.App.AppCodec(), wasmtesting.MockTendermintClientConsensusState)
clientState = types.NewClientState(wrappedClientStateBz, suite.checksum, wasmtesting.MockTendermitClientState.LatestHeight)
consensusState = types.NewConsensusState(wrappedClientConsensusStateBz)
clientID := suite.chainA.App.GetIBCKeeper().ClientKeeper.GenerateClientIdentifier(suite.chainA.GetContext(), clientState.ClientType())
lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID)
suite.Require().NoError(err)
tc.malleate()
// Marshal client state and consensus state:
clientStateBz := suite.chainA.Codec.MustMarshal(clientState)
consensusStateBz := suite.chainA.Codec.MustMarshal(consensusState)
err = lightClientModule.Initialize(suite.chainA.GetContext(), clientID, clientStateBz, consensusStateBz)
if tc.expError == nil {
suite.Require().NoError(err)
} else {
suite.Require().ErrorContains(err, tc.expError.Error())
}
})
}
}
func (suite *WasmTestSuite) TestVerifyMembership() {
var (
clientState *types.ClientState
expClientStateBz []byte
path exported.Path
proof []byte
proofHeight exported.Height
value []byte
clientID string
)
testCases := []struct {
name string
malleate func()
expError error
}{
{
"success",
func() {
expClientStateBz = clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), clientState)
suite.mockVM.RegisterSudoCallback(types.VerifyMembershipMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, sudoMsg []byte, _ wasmvm.KVStore,
_ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction,
) (*wasmvmtypes.ContractResult, uint64, error) {
var payload types.SudoMsg
err := json.Unmarshal(sudoMsg, &payload)
suite.Require().NoError(err)
bz, err := json.Marshal(types.EmptyResult{})
suite.Require().NoError(err)
return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: bz}}, wasmtesting.DefaultGasUsed, nil
})
},
nil,
},
{
"success: with update client state",
func() {
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.ContractResult, uint64, error) {
var payload types.SudoMsg
err := json.Unmarshal(sudoMsg, &payload)
suite.Require().NoError(err)
suite.Require().NotNil(payload.VerifyMembership)
suite.Require().Nil(payload.UpdateState)
suite.Require().Nil(payload.UpdateStateOnMisbehaviour)
suite.Require().Nil(payload.VerifyNonMembership)
suite.Require().Nil(payload.VerifyUpgradeAndUpdateState)
suite.Require().Equal(proofHeight, payload.VerifyMembership.Height)
suite.Require().Equal(proof, payload.VerifyMembership.Proof)
suite.Require().Equal(path, payload.VerifyMembership.Path)
suite.Require().Equal(value, payload.VerifyMembership.Value)
bz, err := json.Marshal(types.EmptyResult{})
suite.Require().NoError(err)
expClientStateBz = wasmtesting.CreateMockClientStateBz(suite.chainA.Codec, suite.checksum)
store.Set(host.ClientStateKey(), expClientStateBz)
return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: bz}}, wasmtesting.DefaultGasUsed, nil
})
},
nil,
},
{
"failure: cannot find client state",
func() {
clientID = unusedWasmClientID
},
clienttypes.ErrClientNotFound,
},
{
"failure: contract returns invalid proof error",
func() {
proof = wasmtesting.MockInvalidProofBz
suite.mockVM.RegisterSudoCallback(types.VerifyMembershipMsg{}, 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: commitmenttypes.ErrInvalidProof.Error()}, wasmtesting.DefaultGasUsed, nil
})
},
types.ErrWasmContractCallFailed,
},
{
"failure: proof height greater than client state latest height",
func() {
proofHeight = clienttypes.NewHeight(1, 100)
},
ibcerrors.ErrInvalidHeight,
},
{
"failure: invalid path argument",
func() {
path = ibcmock.KeyPath{}
},
ibcerrors.ErrInvalidType,
},
{
"failure: proof height is invalid type",
func() {
proofHeight = ibcmock.Height{}
},
ibcerrors.ErrInvalidType,
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
var ok bool
suite.SetupWasmWithMockVM()
endpoint := wasmtesting.NewWasmEndpoint(suite.chainA)
err := endpoint.CreateClient()
suite.Require().NoError(err)
clientID = endpoint.ClientID
path = commitmenttypes.NewMerklePath([]byte("/ibc/key/path"))
proof = wasmtesting.MockValidProofBz
proofHeight = clienttypes.NewHeight(0, 1)
value = []byte("value")
clientState, ok = endpoint.GetClientState().(*types.ClientState)
suite.Require().True(ok)
lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID)
suite.Require().NoError(err)
tc.malleate()
err = lightClientModule.VerifyMembership(suite.chainA.GetContext(), clientID, proofHeight, 0, 0, proof, path, value)
if tc.expError == nil {
clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), clientID)
suite.Require().NoError(err)
suite.Require().Equal(expClientStateBz, clientStore.Get(host.ClientStateKey()))
} else {
suite.Require().ErrorIs(err, tc.expError)
}
})
}
}
func (suite *WasmTestSuite) TestVerifyNonMembership() {
var (
clientState *types.ClientState
expClientStateBz []byte
path exported.Path
proof []byte
proofHeight exported.Height
clientID string
)
testCases := []struct {
name string
malleate func()
expError error
}{
{
"success",
func() {
expClientStateBz = clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), clientState)
suite.mockVM.RegisterSudoCallback(types.VerifyNonMembershipMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, sudoMsg []byte, _ wasmvm.KVStore,
_ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction,
) (*wasmvmtypes.ContractResult, uint64, error) {
var payload types.SudoMsg
err := json.Unmarshal(sudoMsg, &payload)
suite.Require().NoError(err)
suite.Require().NotNil(payload.VerifyNonMembership)
suite.Require().Nil(payload.UpdateState)
suite.Require().Nil(payload.UpdateStateOnMisbehaviour)
suite.Require().Nil(payload.VerifyMembership)
suite.Require().Nil(payload.VerifyUpgradeAndUpdateState)
suite.Require().Equal(proofHeight, payload.VerifyNonMembership.Height)
suite.Require().Equal(proof, payload.VerifyNonMembership.Proof)
suite.Require().Equal(path, payload.VerifyNonMembership.Path)
bz, err := json.Marshal(types.EmptyResult{})
suite.Require().NoError(err)
return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: bz}}, wasmtesting.DefaultGasUsed, nil
})
},
nil,
},
{
"success: with update client state",
func() {
suite.mockVM.RegisterSudoCallback(types.VerifyNonMembershipMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore,
_ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction,
) (*wasmvmtypes.ContractResult, uint64, error) {
var payload types.SudoMsg
err := json.Unmarshal(sudoMsg, &payload)
suite.Require().NoError(err)
suite.Require().NotNil(payload.VerifyNonMembership)
suite.Require().Nil(payload.UpdateState)
suite.Require().Nil(payload.UpdateStateOnMisbehaviour)
suite.Require().Nil(payload.VerifyMembership)
suite.Require().Nil(payload.VerifyUpgradeAndUpdateState)
suite.Require().Equal(proofHeight, payload.VerifyNonMembership.Height)
suite.Require().Equal(proof, payload.VerifyNonMembership.Proof)
suite.Require().Equal(path, payload.VerifyNonMembership.Path)
bz, err := json.Marshal(types.EmptyResult{})
suite.Require().NoError(err)
expClientStateBz = wasmtesting.CreateMockClientStateBz(suite.chainA.Codec, suite.checksum)
store.Set(host.ClientStateKey(), expClientStateBz)
return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: bz}}, wasmtesting.DefaultGasUsed, nil
})
},
nil,
},
{
"failure: cannot find client state",
func() {
clientID = unusedWasmClientID
},
clienttypes.ErrClientNotFound,
},
{
"failure: wasm vm returns error",
func() {
proof = wasmtesting.MockInvalidProofBz
suite.mockVM.RegisterSudoCallback(types.VerifyNonMembershipMsg{}, 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 invalid proof error",
func() {
proof = wasmtesting.MockInvalidProofBz
suite.mockVM.RegisterSudoCallback(types.VerifyNonMembershipMsg{}, 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: commitmenttypes.ErrInvalidProof.Error()}, wasmtesting.DefaultGasUsed, nil
})
},
types.ErrWasmContractCallFailed,
},
{
"failure: proof height greater than client state latest height",
func() {
proofHeight = clienttypes.NewHeight(1, 100)
},
ibcerrors.ErrInvalidHeight,
},
{
"failure: invalid path argument",
func() {
path = ibcmock.KeyPath{}
},
ibcerrors.ErrInvalidType,
},
{
"failure: proof height is invalid type",
func() {
proofHeight = ibcmock.Height{}
},
ibcerrors.ErrInvalidType,
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
var ok bool
suite.SetupWasmWithMockVM()
endpoint := wasmtesting.NewWasmEndpoint(suite.chainA)
err := endpoint.CreateClient()
suite.Require().NoError(err)
clientID = endpoint.ClientID
path = commitmenttypes.NewMerklePath([]byte("/ibc/key/path"))
proof = wasmtesting.MockInvalidProofBz
proofHeight = clienttypes.NewHeight(0, 1)
clientState, ok = endpoint.GetClientState().(*types.ClientState)
suite.Require().True(ok)
lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID)
suite.Require().NoError(err)
tc.malleate()
err = lightClientModule.VerifyNonMembership(suite.chainA.GetContext(), clientID, proofHeight, 0, 0, proof, path)
if tc.expError == nil {
clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), clientID)
suite.Require().NoError(err)
suite.Require().Equal(expClientStateBz, clientStore.Get(host.ClientStateKey()))
} else {
suite.Require().ErrorIs(err, tc.expError)
}
})
}
}
func (suite *WasmTestSuite) TestVerifyClientMessage() {
var (
clientMsg exported.ClientMessage
clientID string
)
testCases := []struct {
name string
malleate func()
expErr error
}{
{
"success: valid misbehaviour",
func() {
suite.mockVM.RegisterQueryCallback(types.VerifyClientMessageMsg{}, func(_ wasmvm.Checksum, env wasmvmtypes.Env, queryMsg []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) {
var msg *types.QueryMsg
err := json.Unmarshal(queryMsg, &msg)
suite.Require().NoError(err)
suite.Require().NotNil(msg.VerifyClientMessage)
suite.Require().NotNil(msg.VerifyClientMessage.ClientMessage)
suite.Require().Nil(msg.Status)
suite.Require().Nil(msg.CheckForMisbehaviour)
suite.Require().Nil(msg.TimestampAtHeight)
suite.Require().Equal(env.Contract.Address, wasmClientID)
resp, err := json.Marshal(types.EmptyResult{})
suite.Require().NoError(err)
return &wasmvmtypes.QueryResult{Ok: resp}, wasmtesting.DefaultGasUsed, nil
})
},
nil,
},
{
"failure: cannot find client state",
func() {
clientID = unusedWasmClientID
},
clienttypes.ErrClientNotFound,
},
{
"failure: invalid client message",
func() {
clientMsg = &ibctm.Header{}
suite.mockVM.RegisterQueryCallback(types.VerifyClientMessageMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, queryMsg []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) {
resp, err := json.Marshal(types.EmptyResult{})
suite.Require().NoError(err)
return &wasmvmtypes.QueryResult{Ok: resp}, wasmtesting.DefaultGasUsed, nil
})
},
ibcerrors.ErrInvalidType,
},
{
"failure: error return from vm",
func() {
suite.mockVM.RegisterQueryCallback(types.VerifyClientMessageMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, queryMsg []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) {
return nil, 0, wasmtesting.ErrMockVM
})
},
types.ErrVMError,
},
{
"failure: error return from contract",
func() {
suite.mockVM.RegisterQueryCallback(types.VerifyClientMessageMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, queryMsg []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) {
return &wasmvmtypes.QueryResult{Err: wasmtesting.ErrMockContract.Error()}, 0, nil
})
},
types.ErrWasmContractCallFailed,
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
// reset suite to create fresh application state
suite.SetupWasmWithMockVM()
endpoint := wasmtesting.NewWasmEndpoint(suite.chainA)
err := endpoint.CreateClient()
suite.Require().NoError(err)
clientID = endpoint.ClientID
clientMsg = &types.ClientMessage{
Data: clienttypes.MustMarshalClientMessage(suite.chainA.App.AppCodec(), wasmtesting.MockTendermintClientHeader),
}
lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID)
suite.Require().NoError(err)
tc.malleate()
err = lightClientModule.VerifyClientMessage(suite.chainA.GetContext(), clientID, clientMsg)
if tc.expErr == nil {
suite.Require().NoError(err)
} else {
suite.Require().ErrorIs(err, tc.expErr)
}
})
}
}
func (suite *WasmTestSuite) TestVerifyUpgradeAndUpdateState() {
var (
upgradedClient exported.ClientState
upgradedConsState exported.ConsensusState
upgradedClientProof []byte
upgradedConsensusStateProof []byte
clientID string
)
testCases := []struct {
name string
malleate func()
expErr error
}{
{
"success: successful upgrade",
func() {
suite.mockVM.RegisterSudoCallback(types.VerifyUpgradeAndUpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) {
var payload types.SudoMsg
err := json.Unmarshal(sudoMsg, &payload)
suite.Require().NoError(err)
expectedUpgradedClient, ok := upgradedClient.(*types.ClientState)
suite.Require().True(ok)
expectedUpgradedConsensus, ok := upgradedConsState.(*types.ConsensusState)
suite.Require().True(ok)
// 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)
// verify other Sudo fields are nil
suite.Require().Nil(payload.UpdateState)
suite.Require().Nil(payload.UpdateStateOnMisbehaviour)
suite.Require().Nil(payload.VerifyMembership)
suite.Require().Nil(payload.VerifyNonMembership)
data, err := json.Marshal(types.EmptyResult{})
suite.Require().NoError(err)
// set new client state and consensus state
wrappedUpgradedClient, ok := clienttypes.MustUnmarshalClientState(suite.chainA.App.AppCodec(), expectedUpgradedClient.Data).(*ibctm.ClientState)
suite.Require().True(ok)
store.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), upgradedClient))
store.Set(host.ConsensusStateKey(wrappedUpgradedClient.LatestHeight), clienttypes.MustMarshalConsensusState(suite.chainA.App.AppCodec(), upgradedConsState))
return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: data}}, wasmtesting.DefaultGasUsed, nil
})
},
nil,
},
{
"failure: invalid client state",
func() {
upgradedClient = &solomachine.ClientState{Sequence: 20}
},
clienttypes.ErrInvalidClient,
},
{
"failure: invalid height",
func() {
upgradedClient = &types.ClientState{LatestHeight: clienttypes.ZeroHeight()}
},
ibcerrors.ErrInvalidHeight,
},
{
"failure: cannot find client state",
func() {
clientID = unusedWasmClientID
},
clienttypes.ErrClientNotFound,
},
/* NOTE(jim): This can't fail on unmarshalling, it appears. Any consensus type
we attempt to unmarshal just creates a Wasm ConsensusState that has a
Data field empty.
{
"failure: upgraded consensus state is not wasm consensus state",
func() {
// set upgraded consensus state to solomachine consensus state
upgradedConsState = &solomachine.ConsensusState{}
},
clienttypes.ErrInvalidConsensus,
},
*/
{
"failure: vm returns error",
func() {
suite.mockVM.RegisterSudoCallback(types.VerifyUpgradeAndUpdateStateMsg{}, 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.RegisterSudoCallback(types.VerifyUpgradeAndUpdateStateMsg{}, 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,
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
// reset suite
suite.SetupWasmWithMockVM()
endpoint := wasmtesting.NewWasmEndpoint(suite.chainA)
err := endpoint.CreateClient()
suite.Require().NoError(err)
clientID = endpoint.ClientID
clientState, ok := endpoint.GetClientState().(*types.ClientState)
suite.Require().True(ok)
newLatestHeight := clienttypes.NewHeight(2, 10)
wrappedUpgradedClient := wasmtesting.CreateMockTendermintClientState(newLatestHeight)
wrappedUpgradedClientBz := clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), wrappedUpgradedClient)
upgradedClient = types.NewClientState(wrappedUpgradedClientBz, clientState.Checksum, newLatestHeight)
wrappedUpgradedConsensus := ibctm.NewConsensusState(time.Now(), commitmenttypes.NewMerkleRoot([]byte("new-hash")), []byte("new-nextValsHash"))
wrappedUpgradedConsensusBz := clienttypes.MustMarshalConsensusState(suite.chainA.App.AppCodec(), wrappedUpgradedConsensus)
upgradedConsState = types.NewConsensusState(wrappedUpgradedConsensusBz)
lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID)
suite.Require().NoError(err)
tc.malleate()
clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), wasmClientID)
upgradedClientProof = wasmtesting.MockUpgradedClientStateProofBz
upgradedConsensusStateProof = wasmtesting.MockUpgradedConsensusStateProofBz
newClient := suite.chainA.Codec.MustMarshal(upgradedClient)
newConsensusState := suite.chainA.Codec.MustMarshal(upgradedConsState)
err = lightClientModule.VerifyUpgradeAndUpdateState(
suite.chainA.GetContext(),
clientID,
newClient,
newConsensusState,
upgradedClientProof,
upgradedConsensusStateProof,
)
if tc.expErr == nil {
suite.Require().NoError(err)
// verify new client state and consensus state
clientStateBz := clientStore.Get(host.ClientStateKey())
suite.Require().NotEmpty(clientStateBz)
suite.Require().Equal(upgradedClient, clienttypes.MustUnmarshalClientState(suite.chainA.Codec, clientStateBz))
consStateBz := clientStore.Get(host.ConsensusStateKey(upgradedClient.(*types.ClientState).LatestHeight))
suite.Require().NotEmpty(consStateBz)
suite.Require().Equal(upgradedConsState, clienttypes.MustUnmarshalConsensusState(suite.chainA.Codec, consStateBz))
} else {
suite.Require().Error(err)
suite.Require().ErrorIs(err, tc.expErr)
}
})
}
}
func (suite *WasmTestSuite) TestCheckForMisbehaviour() {
var (
clientMessage exported.ClientMessage
clientID string
)
testCases := []struct {
name string
malleate func()
foundMisbehaviour bool
expPanic error
}{
{
"success: no misbehaviour",
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) {
resp, err := json.Marshal(types.CheckForMisbehaviourResult{FoundMisbehaviour: false})
suite.Require().NoError(err)
return &wasmvmtypes.QueryResult{Ok: resp}, wasmtesting.DefaultGasUsed, nil
})
},
false,
nil,
},
{
"success: misbehaviour found", 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) {
resp, err := json.Marshal(types.CheckForMisbehaviourResult{FoundMisbehaviour: true})
suite.Require().NoError(err)
return &wasmvmtypes.QueryResult{Ok: resp}, wasmtesting.DefaultGasUsed, nil
})
},
true,
nil,
},
{
"success: contract error, resp cannot be marshalled", 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) {
resp := "cannot be unmarshalled"
return &wasmvmtypes.QueryResult{Ok: []byte(resp)}, wasmtesting.DefaultGasUsed, nil
})
},
false,
nil,
},
{
"success: contract returns error", 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{Err: wasmtesting.ErrMockContract.Error()}, wasmtesting.DefaultGasUsed, nil
})
},
false,
nil,
},
{
"success: vm returns error, ", 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 nil, 0, errors.New("invalid block ID")
})
},
false,
nil,
},
{
"success: invalid client message", func() {
clientMessage = &ibctm.Header{}
// we will not register the callback here because this test case does not reach the VM
},
false,
nil,
},
{
"failure: cannot find client state",
func() {
clientID = unusedWasmClientID
},
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 {
suite.Run(tc.name, func() {
// reset suite to create fresh application state
suite.SetupWasmWithMockVM()
endpoint := wasmtesting.NewWasmEndpoint(suite.chainA)
err := endpoint.CreateClient()
suite.Require().NoError(err)
clientID = endpoint.ClientID
clientMessage = &types.ClientMessage{
Data: clienttypes.MustMarshalClientMessage(suite.chainA.App.AppCodec(), wasmtesting.MockTendermintClientMisbehaviour),
}
lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID)
suite.Require().NoError(err)
tc.malleate()
var foundMisbehaviour bool
foundMisbehaviourFunc := func() {
foundMisbehaviour = lightClientModule.CheckForMisbehaviour(suite.chainA.GetContext(), clientID, clientMessage)
}
if tc.expPanic == nil {
foundMisbehaviourFunc()
suite.Require().Equal(tc.foundMisbehaviour, foundMisbehaviour)
} else {
suite.Require().PanicsWithError(tc.expPanic.Error(), foundMisbehaviourFunc)
}
})
}
}
func (suite *WasmTestSuite) TestUpdateState() {
mockHeight := clienttypes.NewHeight(1, 50)
var (
clientMsg exported.ClientMessage
expectedClientStateBz []byte
clientID string
)
testCases := []struct {
name string
malleate func()
expPanic error
expHeights []exported.Height
}{
{
"success: no update",
func() {
suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, env wasmvmtypes.Env, sudoMsg []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) {
var msg types.SudoMsg
err := json.Unmarshal(sudoMsg, &msg)
suite.Require().NoError(err)
suite.Require().NotNil(msg.UpdateState)
suite.Require().NotNil(msg.UpdateState.ClientMessage)
suite.Require().Equal(msg.UpdateState.ClientMessage, clienttypes.MustMarshalClientMessage(suite.chainA.App.AppCodec(), wasmtesting.MockTendermintClientHeader))
suite.Require().Nil(msg.VerifyMembership)
suite.Require().Nil(msg.VerifyNonMembership)
suite.Require().Nil(msg.UpdateStateOnMisbehaviour)
suite.Require().Nil(msg.VerifyUpgradeAndUpdateState)
suite.Require().Equal(env.Contract.Address, wasmClientID)
updateStateResp := types.UpdateStateResult{
Heights: []clienttypes.Height{},
}
resp, err := json.Marshal(updateStateResp)
if err != nil {
return nil, 0, err
}
return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: resp}}, wasmtesting.DefaultGasUsed, nil
})
},
nil,
[]exported.Height{},
},
{
"success: update client",
func() {
suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) {
var msg types.SudoMsg
err := json.Unmarshal(sudoMsg, &msg)
suite.Require().NoError(err)
bz := store.Get(host.ClientStateKey())
suite.Require().NotEmpty(bz)
clientState, ok := clienttypes.MustUnmarshalClientState(suite.chainA.Codec, bz).(*types.ClientState)
suite.Require().True(ok)
clientState.LatestHeight = mockHeight
expectedClientStateBz = clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), clientState)
store.Set(host.ClientStateKey(), expectedClientStateBz)
updateStateResp := types.UpdateStateResult{
Heights: []clienttypes.Height{mockHeight},
}
resp, err := json.Marshal(updateStateResp)
if err != nil {
return nil, 0, err
}
return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: resp}}, wasmtesting.DefaultGasUsed, nil
})
},
nil,
[]exported.Height{mockHeight},
},
{
"failure: cannot find client state",
func() {
clientID = unusedWasmClientID
},
fmt.Errorf("08-wasm-100: %s", clienttypes.ErrClientNotFound),
nil,
},
{
"failure: invalid ClientMessage type",
func() {
// SudoCallback left nil because clientMsg is checked by 08-wasm before callbackFn is called.
clientMsg = &ibctm.Misbehaviour{}
},
fmt.Errorf("expected type %T, got %T", (*types.ClientMessage)(nil), (*ibctm.Misbehaviour)(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, 0, wasmtesting.ErrMockVM
})
},
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() {
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()}, 0, nil
})
},
errorsmod.Wrap(types.ErrWasmContractCallFailed, wasmtesting.ErrMockContract.Error()),
nil,
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
suite.SetupWasmWithMockVM() // reset
endpoint := wasmtesting.NewWasmEndpoint(suite.chainA)
err := endpoint.CreateClient()
suite.Require().NoError(err)
clientID = endpoint.ClientID
expectedClientStateBz = nil
clientMsg = &types.ClientMessage{
Data: clienttypes.MustMarshalClientMessage(suite.chainA.App.AppCodec(), wasmtesting.MockTendermintClientHeader),
}
lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID)
suite.Require().NoError(err)
tc.malleate()
var heights []exported.Height
updateState := func() {
heights = lightClientModule.UpdateState(suite.chainA.GetContext(), clientID, clientMsg)
}
if tc.expPanic == nil {
updateState()
suite.Require().Equal(tc.expHeights, heights)
if expectedClientStateBz != nil {
clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpoint.ClientID)
clientStateBz := clientStore.Get(host.ClientStateKey())
suite.Require().Equal(expectedClientStateBz, clientStateBz)
}
} else {
suite.Require().PanicsWithError(tc.expPanic.Error(), updateState)
}
})
}
}
func (suite *WasmTestSuite) TestUpdateStateOnMisbehaviour() {
mockHeight := clienttypes.NewHeight(1, 50)
var (
clientMsg exported.ClientMessage
expectedClientStateBz []byte
clientID string
)
testCases := []struct {
name string
malleate func()
panicErr error
updatedClientState []byte
}{
{
"success: no update",
func() {
suite.mockVM.RegisterSudoCallback(types.UpdateStateOnMisbehaviourMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) {
var msg types.SudoMsg
err := json.Unmarshal(sudoMsg, &msg)
suite.Require().NoError(err)
suite.Require().NotNil(msg.UpdateStateOnMisbehaviour)
suite.Require().NotNil(msg.UpdateStateOnMisbehaviour.ClientMessage)
suite.Require().Nil(msg.UpdateState)
suite.Require().Nil(msg.UpdateState)
suite.Require().Nil(msg.VerifyMembership)
suite.Require().Nil(msg.VerifyNonMembership)
suite.Require().Nil(msg.VerifyUpgradeAndUpdateState)
resp, err := json.Marshal(types.EmptyResult{})
if err != nil {
return nil, 0, err
}
return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: resp}}, wasmtesting.DefaultGasUsed, nil
})
},
nil,
nil,
},
{
"success: client state updated on valid misbehaviour",
func() {
suite.mockVM.RegisterSudoCallback(types.UpdateStateOnMisbehaviourMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) {
var msg types.SudoMsg
err := json.Unmarshal(sudoMsg, &msg)
suite.Require().NoError(err)
// set new client state in store
bz := store.Get(host.ClientStateKey())
suite.Require().NotEmpty(bz)
clientState, ok := clienttypes.MustUnmarshalClientState(suite.chainA.App.AppCodec(), bz).(*types.ClientState)
suite.Require().True(ok)
clientState.LatestHeight = mockHeight
expectedClientStateBz = clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), clientState)
store.Set(host.ClientStateKey(), expectedClientStateBz)
resp, err := json.Marshal(types.EmptyResult{})
if err != nil {
return nil, 0, err
}
return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: resp}}, wasmtesting.DefaultGasUsed, nil
})
},
nil,
clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), wasmtesting.CreateMockTendermintClientState(mockHeight)),
},
{
"failure: cannot find client state",
func() {
clientID = unusedWasmClientID
},
fmt.Errorf("%s: %s", unusedWasmClientID, clienttypes.ErrClientNotFound),
nil,
},
{
"failure: invalid client message",
func() {
clientMsg = &ibctm.Header{}
// we will not register the callback here because this test case does not reach the VM
},
fmt.Errorf("expected type %T, got %T", (*types.ClientMessage)(nil), (*ibctm.Header)(nil)),
nil,
},
{
"failure: err return from vm",
func() {
suite.mockVM.RegisterSudoCallback(types.UpdateStateOnMisbehaviourMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) {
return nil, 0, wasmtesting.ErrMockVM
})
},
errorsmod.Wrap(types.ErrVMError, wasmtesting.ErrMockVM.Error()),
nil,
},
{
"failure: err return from contract",
func() {
suite.mockVM.RegisterSudoCallback(types.UpdateStateOnMisbehaviourMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) {
return &wasmvmtypes.ContractResult{Err: wasmtesting.ErrMockContract.Error()}, 0, nil
})
},
errorsmod.Wrap(types.ErrWasmContractCallFailed, wasmtesting.ErrMockContract.Error()),
nil,
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
// reset suite to create fresh application state
suite.SetupWasmWithMockVM()
endpoint := wasmtesting.NewWasmEndpoint(suite.chainA)
err := endpoint.CreateClient()
suite.Require().NoError(err)
clientID = endpoint.ClientID
expectedClientStateBz = nil
clientMsg = &types.ClientMessage{
Data: clienttypes.MustMarshalClientMessage(suite.chainA.App.AppCodec(), wasmtesting.MockTendermintClientMisbehaviour),
}
lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID)
suite.Require().NoError(err)
tc.malleate()
updateFunc := func() {
lightClientModule.UpdateStateOnMisbehaviour(suite.chainA.GetContext(), clientID, clientMsg)
}
if tc.panicErr == nil {
updateFunc()
if expectedClientStateBz != nil {
store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpoint.ClientID)
suite.Require().Equal(expectedClientStateBz, store.Get(host.ClientStateKey()))
}
} else {
suite.Require().PanicsWithError(tc.panicErr.Error(), updateFunc)
}
})
}
}
func (suite *WasmTestSuite) TestRecoverClient() {
var (
expectedClientStateBz []byte
subjectClientID, substituteClientID string
)
testCases := []struct {
name string
malleate func()
expErr error
}{
{
"success",
func() {
suite.mockVM.RegisterSudoCallback(
types.MigrateClientStoreMsg{},
func(_ wasmvm.Checksum, _ wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) {
var payload types.SudoMsg
err := json.Unmarshal(sudoMsg, &payload)
suite.Require().NoError(err)
suite.Require().NotNil(payload.MigrateClientStore)
suite.Require().Nil(payload.UpdateState)
suite.Require().Nil(payload.UpdateStateOnMisbehaviour)
suite.Require().Nil(payload.VerifyMembership)
suite.Require().Nil(payload.VerifyNonMembership)
suite.Require().Nil(payload.VerifyUpgradeAndUpdateState)
bz, err := json.Marshal(types.EmptyResult{})
suite.Require().NoError(err)
prefixedKey := internaltypes.SubjectPrefix
prefixedKey = append(prefixedKey, host.ClientStateKey()...)
expectedClientStateBz = wasmtesting.CreateMockClientStateBz(suite.chainA.Codec, suite.checksum)
store.Set(prefixedKey, expectedClientStateBz)
return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: bz}}, wasmtesting.DefaultGasUsed, nil
},
)
},
nil,
},
{
"failure: cannot parse malformed substitute client ID",
func() {
substituteClientID = ibctesting.InvalidID
},
host.ErrInvalidID,
},
{
"failure: substitute client ID does not contain 08-wasm prefix",
func() {
substituteClientID = tmClientID
},
clienttypes.ErrInvalidClientType,
},
{
"failure: cannot find subject client state",
func() {
subjectClientID = unusedWasmClientID
},
clienttypes.ErrClientNotFound,
},
{
"failure: cannot find substitute client state",
func() {
substituteClientID = unusedWasmClientID
},
clienttypes.ErrClientNotFound,
},
{
"failure: checksums do not match",
func() {
substituteClientState, found := GetSimApp(suite.chainA).IBCKeeper.ClientKeeper.GetClientState(suite.chainA.GetContext(), substituteClientID)
suite.Require().True(found)
wasmSubstituteClientState, ok := substituteClientState.(*types.ClientState)
suite.Require().True(ok)
wasmSubstituteClientState.Checksum = []byte("invalid")
GetSimApp(suite.chainA).IBCKeeper.ClientKeeper.SetClientState(suite.chainA.GetContext(), substituteClientID, wasmSubstituteClientState)
},
clienttypes.ErrInvalidClient,
},
{
"failure: vm returns error",
func() {
suite.mockVM.RegisterSudoCallback(
types.MigrateClientStoreMsg{},
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.MigrateClientStoreMsg{},
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()
subjectEndpoint := wasmtesting.NewWasmEndpoint(suite.chainA)
err := subjectEndpoint.CreateClient()
suite.Require().NoError(err)
subjectClientID = subjectEndpoint.ClientID
substituteEndpoint := wasmtesting.NewWasmEndpoint(suite.chainA)
err = substituteEndpoint.CreateClient()
suite.Require().NoError(err)
substituteClientID = substituteEndpoint.ClientID
expectedClientStateBz = nil
lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), subjectClientID)
suite.Require().NoError(err)
tc.malleate()
err = lightClientModule.RecoverClient(suite.chainA.GetContext(), subjectClientID, substituteClientID)
if tc.expErr == nil {
suite.Require().NoError(err)
subjectClientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), subjectClientID)
suite.Require().Equal(expectedClientStateBz, subjectClientStore.Get(host.ClientStateKey()))
} else {
suite.Require().ErrorIs(err, tc.expErr)
}
})
}
}
func (suite *WasmTestSuite) TestLatestHeight() {
var clientID string
testCases := []struct {
name string
malleate func()
expHeight clienttypes.Height
}{
{
"success",
func() {
},
clienttypes.NewHeight(1, 5),
},
{
"failure: cannot find substitute client state",
func() {
clientID = unusedWasmClientID
},
clienttypes.ZeroHeight(),
},
}
for _, tc := range testCases {
tc := tc
suite.Run(tc.name, func() {
suite.SetupWasmWithMockVM()
subjectEndpoint := wasmtesting.NewWasmEndpoint(suite.chainA)
err := subjectEndpoint.CreateClient()
suite.Require().NoError(err)
clientID = subjectEndpoint.ClientID
lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID)
suite.Require().NoError(err)
tc.malleate()
height := lightClientModule.LatestHeight(suite.chainA.GetContext(), clientID)
suite.Require().Equal(tc.expHeight, height)
})
}
}