diff --git a/app/wasm.go b/app/wasm.go new file mode 100644 index 0000000000..efc4584b31 --- /dev/null +++ b/app/wasm.go @@ -0,0 +1,15 @@ +package app + +// AllCapabilities returns all capabilities available with the current wasmvm +// See https://github.com/CosmWasm/cosmwasm/blob/main/docs/CAPABILITIES-BUILT-IN.md +// This functionality is going to be moved upstream: https://github.com/CosmWasm/wasmvm/issues/425 +func AllCapabilities() []string { + return []string{ + "iterator", + "staking", + "stargate", + "cosmwasm_1_1", + "cosmwasm_1_2", + "cosmwasm_1_3", + } +} diff --git a/x/wasm/client/cli/gov_tx.go b/x/wasm/client/cli/gov_tx.go index cb1b00fc45..d37b529c64 100644 --- a/x/wasm/client/cli/gov_tx.go +++ b/x/wasm/client/cli/gov_tx.go @@ -2,9 +2,9 @@ package cli import ( "bytes" - "crypto/sha256" "encoding/hex" "fmt" + wasmvm "github.com/CosmWasm/wasmvm" "net/url" "strconv" "strings" @@ -149,11 +149,14 @@ func parseVerificationFlags(gzippedWasm []byte, flags *flag.FlagSet) (string, st // wasm is gzipped in parseStoreCodeArgs // checksum generation will be decoupled here // reference https://github.com/CosmWasm/wasmvm/issues/359 - raw, err := ioutils.Uncompress(gzippedWasm, uint64(types.MaxWasmSize)) + raw, err := ioutils.Uncompress(gzippedWasm, int64(types.MaxWasmSize)) if err != nil { return "", "", nil, fmt.Errorf("invalid zip: %w", err) } - checksum := sha256.Sum256(raw) + checksum, err := wasmvm.CreateChecksum(raw) + if err != nil { + return "", "", nil, fmt.Errorf("checksum: %s", err) + } if !bytes.Equal(checksum[:], codeHash) { return "", "", nil, fmt.Errorf("code-hash mismatch: %X, checksum: %X", codeHash, checksum) } diff --git a/x/wasm/ioutils/ioutil.go b/x/wasm/ioutils/ioutil.go index a34a43e9ea..6ed2b94c8f 100644 --- a/x/wasm/ioutils/ioutil.go +++ b/x/wasm/ioutils/ioutil.go @@ -3,14 +3,15 @@ package ioutils import ( "bytes" "compress/gzip" + errorsmod "cosmossdk.io/errors" "io" "github.com/CosmWasm/wasmd/x/wasm/types" ) // Uncompress expects a valid gzip source to unpack or fails. See IsGzip -func Uncompress(gzipSrc []byte, limit uint64) ([]byte, error) { - if uint64(len(gzipSrc)) > limit { +func Uncompress(gzipSrc []byte, limit int64) ([]byte, error) { + if int64(len(gzipSrc)) > limit { return nil, types.ErrLimit } zr, err := gzip.NewReader(bytes.NewReader(gzipSrc)) @@ -19,7 +20,11 @@ func Uncompress(gzipSrc []byte, limit uint64) ([]byte, error) { } zr.Multistream(false) defer zr.Close() - return io.ReadAll(LimitReader(zr, int64(limit))) + bz, err := io.ReadAll(LimitReader(zr, limit)) + if types.ErrLimit.Is(err) { + return nil, errorsmod.Wrapf(err, "max %d bytes", limit) + } + return bz, err } // LimitReader returns a Reader that reads from r diff --git a/x/wasm/keeper/handler_plugin_encoders.go b/x/wasm/keeper/handler_plugin_encoders.go index 0977c80429..6d35611717 100644 --- a/x/wasm/keeper/handler_plugin_encoders.go +++ b/x/wasm/keeper/handler_plugin_encoders.go @@ -144,6 +144,16 @@ func EncodeDistributionMsg(sender sdk.AccAddress, msg *wasmvmtypes.DistributionM ValidatorAddress: msg.WithdrawDelegatorReward.Validator, } return []sdk.Msg{&withdrawMsg}, nil + case msg.FundCommunityPool != nil: + amt, err := ConvertWasmCoinsToSdkCoins(msg.FundCommunityPool.Amount) + if err != nil { + return nil, err + } + fundMsg := distributiontypes.MsgFundCommunityPool{ + Depositor: sender.String(), + Amount: amt, + } + return []sdk.Msg{&fundMsg}, nil default: return nil, errorsmod.Wrap(types.ErrUnknownMsg, "unknown variant of Distribution") } diff --git a/x/wasm/keeper/handler_plugin_encoders_test.go b/x/wasm/keeper/handler_plugin_encoders_test.go index cddd56bb1b..d59da669bb 100644 --- a/x/wasm/keeper/handler_plugin_encoders_test.go +++ b/x/wasm/keeper/handler_plugin_encoders_test.go @@ -386,6 +386,28 @@ func TestEncoding(t *testing.T) { }, }, }, + "distribution fund community pool": { + sender: addr1, + srcMsg: wasmvmtypes.CosmosMsg{ + Distribution: &wasmvmtypes.DistributionMsg{ + FundCommunityPool: &wasmvmtypes.FundCommunityPoolMsg{ + Amount: wasmvmtypes.Coins{ + wasmvmtypes.NewCoin(200, "stones"), + wasmvmtypes.NewCoin(200, "feathers"), + }, + }, + }, + }, + output: []sdk.Msg{ + &distributiontypes.MsgFundCommunityPool{ + Depositor: addr1.String(), + Amount: sdk.NewCoins( + sdk.NewInt64Coin("stones", 200), + sdk.NewInt64Coin("feathers", 200), + ), + }, + }, + }, "stargate encoded bank msg": { sender: addr2, srcMsg: wasmvmtypes.CosmosMsg{ diff --git a/x/wasm/keeper/keeper.go b/x/wasm/keeper/keeper.go index 2f79efde58..0699c3352a 100644 --- a/x/wasm/keeper/keeper.go +++ b/x/wasm/keeper/keeper.go @@ -149,7 +149,7 @@ func (k Keeper) create(ctx sdk.Context, creator sdk.AccAddress, wasmCode []byte, if ioutils.IsGzip(wasmCode) { ctx.GasMeter().ConsumeGas(k.gasRegister.UncompressCosts(len(wasmCode)), "Uncompress gzip bytecode") - wasmCode, err = ioutils.Uncompress(wasmCode, uint64(types.MaxWasmSize)) + wasmCode, err = ioutils.Uncompress(wasmCode, int64(types.MaxWasmSize)) if err != nil { return 0, checksum, errorsmod.Wrap(types.ErrCreateFailed, err.Error()) } @@ -191,7 +191,7 @@ func (k Keeper) storeCodeInfo(ctx sdk.Context, codeID uint64, codeInfo types.Cod func (k Keeper) importCode(ctx sdk.Context, codeID uint64, codeInfo types.CodeInfo, wasmCode []byte) error { if ioutils.IsGzip(wasmCode) { var err error - wasmCode, err = ioutils.Uncompress(wasmCode, uint64(types.MaxWasmSize)) + wasmCode, err = ioutils.Uncompress(wasmCode, int64(types.MaxWasmSize)) if err != nil { return errorsmod.Wrap(types.ErrCreateFailed, err.Error()) } diff --git a/x/wasm/keeper/options.go b/x/wasm/keeper/options.go index cc5547f22c..a2674e8a1e 100644 --- a/x/wasm/keeper/options.go +++ b/x/wasm/keeper/options.go @@ -24,6 +24,13 @@ func WithWasmEngine(x types.WasmerEngine) Option { }) } +// WithWasmEngineDecorator is an optional constructor parameter to decorate the default wasmVM engine. +func WithWasmEngineDecorator(d func(old types.WasmerEngine) types.WasmerEngine) Option { + return optsFn(func(k *Keeper) { + k.wasmVM = d(k.wasmVM) + }) +} + // WithMessageHandler is an optional constructor parameter to set a custom handler for wasmVM messages. // This option should not be combined with Option `WithMessageEncoders` or `WithMessageHandlerDecorator` func WithMessageHandler(x Messenger) Option { diff --git a/x/wasm/keeper/options_test.go b/x/wasm/keeper/options_test.go index b04198710a..acbd880b6b 100644 --- a/x/wasm/keeper/options_test.go +++ b/x/wasm/keeper/options_test.go @@ -1,6 +1,7 @@ package keeper import ( + wasmvm "github.com/CosmWasm/wasmvm" "reflect" "testing" @@ -29,6 +30,15 @@ func TestConstructorOptions(t *testing.T) { assert.IsType(t, &wasmtesting.MockWasmer{}, k.wasmVM) }, }, + "decorate wasmvm": { + srcOpt: WithWasmEngineDecorator(func(old types.WasmerEngine) types.WasmerEngine { + require.IsType(t, &wasmvm.VM{}, old) + return &wasmtesting.MockWasmer{} + }), + verify: func(t *testing.T, k Keeper) { + assert.IsType(t, &wasmtesting.MockWasmer{}, k.wasmVM) + }, + }, "message handler": { srcOpt: WithMessageHandler(&wasmtesting.MockMessageHandler{}), verify: func(t *testing.T, k Keeper) { diff --git a/x/wasm/keeper/query_plugins.go b/x/wasm/keeper/query_plugins.go index f78d037d98..de138f5018 100644 --- a/x/wasm/keeper/query_plugins.go +++ b/x/wasm/keeper/query_plugins.go @@ -3,6 +3,8 @@ package keeper import ( "encoding/json" "errors" + "github.com/cosmos/cosmos-sdk/types/query" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" errorsmod "cosmossdk.io/errors" "github.com/cosmos/cosmos-sdk/baseapp" @@ -77,12 +79,13 @@ func (q QueryHandler) GasConsumed() uint64 { type CustomQuerier func(ctx sdk.Context, request json.RawMessage) ([]byte, error) type QueryPlugins struct { - Bank func(ctx sdk.Context, request *wasmvmtypes.BankQuery) ([]byte, error) - Custom CustomQuerier - IBC func(ctx sdk.Context, caller sdk.AccAddress, request *wasmvmtypes.IBCQuery) ([]byte, error) - Staking func(ctx sdk.Context, request *wasmvmtypes.StakingQuery) ([]byte, error) - Stargate func(ctx sdk.Context, request *wasmvmtypes.StargateQuery) ([]byte, error) - Wasm func(ctx sdk.Context, request *wasmvmtypes.WasmQuery) ([]byte, error) + Bank func(ctx sdk.Context, request *wasmvmtypes.BankQuery) ([]byte, error) + Custom CustomQuerier + IBC func(ctx sdk.Context, caller sdk.AccAddress, request *wasmvmtypes.IBCQuery) ([]byte, error) + Staking func(ctx sdk.Context, request *wasmvmtypes.StakingQuery) ([]byte, error) + Stargate func(ctx sdk.Context, request *wasmvmtypes.StargateQuery) ([]byte, error) + Wasm func(ctx sdk.Context, request *wasmvmtypes.WasmQuery) ([]byte, error) + Distribution func(ctx sdk.Context, request *wasmvmtypes.DistributionQuery) ([]byte, error) } type contractMetaDataSource interface { @@ -105,12 +108,13 @@ func DefaultQueryPlugins( wasm wasmQueryKeeper, ) QueryPlugins { return QueryPlugins{ - Bank: BankQuerier(bank), - Custom: NoCustomQuerier, - IBC: IBCQuerier(wasm, channelKeeper), - Staking: StakingQuerier(staking, distKeeper), - Stargate: StargateQuerier(), - Wasm: WasmQuerier(wasm), + Bank: BankQuerier(bank), + Custom: NoCustomQuerier, + IBC: IBCQuerier(wasm, channelKeeper), + Staking: StakingQuerier(staking, distKeeper), + Stargate: StargateQuerier(), + Wasm: WasmQuerier(wasm), + Distribution: DistributionQuerier(distKeeper), } } @@ -137,6 +141,9 @@ func (e QueryPlugins) Merge(o *QueryPlugins) QueryPlugins { if o.Wasm != nil { e.Wasm = o.Wasm } + if o.Distribution != nil { + e.Distribution = o.Distribution + } return e } @@ -161,6 +168,9 @@ func (e QueryPlugins) HandleQuery(ctx sdk.Context, caller sdk.AccAddress, reques if request.Wasm != nil { return e.Wasm(ctx, request.Wasm) } + if request.Distribution != nil { + return e.Distribution(ctx, request.Distribution) + } return nil, wasmvmtypes.Unknown{} } @@ -201,6 +211,27 @@ func BankQuerier(bankKeeper types.BankViewKeeper) func(ctx sdk.Context, request } return json.Marshal(res) } + if request.DenomMetadata != nil { + denomMetadata, ok := bankKeeper.GetDenomMetaData(ctx, request.DenomMetadata.Denom) + if !ok { + return nil, sdkerrors.Wrap(sdkerrors.ErrNotFound, request.DenomMetadata.Denom) + } + res := wasmvmtypes.DenomMetadataResponse{ + Metadata: ConvertSdkDenomMetadataToWasmDenomMetadata(denomMetadata), + } + return json.Marshal(res) + } + if request.AllDenomMetadata != nil { + bankQueryRes, err := bankKeeper.DenomsMetadata(ctx, ConvertToDenomsMetadataRequest(request.AllDenomMetadata)) + if err != nil { + return nil, sdkerrors.ErrInvalidRequest + } + res := wasmvmtypes.AllDenomMetadataResponse{ + Metadata: ConvertSdkDenomMetadatasToWasmDenomMetadatas(bankQueryRes.Metadatas), + NextKey: bankQueryRes.Pagination.NextKey, + } + return json.Marshal(res) + } return nil, wasmvmtypes.UnsupportedRequest{Kind: "unknown BankQuery variant"} } } @@ -454,7 +485,7 @@ func getAccumulatedRewards(ctx sdk.Context, distKeeper types.DistributionKeeper, ValidatorAddress: delegation.ValidatorAddress, } cache, _ := ctx.CacheContext() - qres, err := distKeeper.DelegationRewards(sdk.WrapSDKContext(cache), ¶ms) + qres, err := distKeeper.DelegationRewards(cache, ¶ms) if err != nil { return nil, err } @@ -529,6 +560,22 @@ func WasmQuerier(k wasmQueryKeeper) func(ctx sdk.Context, request *wasmvmtypes.W } } +func DistributionQuerier(k types.DistributionKeeper) func(ctx sdk.Context, request *wasmvmtypes.DistributionQuery) ([]byte, error) { + return func(ctx sdk.Context, req *wasmvmtypes.DistributionQuery) ([]byte, error) { + if req.DelegatorWithdrawAddress == nil { + return nil, wasmvmtypes.UnsupportedRequest{Kind: "unknown distribution query"} + } + addr, err := sdk.AccAddressFromBech32(req.DelegatorWithdrawAddress.DelegatorAddress) + if err != nil { + return nil, sdkerrors.ErrInvalidAddress.Wrap("delegator address") + } + res := wasmvmtypes.DelegatorWithdrawAddressResponse{ + WithdrawAddress: k.GetDelegatorWithdrawAddr(ctx, addr).String(), + } + return json.Marshal(res) + } +} + // ConvertSdkCoinsToWasmCoins covert sdk type to wasmvm coins type func ConvertSdkCoinsToWasmCoins(coins []sdk.Coin) wasmvmtypes.Coins { converted := make(wasmvmtypes.Coins, len(coins)) @@ -546,6 +593,49 @@ func ConvertSdkCoinToWasmCoin(coin sdk.Coin) wasmvmtypes.Coin { } } +func ConvertToDenomsMetadataRequest(wasmRequest *wasmvmtypes.AllDenomMetadataQuery) *banktypes.QueryDenomsMetadataRequest { + ret := &banktypes.QueryDenomsMetadataRequest{} + if wasmRequest.Pagination != nil { + ret.Pagination = &query.PageRequest{ + Key: wasmRequest.Pagination.Key, + Limit: uint64(wasmRequest.Pagination.Limit), + Reverse: wasmRequest.Pagination.Reverse, + } + } + return ret +} + +func ConvertSdkDenomMetadatasToWasmDenomMetadatas(metadata []banktypes.Metadata) []wasmvmtypes.DenomMetadata { + converted := make([]wasmvmtypes.DenomMetadata, len(metadata)) + for i, m := range metadata { + converted[i] = ConvertSdkDenomMetadataToWasmDenomMetadata(m) + } + return converted +} + +func ConvertSdkDenomMetadataToWasmDenomMetadata(metadata banktypes.Metadata) wasmvmtypes.DenomMetadata { + return wasmvmtypes.DenomMetadata{ + Description: metadata.Description, + DenomUnits: ConvertSdkDenomUnitsToWasmDenomUnits(metadata.DenomUnits), + Base: metadata.Base, + Display: metadata.Display, + Name: metadata.Name, + Symbol: metadata.Symbol, + } +} + +func ConvertSdkDenomUnitsToWasmDenomUnits(denomUnits []*banktypes.DenomUnit) []wasmvmtypes.DenomUnit { + converted := make([]wasmvmtypes.DenomUnit, len(denomUnits)) + for i, u := range denomUnits { + converted[i] = wasmvmtypes.DenomUnit{ + Denom: u.Denom, + Exponent: u.Exponent, + Aliases: u.Aliases, + } + } + return converted +} + // ConvertProtoToJSONMarshal unmarshals the given bytes into a proto message and then marshals it to json. // This is done so that clients calling stargate queries do not need to define their own proto unmarshalers, // being able to use response directly by json marshalling, which is supported in cosmwasm. diff --git a/x/wasm/keeper/query_plugins_test.go b/x/wasm/keeper/query_plugins_test.go index 7de2236eb7..4354da80b5 100644 --- a/x/wasm/keeper/query_plugins_test.go +++ b/x/wasm/keeper/query_plugins_test.go @@ -1,9 +1,15 @@ package keeper_test import ( + "context" "encoding/hex" "encoding/json" "fmt" + "github.com/cosmos/cosmos-sdk/types/address" + distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + "github.com/tendermint/tendermint/libs/log" + "github.com/tendermint/tendermint/libs/rand" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "testing" errorsmod "cosmossdk.io/errors" @@ -353,6 +359,133 @@ func TestBankQuerierBalance(t *testing.T) { assert.Equal(t, exp, got) } +func TestBankQuerierMetadata(t *testing.T) { + metadata := banktypes.Metadata{ + Name: "Test Token", + Base: "utest", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: "utest", + Exponent: 0, + }, + }, + } + + mock := bankKeeperMock{GetDenomMetadataFn: func(ctx sdk.Context, denom string) (banktypes.Metadata, bool) { + if denom == "utest" { + return metadata, true + } else { + return banktypes.Metadata{}, false + } + }} + + ctx := sdk.Context{} + q := keeper.BankQuerier(mock) + gotBz, gotErr := q(ctx, &wasmvmtypes.BankQuery{ + DenomMetadata: &wasmvmtypes.DenomMetadataQuery{ + Denom: "utest", + }, + }) + require.NoError(t, gotErr) + var got wasmvmtypes.DenomMetadataResponse + require.NoError(t, json.Unmarshal(gotBz, &got)) + exp := wasmvmtypes.DenomMetadata{ + Name: "Test Token", + Base: "utest", + DenomUnits: []wasmvmtypes.DenomUnit{ + { + Denom: "utest", + Exponent: 0, + }, + }, + } + assert.Equal(t, exp, got.Metadata) + + _, gotErr2 := q(ctx, &wasmvmtypes.BankQuery{ + DenomMetadata: &wasmvmtypes.DenomMetadataQuery{ + Denom: "uatom", + }, + }) + require.Error(t, gotErr2) + assert.Contains(t, gotErr2.Error(), "uatom: not found") +} + +func TestBankQuerierAllMetadata(t *testing.T) { + metadata := []banktypes.Metadata{ + { + Name: "Test Token", + Base: "utest", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: "utest", + Exponent: 0, + }, + }, + }, + } + + mock := bankKeeperMock{GetDenomsMetadataFn: func(ctx context.Context, req *banktypes.QueryDenomsMetadataRequest) (*banktypes.QueryDenomsMetadataResponse, error) { + return &banktypes.QueryDenomsMetadataResponse{ + Metadatas: metadata, + Pagination: &query.PageResponse{}, + }, nil + }} + + ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) + q := keeper.BankQuerier(mock) + gotBz, gotErr := q(ctx, &wasmvmtypes.BankQuery{ + AllDenomMetadata: &wasmvmtypes.AllDenomMetadataQuery{}, + }) + require.NoError(t, gotErr) + var got wasmvmtypes.AllDenomMetadataResponse + require.NoError(t, json.Unmarshal(gotBz, &got)) + exp := wasmvmtypes.AllDenomMetadataResponse{ + Metadata: []wasmvmtypes.DenomMetadata{ + { + Name: "Test Token", + Base: "utest", + DenomUnits: []wasmvmtypes.DenomUnit{ + { + Denom: "utest", + Exponent: 0, + }, + }, + }, + }, + } + assert.Equal(t, exp, got) +} + +func TestBankQuerierAllMetadataPagination(t *testing.T) { + var capturedPagination *query.PageRequest + mock := bankKeeperMock{GetDenomsMetadataFn: func(ctx context.Context, req *banktypes.QueryDenomsMetadataRequest) (*banktypes.QueryDenomsMetadataResponse, error) { + capturedPagination = req.Pagination + return &banktypes.QueryDenomsMetadataResponse{ + Metadatas: []banktypes.Metadata{}, + Pagination: &query.PageResponse{ + NextKey: nil, + }, + }, nil + }} + + ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) + q := keeper.BankQuerier(mock) + _, gotErr := q(ctx, &wasmvmtypes.BankQuery{ + AllDenomMetadata: &wasmvmtypes.AllDenomMetadataQuery{ + Pagination: &wasmvmtypes.PageRequest{ + Key: []byte("key"), + Limit: 10, + }, + }, + }) + require.NoError(t, gotErr) + exp := &query.PageRequest{ + Key: []byte("key"), + Limit: 10, + } + assert.Equal(t, exp, capturedPagination) +} + func TestContractInfoWasmQuerier(t *testing.T) { myValidContractAddr := keeper.RandomBech32AccountAddress(t) myCreatorAddr := keeper.RandomBech32AccountAddress(t) @@ -457,6 +590,82 @@ func TestContractInfoWasmQuerier(t *testing.T) { } } +func TestDistributionQuerier(t *testing.T) { + ctx := sdk.Context{} + var myAddr sdk.AccAddress = rand.Bytes(address.Len) + var myOtherAddr sdk.AccAddress = rand.Bytes(address.Len) + specs := map[string]struct { + q wasmvmtypes.DistributionQuery + mockFn func(ctx sdk.Context, delAddr sdk.AccAddress) sdk.AccAddress + expAddr string + expErr bool + }{ + "withdrawal override": { + q: wasmvmtypes.DistributionQuery{ + DelegatorWithdrawAddress: &wasmvmtypes.DelegatorWithdrawAddressQuery{DelegatorAddress: myAddr.String()}, + }, + mockFn: func(_ sdk.Context, delAddr sdk.AccAddress) sdk.AccAddress { + return myOtherAddr + }, + expAddr: myOtherAddr.String(), + }, + "no withdrawal override": { + q: wasmvmtypes.DistributionQuery{ + DelegatorWithdrawAddress: &wasmvmtypes.DelegatorWithdrawAddressQuery{DelegatorAddress: myAddr.String()}, + }, + mockFn: func(_ sdk.Context, delAddr sdk.AccAddress) sdk.AccAddress { + return delAddr + }, + expAddr: myAddr.String(), + }, + "empty address": { + q: wasmvmtypes.DistributionQuery{ + DelegatorWithdrawAddress: &wasmvmtypes.DelegatorWithdrawAddressQuery{}, + }, + expErr: true, + }, + "unknown query": { + q: wasmvmtypes.DistributionQuery{}, + expErr: true, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + mock := distrKeeperMock{GetDelegatorWithdrawAddrFn: spec.mockFn} + q := keeper.DistributionQuerier(mock) + + gotBz, gotErr := q(ctx, &spec.q) + if spec.expErr { + require.Error(t, gotErr) + return + } + require.NoError(t, gotErr) + var rsp wasmvmtypes.DelegatorWithdrawAddressResponse + require.NoError(t, json.Unmarshal(gotBz, &rsp)) + assert.Equal(t, spec.expAddr, rsp.WithdrawAddress) + }) + } +} + +type distrKeeperMock struct { + DelegationRewardsFn func(c context.Context, req *distributiontypes.QueryDelegationRewardsRequest) (*distributiontypes.QueryDelegationRewardsResponse, error) + GetDelegatorWithdrawAddrFn func(ctx sdk.Context, delAddr sdk.AccAddress) sdk.AccAddress +} + +func (m distrKeeperMock) DelegationRewards(ctx context.Context, req *distributiontypes.QueryDelegationRewardsRequest) (*distributiontypes.QueryDelegationRewardsResponse, error) { + if m.DelegationRewardsFn == nil { + panic("not expected to be called") + } + return m.DelegationRewardsFn(ctx, req) +} + +func (m distrKeeperMock) GetDelegatorWithdrawAddr(ctx sdk.Context, delAddr sdk.AccAddress) sdk.AccAddress { + if m.GetDelegatorWithdrawAddrFn == nil { + panic("not expected to be called") + } + return m.GetDelegatorWithdrawAddrFn(ctx, delAddr) +} + func TestCodeInfoWasmQuerier(t *testing.T) { myCreatorAddr := keeper.RandomBech32AccountAddress(t) var ctx sdk.Context @@ -604,9 +813,11 @@ func (m mockWasmQueryKeeper) GetCodeInfo(ctx sdk.Context, codeID uint64) *types. } type bankKeeperMock struct { - GetSupplyFn func(ctx sdk.Context, denom string) sdk.Coin - GetBalanceFn func(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin - GetAllBalancesFn func(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins + GetSupplyFn func(ctx sdk.Context, denom string) sdk.Coin + GetBalanceFn func(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin + GetAllBalancesFn func(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins + GetDenomMetadataFn func(ctx sdk.Context, denom string) (banktypes.Metadata, bool) + GetDenomsMetadataFn func(ctx context.Context, req *banktypes.QueryDenomsMetadataRequest) (*banktypes.QueryDenomsMetadataResponse, error) } func (m bankKeeperMock) GetSupply(ctx sdk.Context, denom string) sdk.Coin { @@ -630,6 +841,20 @@ func (m bankKeeperMock) GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk return m.GetAllBalancesFn(ctx, addr) } +func (m bankKeeperMock) GetDenomMetaData(ctx sdk.Context, denom string) (banktypes.Metadata, bool) { + if m.GetDenomMetadataFn == nil { + panic("not expected to be called") + } + return m.GetDenomMetadataFn(ctx, denom) +} + +func (m bankKeeperMock) DenomsMetadata(ctx context.Context, req *banktypes.QueryDenomsMetadataRequest) (*banktypes.QueryDenomsMetadataResponse, error) { + if m.GetDenomsMetadataFn == nil { + panic("not expected to be called") + } + return m.GetDenomsMetadataFn(ctx, req) +} + func TestConvertProtoToJSONMarshal(t *testing.T) { testCases := []struct { name string diff --git a/x/wasm/keeper/reflect_test.go b/x/wasm/keeper/reflect_test.go index af33fd9f34..48c04260c1 100644 --- a/x/wasm/keeper/reflect_test.go +++ b/x/wasm/keeper/reflect_test.go @@ -2,6 +2,7 @@ package keeper import ( "encoding/json" + "fmt" "os" "strings" "testing" @@ -36,7 +37,10 @@ func mustParse(t *testing.T, data []byte, res interface{}) { require.NoError(t, err) } -const ReflectFeatures = "staking,mask,stargate,cosmwasm_1_1" +const ( + ReflectFeatures = "staking,mask,stargate,cosmwasm_1_1" + CyberpunkFeatures = "staking,mask,stargate,cosmwasm_1_1,cosmwasm_1_2,cosmwasm_1_3" +) func TestReflectContractSend(t *testing.T) { cdc := MakeEncodingConfig(t).Marshaler @@ -546,7 +550,7 @@ func TestWasmRawQueryWithNil(t *testing.T) { } func TestRustPanicIsHandled(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, ReflectFeatures) + ctx, keepers := CreateTestInput(t, false, CyberpunkFeatures) keeper := keepers.ContractKeeper creator := keepers.Faucet.NewFundedRandomAccount(ctx, sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))...) @@ -568,6 +572,117 @@ func TestRustPanicIsHandled(t *testing.T) { assert.Nil(t, gotData) } +func TestQueryDenomsIntegration(t *testing.T) { + ctx, keepers := CreateTestInput(t, false, CyberpunkFeatures) + ck, k := keepers.ContractKeeper, keepers.WasmKeeper + creator := keepers.Faucet.NewFundedRandomAccount(ctx, sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))...) + + // upload code + codeID, _, err := ck.Create(ctx, creator, testdata.CyberpunkContractWasm(), nil) + require.NoError(t, err) + + contractAddr, _, err := ck.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "cyberpunk contract", nil) + require.NoError(t, err) + + var ( + metadata1 = banktypes.Metadata{ + Description: "testing", + DenomUnits: []*banktypes.DenomUnit{ + {Denom: "ualx", Exponent: 0, Aliases: []string{"microalx"}}, + {Denom: "alx", Exponent: 6, Aliases: []string{"ALX"}}, + }, + Base: "ualx", + Display: "alx", + Name: "my test denom", + Symbol: "XALX", + } + metadata2 = banktypes.Metadata{ + Description: "testing2", + DenomUnits: []*banktypes.DenomUnit{ + {Denom: "ublx", Exponent: 0, Aliases: []string{"microblx"}}, + {Denom: "blx", Exponent: 6, Aliases: []string{"BLX"}}, + }, + Base: "ublx", + Display: "blx", + Name: "my other test denom", + Symbol: "XBLX", + } + ) + type dict map[string]any + + keepers.BankKeeper.SetDenomMetaData(ctx, metadata1) + keepers.BankKeeper.SetDenomMetaData(ctx, metadata2) + + specs := map[string]struct { + query string + exp []byte + expErr *sdkerrors.Error + }{ + "all denoms": { + query: `{"denoms":{}}`, + exp: mustMarshal(t, []dict{ + { + "description": "testing", + "denom_units": []dict{ + {"denom": "ualx", "exponent": 0, "aliases": []string{"microalx"}}, + {"denom": "alx", "exponent": 6, "aliases": []string{"ALX"}}, + }, + "base": "ualx", + "display": "alx", + "name": "my test denom", + "symbol": "XALX", + "uri": "", + "uri_hash": "", + }, { + "description": "testing2", + "denom_units": []dict{ + {"denom": "ublx", "exponent": 0, "aliases": []string{"microblx"}}, + {"denom": "blx", "exponent": 6, "aliases": []string{"BLX"}}, + }, + "base": "ublx", + "display": "blx", + "name": "my other test denom", + "symbol": "XBLX", + "uri": "", + "uri_hash": "", + }, + }), + }, + "single denom": { + query: `{"denom":{"denom":"ublx"}}`, + exp: mustMarshal(t, dict{ + "description": "testing2", + "denom_units": []dict{ + {"denom": "ublx", "exponent": 0, "aliases": []string{"microblx"}}, + {"denom": "blx", "exponent": 6, "aliases": []string{"BLX"}}, + }, + "base": "ublx", + "display": "blx", + "name": "my other test denom", + "symbol": "XBLX", + "uri": "", + "uri_hash": "", + }), + }, + "unknown denom": { + query: `{"denom":{"denom":"unknown"}}`, + expErr: sdkerrors.ErrNotFound, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + gotData, gotErr := k.QuerySmart(ctx, contractAddr, []byte(spec.query)) + if spec.expErr != nil { + require.Error(t, gotErr) + assert.Contains(t, gotErr.Error(), fmt.Sprintf("codespace: %s, code: %d:", spec.expErr.Codespace(), spec.expErr.ABCICode())) + return + } + require.NoError(t, gotErr) + assert.JSONEq(t, string(spec.exp), string(gotData), string(gotData)) + }) + } +} + func CheckAccount(t *testing.T, ctx sdk.Context, accKeeper authkeeper.AccountKeeper, bankKeeper bankkeeper.Keeper, addr sdk.AccAddress, expected sdk.Coins) { acct := accKeeper.GetAccount(ctx, addr) if expected == nil { diff --git a/x/wasm/keeper/snapshotter.go b/x/wasm/keeper/snapshotter.go index 075643f851..e9ee1eee72 100644 --- a/x/wasm/keeper/snapshotter.go +++ b/x/wasm/keeper/snapshotter.go @@ -3,6 +3,7 @@ package keeper import ( "encoding/hex" "io" + "math" errorsmod "cosmossdk.io/errors" snapshot "github.com/cosmos/cosmos-sdk/snapshots/types" @@ -110,13 +111,13 @@ func restoreV1(_ sdk.Context, k *Keeper, compressedCode []byte) error { if !ioutils.IsGzip(compressedCode) { return types.ErrInvalid.Wrap("not a gzip") } - wasmCode, err := ioutils.Uncompress(compressedCode, uint64(types.MaxWasmSize)) + wasmCode, err := ioutils.Uncompress(compressedCode, math.MaxInt64) if err != nil { return errorsmod.Wrap(types.ErrCreateFailed, err.Error()) } // FIXME: check which codeIDs the checksum matches?? - _, err = k.wasmVM.Create(wasmCode) + _, err = k.wasmVM.StoreCodeUnchecked(wasmCode) if err != nil { return errorsmod.Wrap(types.ErrCreateFailed, err.Error()) } diff --git a/x/wasm/keeper/snapshotter_integration_test.go b/x/wasm/keeper/snapshotter_integration_test.go index 202a5a843d..a23662b6f8 100644 --- a/x/wasm/keeper/snapshotter_integration_test.go +++ b/x/wasm/keeper/snapshotter_integration_test.go @@ -1,7 +1,8 @@ package keeper_test import ( - "crypto/sha256" + wasmvm "github.com/CosmWasm/wasmvm" + wasmvmtypes "github.com/CosmWasm/wasmvm/types" "os" "testing" "time" @@ -67,6 +68,12 @@ func TestSnapshotter(t *testing.T) { require.NoError(t, err) assert.NotNil(t, snapshot) + originalMaxWasmSize := types.MaxWasmSize + types.MaxWasmSize = 1 + t.Cleanup(func() { + types.MaxWasmSize = originalMaxWasmSize + }) + // when snapshot imported into dest app instance destWasmApp := app.SetupWithEmptyStore(t) require.NoError(t, destWasmApp.SnapshotManager().Restore(*snapshot)) @@ -92,9 +99,10 @@ func TestSnapshotter(t *testing.T) { wasmKeeper.IterateCodeInfos(ctx, func(id uint64, info types.CodeInfo) bool { bz, err := wasmKeeper.GetByteCode(ctx, id) require.NoError(t, err) - hash := sha256.Sum256(bz) + hash, err := wasmvm.CreateChecksum(bz) + require.NoError(t, err) destCodeIDToChecksum[id] = hash[:] - assert.Equal(t, hash[:], info.CodeHash) + assert.Equal(t, hash[:], wasmvmtypes.Checksum(info.CodeHash)) return false }) assert.Equal(t, srcCodeIDToChecksum, destCodeIDToChecksum) diff --git a/x/wasm/keeper/testdata/version.txt b/x/wasm/keeper/testdata/version.txt index 79127d85a4..31899b9631 100644 --- a/x/wasm/keeper/testdata/version.txt +++ b/x/wasm/keeper/testdata/version.txt @@ -1 +1,3 @@ -v1.2.0 +v1.3.0 +burner.wasm: v1.2.0 +ibc-reflect.wasm: custom build, see: https://github.com/CosmWasm/cosmwasm/pull/1690 \ No newline at end of file diff --git a/x/wasm/keeper/wasmtesting/mock_engine.go b/x/wasm/keeper/wasmtesting/mock_engine.go index f72c80435a..df13b33769 100644 --- a/x/wasm/keeper/wasmtesting/mock_engine.go +++ b/x/wasm/keeper/wasmtesting/mock_engine.go @@ -3,6 +3,7 @@ package wasmtesting import ( "bytes" "crypto/sha256" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" errorsmod "cosmossdk.io/errors" wasmvm "github.com/CosmWasm/wasmvm" @@ -17,25 +18,27 @@ var _ types.WasmerEngine = &MockWasmer{} // MockWasmer implements types.WasmerEngine for testing purpose. One or multiple messages can be stubbed. // Without a stub function a panic is thrown. type MockWasmer struct { - CreateFn func(codeID wasmvm.WasmCode) (wasmvm.Checksum, error) - AnalyzeCodeFn func(codeID wasmvm.Checksum) (*wasmvmtypes.AnalysisReport, error) - InstantiateFn func(codeID 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) - ExecuteFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, info wasmvmtypes.MessageInfo, executeMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) - QueryFn func(codeID 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) - MigrateFn func(codeID 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) - SudoFn func(codeID 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) - ReplyFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, reply wasmvmtypes.Reply, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) - GetCodeFn func(codeID wasmvm.Checksum) (wasmvm.WasmCode, error) - CleanupFn func() - IBCChannelOpenFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, msg wasmvmtypes.IBCChannelOpenMsg, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.IBC3ChannelOpenResponse, uint64, error) - IBCChannelConnectFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, msg wasmvmtypes.IBCChannelConnectMsg, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.IBCBasicResponse, uint64, error) - IBCChannelCloseFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, msg wasmvmtypes.IBCChannelCloseMsg, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.IBCBasicResponse, uint64, error) - IBCPacketReceiveFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, msg wasmvmtypes.IBCPacketReceiveMsg, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.IBCReceiveResult, uint64, error) - IBCPacketAckFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, msg wasmvmtypes.IBCPacketAckMsg, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.IBCBasicResponse, uint64, error) - IBCPacketTimeoutFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, msg wasmvmtypes.IBCPacketTimeoutMsg, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.IBCBasicResponse, uint64, error) - PinFn func(checksum wasmvm.Checksum) error - UnpinFn func(checksum wasmvm.Checksum) error - GetMetricsFn func() (*wasmvmtypes.Metrics, error) + CreateFn func(codeID wasmvm.WasmCode) (wasmvm.Checksum, error) + StoreCodeFn func(codeID wasmvm.WasmCode) (wasmvm.Checksum, error) + StoreCodeUncheckedFn func(codeID wasmvm.WasmCode) (wasmvm.Checksum, error) + AnalyzeCodeFn func(codeID wasmvm.Checksum) (*wasmvmtypes.AnalysisReport, error) + InstantiateFn func(codeID 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) + ExecuteFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, info wasmvmtypes.MessageInfo, executeMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) + QueryFn func(codeID 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) + MigrateFn func(codeID 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) + SudoFn func(codeID 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) + ReplyFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, reply wasmvmtypes.Reply, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) + GetCodeFn func(codeID wasmvm.Checksum) (wasmvm.WasmCode, error) + CleanupFn func() + IBCChannelOpenFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, msg wasmvmtypes.IBCChannelOpenMsg, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.IBC3ChannelOpenResponse, uint64, error) + IBCChannelConnectFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, msg wasmvmtypes.IBCChannelConnectMsg, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.IBCBasicResponse, uint64, error) + IBCChannelCloseFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, msg wasmvmtypes.IBCChannelCloseMsg, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.IBCBasicResponse, uint64, error) + IBCPacketReceiveFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, msg wasmvmtypes.IBCPacketReceiveMsg, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.IBCReceiveResult, uint64, error) + IBCPacketAckFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, msg wasmvmtypes.IBCPacketAckMsg, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.IBCBasicResponse, uint64, error) + IBCPacketTimeoutFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, msg wasmvmtypes.IBCPacketTimeoutMsg, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.IBCBasicResponse, uint64, error) + PinFn func(checksum wasmvm.Checksum) error + UnpinFn func(checksum wasmvm.Checksum) error + GetMetricsFn func() (*wasmvmtypes.Metrics, error) } func (m *MockWasmer) IBCChannelOpen(codeID wasmvm.Checksum, env wasmvmtypes.Env, msg wasmvmtypes.IBCChannelOpenMsg, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.IBC3ChannelOpenResponse, uint64, error) { @@ -87,6 +90,20 @@ func (m *MockWasmer) Create(codeID wasmvm.WasmCode) (wasmvm.Checksum, error) { return m.CreateFn(codeID) } +func (m *MockWasmer) StoreCode(codeID wasmvm.WasmCode) (wasmvm.Checksum, error) { + if m.StoreCodeFn == nil { + panic("not supposed to be called!") + } + return m.StoreCodeFn(codeID) +} + +func (m *MockWasmer) StoreCodeUnchecked(codeID wasmvm.WasmCode) (wasmvm.Checksum, error) { + if m.StoreCodeUncheckedFn == nil { + panic("not supposed to be called!") + } + return m.StoreCodeUncheckedFn(codeID) +} + func (m *MockWasmer) AnalyzeCode(codeID wasmvm.Checksum) (*wasmvmtypes.AnalysisReport, error) { if m.AnalyzeCodeFn == nil { panic("not supposed to be called!") @@ -291,6 +308,7 @@ type contractExecutable interface { // MakeInstantiable adds some noop functions to not fail when contract is used for instantiation func MakeInstantiable(m *MockWasmer) { m.CreateFn = HashOnlyCreateFn + m.StoreCodeFn = HashOnlyStoreCodeFn m.InstantiateFn = NoOpInstantiateFn m.AnalyzeCodeFn = WithoutIBCAnalyzeFn } @@ -329,6 +347,13 @@ func HashOnlyCreateFn(code wasmvm.WasmCode) (wasmvm.Checksum, error) { return hash[:], nil } +func HashOnlyStoreCodeFn(code wasmvm.WasmCode) (wasmvm.Checksum, error) { + if code == nil { + return nil, sdkerrors.Wrap(types.ErrInvalid, "wasm code must not be nil") + } + return wasmvm.CreateChecksum(code) +} + func NoOpInstantiateFn(wasmvm.Checksum, wasmvmtypes.Env, wasmvmtypes.MessageInfo, []byte, wasmvm.KVStore, wasmvm.GoAPI, wasmvm.Querier, wasmvm.GasMeter, uint64, wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { return &wasmvmtypes.Response{}, 0, nil } @@ -337,6 +362,10 @@ func NoOpCreateFn(_ wasmvm.WasmCode) (wasmvm.Checksum, error) { return rand.Bytes(32), nil } +func NoOpStoreCodeFn(_ wasmvm.WasmCode) (wasmvm.Checksum, error) { + return rand.Bytes(32), nil +} + func HasIBCAnalyzeFn(wasmvm.Checksum) (*wasmvmtypes.AnalysisReport, error) { return &wasmvmtypes.AnalysisReport{ HasIBCEntryPoints: true, diff --git a/x/wasm/module.go b/x/wasm/module.go index 5d91b519eb..6596cb4bc6 100644 --- a/x/wasm/module.go +++ b/x/wasm/module.go @@ -35,9 +35,10 @@ var ( // Module init related flags const ( - flagWasmMemoryCacheSize = "wasm.memory_cache_size" - flagWasmQueryGasLimit = "wasm.query_gas_limit" - flagWasmSimulationGasLimit = "wasm.simulation_gas_limit" + flagWasmMemoryCacheSize = "wasm.memory_cache_size" + flagWasmQueryGasLimit = "wasm.query_gas_limit" + flagWasmSimulationGasLimit = "wasm.simulation_gas_limit" + flagWasmSkipWasmVMVersionCheck = "wasm.skip_wasmvm_version_check" //nolint:gosec ) // AppModuleBasic defines the basic application module used by the wasm module. @@ -220,8 +221,20 @@ func AddModuleInitFlags(startCmd *cobra.Command) { startCmd.Flags().Uint32(flagWasmMemoryCacheSize, defaults.MemoryCacheSize, "Sets the size in MiB (NOT bytes) of an in-memory cache for Wasm modules. Set to 0 to disable.") startCmd.Flags().Uint64(flagWasmQueryGasLimit, defaults.SmartQueryGasLimit, "Set the max gas that can be spent on executing a query with a Wasm contract") startCmd.Flags().String(flagWasmSimulationGasLimit, "", "Set the max gas that can be spent when executing a simulation TX") + startCmd.Flags().Bool(flagWasmSkipWasmVMVersionCheck, false, "Skip check that ensures that libwasmvm version (the Rust project) and wasmvm version (the Go project) match") - startCmd.PreRunE = chainPreRuns(checkLibwasmVersion, startCmd.PreRunE) + preCheck := func(cmd *cobra.Command, _ []string) error { + skip, err := cmd.Flags().GetBool(flagWasmSkipWasmVMVersionCheck) + if err != nil { + return fmt.Errorf("unable to read skip flag value: %w", err) + } + if skip { + cmd.Println("libwasmvm version check skipped") + return nil + } + return CheckLibwasmVersion(getExpectedLibwasmVersion()) + } + startCmd.PreRunE = chainPreRuns(preCheck, startCmd.PreRunE) } // ReadWasmConfig reads the wasm specifig configuration @@ -273,15 +286,25 @@ func getExpectedLibwasmVersion() string { return "" } -func checkLibwasmVersion(_ *cobra.Command, _ []string) error { +// CheckLibwasmVersion ensures that the libwasmvm version loaded at runtime matches the version +// of the github.com/CosmWasm/wasmvm dependency in go.mod. This us useful when dealing with +// shared libraries that are copied or moved from their default location, e.g. when building the node +// on one machine and deploying it to other machines. +// +// Usually the libwasmvm version (the Rust project) and wasmvm version (the Go project) match. However, +// there are situations in which this is not the case. This can be during development or if one of the +// two is patched. In such cases it is advised to not execute the check. +// +// An alternative method to obtain the libwasmvm version loaded at runtime is executing +// `wasmd query wasm libwasmvm-version`. +func CheckLibwasmVersion(wasmExpectedVersion string) error { + if wasmExpectedVersion == "" { + return fmt.Errorf("wasmvm module not exist") + } wasmVersion, err := wasmvm.LibwasmvmVersion() if err != nil { return fmt.Errorf("unable to retrieve libwasmversion %w", err) } - wasmExpectedVersion := getExpectedLibwasmVersion() - if wasmExpectedVersion == "" { - return fmt.Errorf("wasmvm module not exist") - } if !strings.Contains(wasmExpectedVersion, wasmVersion) { return fmt.Errorf("libwasmversion mismatch. got: %s; expected: %s", wasmVersion, wasmExpectedVersion) } diff --git a/x/wasm/types/expected_keepers.go b/x/wasm/types/expected_keepers.go index 82547cadc4..3f13d5c1e9 100644 --- a/x/wasm/types/expected_keepers.go +++ b/x/wasm/types/expected_keepers.go @@ -2,11 +2,11 @@ package types import ( "context" - sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" - "github.com/cosmos/cosmos-sdk/x/distribution/types" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" clienttypes "github.com/cosmos/ibc-go/v6/modules/core/02-client/types" connectiontypes "github.com/cosmos/ibc-go/v6/modules/core/03-connection/types" @@ -19,6 +19,8 @@ type BankViewKeeper interface { GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin GetSupply(ctx sdk.Context, denom string) sdk.Coin + GetDenomMetaData(ctx sdk.Context, denom string) (banktypes.Metadata, bool) + DenomsMetadata(ctx context.Context, req *banktypes.QueryDenomsMetadataRequest) (*banktypes.QueryDenomsMetadataResponse, error) } // Burner is a subset of the sdk bank keeper methods @@ -48,7 +50,8 @@ type AccountKeeper interface { // DistributionKeeper defines a subset of methods implemented by the cosmos-sdk distribution keeper type DistributionKeeper interface { - DelegationRewards(c context.Context, req *types.QueryDelegationRewardsRequest) (*types.QueryDelegationRewardsResponse, error) + DelegationRewards(ctx context.Context, req *distrtypes.QueryDelegationRewardsRequest) (*distrtypes.QueryDelegationRewardsResponse, error) + GetDelegatorWithdrawAddr(ctx sdk.Context, delAddr sdk.AccAddress) sdk.AccAddress } // StakingKeeper defines a subset of methods implemented by the cosmos-sdk staking keeper diff --git a/x/wasm/types/wasmer_engine.go b/x/wasm/types/wasmer_engine.go index 9b2efdef50..d4c5d037ec 100644 --- a/x/wasm/types/wasmer_engine.go +++ b/x/wasm/types/wasmer_engine.go @@ -21,6 +21,20 @@ type WasmerEngine interface { // be instantiated with custom inputs in the future. Create(code wasmvm.WasmCode) (wasmvm.Checksum, error) + // Create will compile the wasm code, and store the resulting pre-compile + // as well as the original code. Both can be referenced later via checksum + // This must be done one time for given code, after which it can be + // instatitated 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) + + // Create will compile the wasm code, and store the resulting pre-compile + // as well as the original code. Both can be referenced later via checksum + // This must be done one time for given code, after which it can be + // instatitated many times, and each instance called many times. + // It does the same as StoreCode but without the static checks. + StoreCodeUnchecked(code wasmvm.WasmCode) (wasmvm.Checksum, error) + // AnalyzeCode will statically analyze the code. // Currently just reports if it exposes all IBC entry points. AnalyzeCode(checksum wasmvm.Checksum) (*wasmvmtypes.AnalysisReport, error)