diff --git a/Dockerfile b/Dockerfile index 649cf7eb5b..de15ee3218 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,12 +14,11 @@ RUN apk add git WORKDIR /code COPY . /code/ - # See https://github.com/CosmWasm/wasmvm/releases -ADD https://github.com/CosmWasm/wasmvm/releases/download/v1.1.1/libwasmvm_muslc.aarch64.a /lib/libwasmvm_muslc.aarch64.a -ADD https://github.com/CosmWasm/wasmvm/releases/download/v1.1.1/libwasmvm_muslc.x86_64.a /lib/libwasmvm_muslc.x86_64.a -RUN sha256sum /lib/libwasmvm_muslc.aarch64.a | grep 9ecb037336bd56076573dc18c26631a9d2099a7f2b40dc04b6cae31ffb4c8f9a -RUN sha256sum /lib/libwasmvm_muslc.x86_64.a | grep 6e4de7ba9bad4ae9679c7f9ecf7e283dd0160e71567c6a7be6ae47c81ebe7f32 +ADD https://github.com/CosmWasm/wasmvm/releases/download/v1.2.0/libwasmvm_muslc.aarch64.a /lib/libwasmvm_muslc.aarch64.a +ADD https://github.com/CosmWasm/wasmvm/releases/download/v1.2.0/libwasmvm_muslc.x86_64.a /lib/libwasmvm_muslc.x86_64.a +RUN sha256sum /lib/libwasmvm_muslc.aarch64.a | grep cba4b334893456c64df177939cbdd09afe4812432c02ae37d60d69a111b1b50d +RUN sha256sum /lib/libwasmvm_muslc.x86_64.a | grep 6f87082f7a62602f9725d529677f330b9c4dd4607887be52a86328c6c919495b # Copy the library you want to the final location that will be found by the linker flag `-lwasmvm_muslc` RUN cp /lib/libwasmvm_muslc.${arch}.a /lib/libwasmvm_muslc.a diff --git a/README.md b/README.md index 367492f4f1..e85a21f328 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ + # Wasm Zone [![CircleCI](https://circleci.com/gh/CosmWasm/wasmd/tree/main.svg?style=shield)](https://circleci.com/gh/CosmWasm/wasmd/tree/main) @@ -26,6 +27,7 @@ compatibility list: | wasmd | wasmvm | cosmwasm-vm | cosmwasm-std | |-------|--------------|-------------|--------------| +| 0.31 | v1.2.0 | | 1.0-1.2 | | 0.30 | v1.1.0 | | 1.0-1.1 | | 0.29 | v1.1.0 | | 1.0-1.1 | | 0.28 | v1.0.0 | | 1.0-1.1 | diff --git a/app/app.go b/app/app.go index 7a13443d43..69e22aa5dd 100644 --- a/app/app.go +++ b/app/app.go @@ -590,7 +590,8 @@ func NewWasmApp( // The last arguments can contain custom message handlers, and custom query handlers, // if we want to allow any custom callbacks - availableCapabilities := "iterator,staking,stargate,cosmwasm_1_1" + // See https://github.com/CosmWasm/cosmwasm/blob/main/docs/CAPABILITIES-BUILT-IN.md + availableCapabilities := "iterator,staking,stargate,cosmwasm_1_1,cosmwasm_1_2" app.WasmKeeper = wasm.NewKeeper( appCodec, keys[wasm.StoreKey], diff --git a/go.mod b/go.mod index f62c2c16ce..5539fb507e 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/CosmWasm/wasmd go 1.19 require ( - github.com/CosmWasm/wasmvm v1.1.1 + github.com/CosmWasm/wasmvm v1.2.0 github.com/cosmos/cosmos-proto v1.0.0-beta.1 github.com/cosmos/cosmos-sdk v0.47.0-rc1.0.20230116204658-efb7acbf244f github.com/cosmos/gogogateway v1.2.0 // indirect @@ -24,7 +24,6 @@ require ( github.com/spf13/cast v1.5.0 github.com/spf13/cobra v1.6.1 github.com/spf13/pflag v1.0.5 - github.com/spf13/viper v1.14.0 github.com/stretchr/testify v1.8.1 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 github.com/tendermint/tendermint v0.37.0-rc2 @@ -37,8 +36,10 @@ require ( require ( cosmossdk.io/api v0.2.6 cosmossdk.io/core v0.3.2 + cosmossdk.io/errors v1.0.0-beta.7 cosmossdk.io/math v1.0.0-beta.4 cosmossdk.io/tools/rosetta v0.2.0 + github.com/spf13/viper v1.14.0 ) require ( @@ -48,7 +49,6 @@ require ( cloud.google.com/go/iam v0.7.0 // indirect cloud.google.com/go/storage v1.27.0 // indirect cosmossdk.io/depinject v1.0.0-alpha.3 // indirect - cosmossdk.io/errors v1.0.0-beta.7 // indirect filippo.io/edwards25519 v1.0.0-rc.1 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/99designs/keyring v1.2.1 // indirect diff --git a/go.sum b/go.sum index f421b63477..1bd245bd07 100644 --- a/go.sum +++ b/go.sum @@ -68,8 +68,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d h1:nalkkPQcITbvhmL4+C4cKA87NW0tfm3Kl9VXRoPywFg= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= -github.com/CosmWasm/wasmvm v1.1.1 h1:0xtdrmmsP9fibe+x42WcMkp5aQ738BICgcH3FNVLzm4= -github.com/CosmWasm/wasmvm v1.1.1/go.mod h1:ei0xpvomwSdONsxDuONzV7bL1jSET1M8brEx0FCXc+A= +github.com/CosmWasm/wasmvm v1.2.0 h1:pNCp175id+r/dSa4Ii5zoTkmauOoeipkvepvEJM1bao= +github.com/CosmWasm/wasmvm v1.2.0/go.mod h1:OIhXFPi9BbcEL1USBj4OIrBTtSSds+9eEql56fsdyfE= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= diff --git a/x/wasm/client/cli/gov_tx_test.go b/x/wasm/client/cli/gov_tx_test.go index 147b9f61b1..265cad4939 100644 --- a/x/wasm/client/cli/gov_tx_test.go +++ b/x/wasm/client/cli/gov_tx_test.go @@ -104,7 +104,7 @@ func TestParseCodeInfoFlags(t *testing.T) { wasmBin, err := os.ReadFile("../../keeper/testdata/hackatom.wasm") require.NoError(t, err) - checksumStr := "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5" + checksumStr := "beb3de5e9b93b52e514c74ce87ccddb594b9bcd33b7f1af1bb6da63fc883917b" specs := map[string]struct { args []string diff --git a/x/wasm/keeper/handler_plugin_encoders.go b/x/wasm/keeper/handler_plugin_encoders.go index 123c80b5b2..273d8a406e 100644 --- a/x/wasm/keeper/handler_plugin_encoders.go +++ b/x/wasm/keeper/handler_plugin_encoders.go @@ -238,6 +238,24 @@ func EncodeWasmMsg(sender sdk.AccAddress, msg *wasmvmtypes.WasmMsg) ([]sdk.Msg, Funds: coins, } return []sdk.Msg{&sdkMsg}, nil + case msg.Instantiate2 != nil: + coins, err := ConvertWasmCoinsToSdkCoins(msg.Instantiate2.Funds) + if err != nil { + return nil, err + } + + sdkMsg := types.MsgInstantiateContract2{ + Sender: sender.String(), + Admin: msg.Instantiate2.Admin, + CodeID: msg.Instantiate2.CodeID, + Label: msg.Instantiate2.Label, + Msg: msg.Instantiate2.Msg, + Funds: coins, + Salt: msg.Instantiate2.Salt, + // FixMsg is discouraged, see: https://medium.com/cosmwasm/dev-note-3-limitations-of-instantiate2-and-how-to-deal-with-them-a3f946874230 + FixMsg: false, + } + return []sdk.Msg{&sdkMsg}, nil case msg.Migrate != nil: sdkMsg := types.MsgMigrateContract{ Sender: sender.String(), @@ -289,14 +307,44 @@ func EncodeIBCMsg(portSource types.ICS20TransferPortSource) func(ctx sdk.Context } return []sdk.Msg{msg}, nil default: - return nil, sdkerrors.Wrap(types.ErrUnknownMsg, "Unknown variant of IBC") + return nil, sdkerrors.Wrap(types.ErrUnknownMsg, "unknown variant of IBC") } } } func EncodeGovMsg(sender sdk.AccAddress, msg *wasmvmtypes.GovMsg) ([]sdk.Msg, error) { + switch { + case msg.Vote != nil: + voteOption, err := convertVoteOption(msg.Vote.Vote) + if err != nil { + return nil, sdkerrors.Wrap(err, "vote option") + } + m := v1.NewMsgVote(sender, msg.Vote.ProposalId, voteOption, "") + return []sdk.Msg{m}, nil + case msg.VoteWeighted != nil: + opts := make([]*v1.WeightedVoteOption, len(msg.VoteWeighted.Options)) + for i, v := range msg.VoteWeighted.Options { + weight, err := sdk.NewDecFromStr(v.Weight) + if err != nil { + return nil, sdkerrors.Wrapf(err, "weight for vote %d", i+1) + } + voteOption, err := convertVoteOption(v.Option) + if err != nil { + return nil, sdkerrors.Wrap(err, "vote option") + } + opts[i] = &v1.WeightedVoteOption{Option: voteOption, Weight: weight.String()} + } + m := v1.NewMsgVoteWeighted(sender, msg.VoteWeighted.ProposalId, opts, "") + return []sdk.Msg{m}, nil + + default: + return nil, types.ErrUnknownMsg.Wrap("unknown variant of gov") + } +} + +func convertVoteOption(s interface{}) (v1.VoteOption, error) { var option v1.VoteOption - switch msg.Vote.Vote { + switch s { case wasmvmtypes.Yes: option = v1.OptionYes case wasmvmtypes.No: @@ -305,13 +353,10 @@ func EncodeGovMsg(sender sdk.AccAddress, msg *wasmvmtypes.GovMsg) ([]sdk.Msg, er option = v1.OptionNoWithVeto case wasmvmtypes.Abstain: option = v1.OptionAbstain + default: + return v1.OptionEmpty, types.ErrInvalid } - vote := &v1.MsgVote{ - ProposalId: msg.Vote.ProposalId, - Voter: sender.String(), - Option: option, - } - return []sdk.Msg{vote}, nil + return option, nil } // ConvertWasmIBCTimeoutHeightToCosmosHeight converts a wasmvm type ibc timeout height to ibc module type height diff --git a/x/wasm/keeper/handler_plugin_encoders_test.go b/x/wasm/keeper/handler_plugin_encoders_test.go index f82d668fda..5569fe044d 100644 --- a/x/wasm/keeper/handler_plugin_encoders_test.go +++ b/x/wasm/keeper/handler_plugin_encoders_test.go @@ -65,8 +65,10 @@ func TestEncoding(t *testing.T) { transferPortSource types.ICS20TransferPortSource // set if valid output []sdk.Msg - // set if invalid - isError bool + // set if expect mapping fails + expError bool + // set if sdk validate basic should fail + expInvalid bool }{ "simple send": { sender: addr1, @@ -113,7 +115,7 @@ func TestEncoding(t *testing.T) { }, }, }, - isError: true, + expError: true, }, "invalid address": { sender: addr1, @@ -130,7 +132,8 @@ func TestEncoding(t *testing.T) { }, }, }, - isError: false, // addresses are checked in the handler + expError: false, // addresses are checked in the handler + expInvalid: true, output: []sdk.Msg{ &banktypes.MsgSend{ FromAddress: addr1.String(), @@ -189,6 +192,35 @@ func TestEncoding(t *testing.T) { }, }, }, + "wasm instantiate2": { + sender: addr1, + srcMsg: wasmvmtypes.CosmosMsg{ + Wasm: &wasmvmtypes.WasmMsg{ + Instantiate2: &wasmvmtypes.Instantiate2Msg{ + CodeID: 7, + Msg: jsonMsg, + Funds: []wasmvmtypes.Coin{ + wasmvmtypes.NewCoin(123, "eth"), + }, + Label: "myLabel", + Admin: addr2.String(), + Salt: []byte("mySalt"), + }, + }, + }, + output: []sdk.Msg{ + &types.MsgInstantiateContract2{ + Sender: addr1.String(), + Admin: addr2.String(), + CodeID: 7, + Label: "myLabel", + Msg: jsonMsg, + Funds: sdk.NewCoins(sdk.NewInt64Coin("eth", 123)), + Salt: []byte("mySalt"), + FixMsg: false, + }, + }, + }, "wasm migrate": { sender: addr2, srcMsg: wasmvmtypes.CosmosMsg{ @@ -271,7 +303,8 @@ func TestEncoding(t *testing.T) { }, }, }, - isError: false, // fails in the handler + expError: false, // fails in the handler + expInvalid: true, output: []sdk.Msg{ &stakingtypes.MsgDelegate{ DelegatorAddress: addr1.String(), @@ -378,7 +411,7 @@ func TestEncoding(t *testing.T) { Value: bankMsgBin, }, }, - isError: true, + expError: true, }, "IBC transfer with block timeout": { sender: addr1, @@ -500,9 +533,49 @@ func TestEncoding(t *testing.T) { }, }, }, + } + encodingConfig := MakeEncodingConfig(t) + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + var ctx sdk.Context + encoder := DefaultEncoders(encodingConfig.Marshaler, tc.transferPortSource) + res, err := encoder.Encode(ctx, tc.sender, tc.srcContractIBCPort, tc.srcMsg) + if tc.expError { + assert.Error(t, err) + return + } else { + require.NoError(t, err) + assert.Equal(t, tc.output, res) + } + // and valid sdk message + for _, v := range res { + gotErr := v.ValidateBasic() + if tc.expInvalid { + assert.Error(t, gotErr) + } else { + assert.NoError(t, gotErr) + } + } + }) + } +} + +func TestEncodeGovMsg(t *testing.T) { + myAddr := RandomAccountAddress(t) + + cases := map[string]struct { + sender sdk.AccAddress + srcMsg wasmvmtypes.CosmosMsg + transferPortSource types.ICS20TransferPortSource + // set if valid + output []sdk.Msg + // set if expect mapping fails + expError bool + // set if sdk validate basic should fail + expInvalid bool + }{ "Gov vote: yes": { - sender: addr1, - srcContractIBCPort: "myIBCPort", + sender: myAddr, srcMsg: wasmvmtypes.CosmosMsg{ Gov: &wasmvmtypes.GovMsg{ Vote: &wasmvmtypes.VoteMsg{ProposalId: 1, Vote: wasmvmtypes.Yes}, @@ -511,14 +584,13 @@ func TestEncoding(t *testing.T) { output: []sdk.Msg{ &v1.MsgVote{ ProposalId: 1, - Voter: addr1.String(), + Voter: myAddr.String(), Option: v1.OptionYes, }, }, }, "Gov vote: No": { - sender: addr1, - srcContractIBCPort: "myIBCPort", + sender: myAddr, srcMsg: wasmvmtypes.CosmosMsg{ Gov: &wasmvmtypes.GovMsg{ Vote: &wasmvmtypes.VoteMsg{ProposalId: 1, Vote: wasmvmtypes.No}, @@ -527,14 +599,13 @@ func TestEncoding(t *testing.T) { output: []sdk.Msg{ &v1.MsgVote{ ProposalId: 1, - Voter: addr1.String(), + Voter: myAddr.String(), Option: v1.OptionNo, }, }, }, "Gov vote: Abstain": { - sender: addr1, - srcContractIBCPort: "myIBCPort", + sender: myAddr, srcMsg: wasmvmtypes.CosmosMsg{ Gov: &wasmvmtypes.GovMsg{ Vote: &wasmvmtypes.VoteMsg{ProposalId: 10, Vote: wasmvmtypes.Abstain}, @@ -543,14 +614,13 @@ func TestEncoding(t *testing.T) { output: []sdk.Msg{ &v1.MsgVote{ ProposalId: 10, - Voter: addr1.String(), + Voter: myAddr.String(), Option: v1.OptionAbstain, }, }, }, "Gov vote: No with veto": { - sender: addr1, - srcContractIBCPort: "myIBCPort", + sender: myAddr, srcMsg: wasmvmtypes.CosmosMsg{ Gov: &wasmvmtypes.GovMsg{ Vote: &wasmvmtypes.VoteMsg{ProposalId: 1, Vote: wasmvmtypes.NoWithVeto}, @@ -559,24 +629,168 @@ func TestEncoding(t *testing.T) { output: []sdk.Msg{ &v1.MsgVote{ ProposalId: 1, - Voter: addr1.String(), + Voter: myAddr.String(), Option: v1.OptionNoWithVeto, }, }, }, + "Gov vote: unset option": { + sender: myAddr, + srcMsg: wasmvmtypes.CosmosMsg{ + Gov: &wasmvmtypes.GovMsg{ + Vote: &wasmvmtypes.VoteMsg{ProposalId: 1}, + }, + }, + expError: true, + }, + "Gov weighted vote: single vote": { + sender: myAddr, + srcMsg: wasmvmtypes.CosmosMsg{ + Gov: &wasmvmtypes.GovMsg{ + VoteWeighted: &wasmvmtypes.VoteWeightedMsg{ + ProposalId: 1, + Options: []wasmvmtypes.WeightedVoteOption{ + {Option: wasmvmtypes.Yes, Weight: "1"}, + }, + }, + }, + }, + output: []sdk.Msg{ + &v1.MsgVoteWeighted{ + ProposalId: 1, + Voter: myAddr.String(), + Options: []*v1.WeightedVoteOption{ + {Option: v1.OptionYes, Weight: sdk.NewDec(1).String()}, + }, + }, + }, + }, + "Gov weighted vote: splitted": { + sender: myAddr, + srcMsg: wasmvmtypes.CosmosMsg{ + Gov: &wasmvmtypes.GovMsg{ + VoteWeighted: &wasmvmtypes.VoteWeightedMsg{ + ProposalId: 1, + Options: []wasmvmtypes.WeightedVoteOption{ + {Option: wasmvmtypes.Yes, Weight: "0.23"}, + {Option: wasmvmtypes.No, Weight: "0.24"}, + {Option: wasmvmtypes.Abstain, Weight: "0.26"}, + {Option: wasmvmtypes.NoWithVeto, Weight: "0.27"}, + }, + }, + }, + }, + output: []sdk.Msg{ + &v1.MsgVoteWeighted{ + ProposalId: 1, + Voter: myAddr.String(), + Options: []*v1.WeightedVoteOption{ + {Option: v1.OptionYes, Weight: sdk.NewDecWithPrec(23, 2).String()}, + {Option: v1.OptionNo, Weight: sdk.NewDecWithPrec(24, 2).String()}, + {Option: v1.OptionAbstain, Weight: sdk.NewDecWithPrec(26, 2).String()}, + {Option: v1.OptionNoWithVeto, Weight: sdk.NewDecWithPrec(27, 2).String()}, + }, + }, + }, + }, + "Gov weighted vote: duplicate option": { + sender: myAddr, + srcMsg: wasmvmtypes.CosmosMsg{ + Gov: &wasmvmtypes.GovMsg{ + VoteWeighted: &wasmvmtypes.VoteWeightedMsg{ + ProposalId: 1, + Options: []wasmvmtypes.WeightedVoteOption{ + {Option: wasmvmtypes.Yes, Weight: "0.5"}, + {Option: wasmvmtypes.Yes, Weight: "0.5"}, + }, + }, + }, + }, + output: []sdk.Msg{ + &v1.MsgVoteWeighted{ + ProposalId: 1, + Voter: myAddr.String(), + Options: []*v1.WeightedVoteOption{ + {Option: v1.OptionYes, Weight: sdk.NewDecWithPrec(5, 1).String()}, + {Option: v1.OptionYes, Weight: sdk.NewDecWithPrec(5, 1).String()}, + }, + }, + }, + expInvalid: true, + }, + "Gov weighted vote: weight sum exceeds 1": { + sender: myAddr, + srcMsg: wasmvmtypes.CosmosMsg{ + Gov: &wasmvmtypes.GovMsg{ + VoteWeighted: &wasmvmtypes.VoteWeightedMsg{ + ProposalId: 1, + Options: []wasmvmtypes.WeightedVoteOption{ + {Option: wasmvmtypes.Yes, Weight: "0.51"}, + {Option: wasmvmtypes.No, Weight: "0.5"}, + }, + }, + }, + }, + output: []sdk.Msg{ + &v1.MsgVoteWeighted{ + ProposalId: 1, + Voter: myAddr.String(), + Options: []*v1.WeightedVoteOption{ + {Option: v1.OptionYes, Weight: sdk.NewDecWithPrec(51, 2).String()}, + {Option: v1.OptionNo, Weight: sdk.NewDecWithPrec(5, 1).String()}, + }, + }, + }, + expInvalid: true, + }, + "Gov weighted vote: weight sum less than 1": { + sender: myAddr, + srcMsg: wasmvmtypes.CosmosMsg{ + Gov: &wasmvmtypes.GovMsg{ + VoteWeighted: &wasmvmtypes.VoteWeightedMsg{ + ProposalId: 1, + Options: []wasmvmtypes.WeightedVoteOption{ + {Option: wasmvmtypes.Yes, Weight: "0.49"}, + {Option: wasmvmtypes.No, Weight: "0.5"}, + }, + }, + }, + }, + output: []sdk.Msg{ + &v1.MsgVoteWeighted{ + ProposalId: 1, + Voter: myAddr.String(), + Options: []*v1.WeightedVoteOption{ + {Option: v1.OptionYes, Weight: sdk.NewDecWithPrec(49, 2).String()}, + {Option: v1.OptionNo, Weight: sdk.NewDecWithPrec(5, 1).String()}, + }, + }, + }, + expInvalid: true, + }, } encodingConfig := MakeEncodingConfig(t) for name, tc := range cases { t.Run(name, func(t *testing.T) { var ctx sdk.Context encoder := DefaultEncoders(encodingConfig.Marshaler, tc.transferPortSource) - res, err := encoder.Encode(ctx, tc.sender, tc.srcContractIBCPort, tc.srcMsg) - if tc.isError { - require.Error(t, err) + res, gotEncErr := encoder.Encode(ctx, tc.sender, "myIBCPort", tc.srcMsg) + if tc.expError { + assert.Error(t, gotEncErr) + return } else { - require.NoError(t, err) + require.NoError(t, gotEncErr) assert.Equal(t, tc.output, res) } + // and valid sdk message + for _, v := range res { + gotErr := v.ValidateBasic() + if tc.expInvalid { + assert.Error(t, gotErr) + } else { + assert.NoError(t, gotErr) + } + } }) } } diff --git a/x/wasm/keeper/keeper.go b/x/wasm/keeper/keeper.go index 7310d2f15b..41cb398ca3 100644 --- a/x/wasm/keeper/keeper.go +++ b/x/wasm/keeper/keeper.go @@ -7,7 +7,6 @@ import ( "encoding/hex" "fmt" "math" - "path/filepath" "reflect" "strconv" "strings" @@ -106,61 +105,6 @@ type Keeper struct { accountPruner AccountPruner } -// NewKeeper creates a new contract Keeper instance -// If customEncoders is non-nil, we can use this to override some of the message handler, especially custom -func NewKeeper( - cdc codec.Codec, - storeKey storetypes.StoreKey, - paramSpace paramtypes.Subspace, - accountKeeper types.AccountKeeper, - bankKeeper types.BankKeeper, - stakingKeeper types.StakingKeeper, - distrKeeper types.DistributionKeeper, - channelKeeper types.ChannelKeeper, - portKeeper types.PortKeeper, - capabilityKeeper types.CapabilityKeeper, - portSource types.ICS20TransferPortSource, - router MessageRouter, - _ GRPCQueryRouter, - homeDir string, - wasmConfig types.WasmConfig, - availableCapabilities string, - opts ...Option, -) Keeper { - wasmer, err := wasmvm.NewVM(filepath.Join(homeDir, "wasm"), availableCapabilities, contractMemoryLimit, wasmConfig.ContractDebugMode, wasmConfig.MemoryCacheSize) - if err != nil { - panic(err) - } - // set KeyTable if it has not already been set - if !paramSpace.HasKeyTable() { - paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable()) - } - - keeper := &Keeper{ - storeKey: storeKey, - cdc: cdc, - wasmVM: wasmer, - accountKeeper: accountKeeper, - bank: NewBankCoinTransferrer(bankKeeper), - accountPruner: NewVestingCoinBurner(bankKeeper), - portKeeper: portKeeper, - capabilityKeeper: capabilityKeeper, - messenger: NewDefaultMessageHandler(router, channelKeeper, capabilityKeeper, bankKeeper, cdc, portSource), - queryGasLimit: wasmConfig.SmartQueryGasLimit, - paramSpace: paramSpace, - gasRegister: NewDefaultWasmGasRegister(), - maxQueryStackSize: types.DefaultMaxQueryStackSize, - acceptedAccountTypes: defaultAcceptedAccountTypes, - } - keeper.wasmVMQueryHandler = DefaultQueryPlugins(bankKeeper, stakingKeeper, distrKeeper, channelKeeper, keeper) - for _, o := range opts { - o.apply(keeper) - } - // not updateable, yet - keeper.wasmVMResponseHandler = NewDefaultWasmVMContractResponseHandler(NewMessageDispatcher(keeper.messenger, keeper)) - return *keeper -} - func (k Keeper) getUploadAccessConfig(ctx sdk.Context) types.AccessConfig { var a types.AccessConfig k.paramSpace.Get(ctx, types.ParamStoreKeyUploadAccess, &a) diff --git a/x/wasm/keeper/keeper_cgo.go b/x/wasm/keeper/keeper_cgo.go new file mode 100644 index 0000000000..7fe744ed7d --- /dev/null +++ b/x/wasm/keeper/keeper_cgo.go @@ -0,0 +1,70 @@ +//go:build cgo + +package keeper + +import ( + "path/filepath" + + storetypes "github.com/cosmos/cosmos-sdk/store/types" + + wasmvm "github.com/CosmWasm/wasmvm" + "github.com/cosmos/cosmos-sdk/codec" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + + "github.com/CosmWasm/wasmd/x/wasm/types" +) + +// NewKeeper creates a new contract Keeper instance +// If customEncoders is non-nil, we can use this to override some of the message handler, especially custom +func NewKeeper( + cdc codec.Codec, + storeKey storetypes.StoreKey, + paramSpace paramtypes.Subspace, + accountKeeper types.AccountKeeper, + bankKeeper types.BankKeeper, + stakingKeeper types.StakingKeeper, + distrKeeper types.DistributionKeeper, + channelKeeper types.ChannelKeeper, + portKeeper types.PortKeeper, + capabilityKeeper types.CapabilityKeeper, + portSource types.ICS20TransferPortSource, + router MessageRouter, + _ GRPCQueryRouter, + homeDir string, + wasmConfig types.WasmConfig, + availableCapabilities string, + opts ...Option, +) Keeper { + wasmer, err := wasmvm.NewVM(filepath.Join(homeDir, "wasm"), availableCapabilities, contractMemoryLimit, wasmConfig.ContractDebugMode, wasmConfig.MemoryCacheSize) + if err != nil { + panic(err) + } + // set KeyTable if it has not already been set + if !paramSpace.HasKeyTable() { + paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable()) + } + + keeper := &Keeper{ + storeKey: storeKey, + cdc: cdc, + wasmVM: wasmer, + accountKeeper: accountKeeper, + bank: NewBankCoinTransferrer(bankKeeper), + accountPruner: NewVestingCoinBurner(bankKeeper), + portKeeper: portKeeper, + capabilityKeeper: capabilityKeeper, + messenger: NewDefaultMessageHandler(router, channelKeeper, capabilityKeeper, bankKeeper, cdc, portSource), + queryGasLimit: wasmConfig.SmartQueryGasLimit, + paramSpace: paramSpace, + gasRegister: NewDefaultWasmGasRegister(), + maxQueryStackSize: types.DefaultMaxQueryStackSize, + acceptedAccountTypes: defaultAcceptedAccountTypes, + } + keeper.wasmVMQueryHandler = DefaultQueryPlugins(bankKeeper, stakingKeeper, distrKeeper, channelKeeper, keeper) + for _, o := range opts { + o.apply(keeper) + } + // not updateable, yet + keeper.wasmVMResponseHandler = NewDefaultWasmVMContractResponseHandler(NewMessageDispatcher(keeper.messenger, keeper)) + return *keeper +} diff --git a/x/wasm/keeper/keeper_no_cgo.go b/x/wasm/keeper/keeper_no_cgo.go new file mode 100644 index 0000000000..0b8eb1c678 --- /dev/null +++ b/x/wasm/keeper/keeper_no_cgo.go @@ -0,0 +1,35 @@ +//go:build !cgo + +package keeper + +import ( + "github.com/cosmos/cosmos-sdk/codec" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + + "github.com/CosmWasm/wasmd/x/wasm/types" +) + +// NewKeeper creates a new contract Keeper instance +// If customEncoders is non-nil, we can use this to override some of the message handler, especially custom +func NewKeeper( + cdc codec.Codec, + storeKey storetypes.StoreKey, + paramSpace paramtypes.Subspace, + accountKeeper types.AccountKeeper, + bankKeeper types.BankKeeper, + stakingKeeper types.StakingKeeper, + distrKeeper types.DistributionKeeper, + channelKeeper types.ChannelKeeper, + portKeeper types.PortKeeper, + capabilityKeeper types.CapabilityKeeper, + portSource types.ICS20TransferPortSource, + router MessageRouter, + _ GRPCQueryRouter, + homeDir string, + wasmConfig types.WasmConfig, + availableCapabilities string, + opts ...Option, +) Keeper { + panic("not implemented, please build with cgo enabled") +} diff --git a/x/wasm/keeper/keeper_test.go b/x/wasm/keeper/keeper_test.go index 5c358b4c39..d891dc52e3 100644 --- a/x/wasm/keeper/keeper_test.go +++ b/x/wasm/keeper/keeper_test.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "os" + "strings" "testing" "time" @@ -59,7 +60,7 @@ func TestCreateSuccess(t *testing.T) { require.NoError(t, err) require.Equal(t, hackatomWasm, storedCode) // and events emitted - codeHash := "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5" + codeHash := strings.ToLower("beb3de5e9b93b52e514c74ce87ccddb594b9bcd33b7f1af1bb6da63fc883917b") exp := sdk.Events{sdk.NewEvent("store_code", sdk.NewAttribute("code_checksum", codeHash), sdk.NewAttribute("code_id", "1"))} assert.Equal(t, exp, em.Events()) } @@ -409,7 +410,7 @@ func TestInstantiate(t *testing.T) { gasAfter := ctx.GasMeter().GasConsumed() if types.EnableGasVerification { - require.Equal(t, uint64(0x1b5c1), gasAfter-gasBefore) + require.Equal(t, uint64(0x1b5bc), gasAfter-gasBefore) } // ensure it is stored properly @@ -853,7 +854,7 @@ func TestExecute(t *testing.T) { // make sure gas is properly deducted from ctx gasAfter := ctx.GasMeter().GasConsumed() if types.EnableGasVerification { - require.Equal(t, uint64(0x1a15c), gasAfter-gasBefore) + require.Equal(t, uint64(0x1a154), gasAfter-gasBefore) } // ensure bob now exists and got both payments released bobAcct = accKeeper.GetAccount(ctx, bob) @@ -1008,7 +1009,7 @@ func TestExecuteWithPanic(t *testing.T) { require.Error(t, err) require.True(t, errors.Is(err, types.ErrExecuteFailed)) // test with contains as "Display" implementation of the Wasmer "RuntimeError" is different for Mac and Linux - assert.Contains(t, err.Error(), "Error calling the VM: Error executing Wasm: Wasmer runtime error: RuntimeError: unreachable") + assert.Contains(t, err.Error(), "Error calling the VM: Error executing Wasm: Wasmer runtime error: RuntimeError: Aborted: panicked at 'This page intentionally faulted', src/contract.rs:169:5: execute wasm contract failed") } func TestExecuteWithCpuLoop(t *testing.T) { diff --git a/x/wasm/keeper/proposal_integration_test.go b/x/wasm/keeper/proposal_integration_test.go index 609ee442e7..72e73575b8 100644 --- a/x/wasm/keeper/proposal_integration_test.go +++ b/x/wasm/keeper/proposal_integration_test.go @@ -32,7 +32,7 @@ func TestStoreCodeProposal(t *testing.T) { }) wasmCode, err := os.ReadFile("./testdata/hackatom.wasm") require.NoError(t, err) - checksum, err := hex.DecodeString("13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5") + checksum, err := hex.DecodeString("beb3de5e9b93b52e514c74ce87ccddb594b9bcd33b7f1af1bb6da63fc883917b") require.NoError(t, err) specs := map[string]struct { @@ -314,7 +314,7 @@ func TestStoreAndInstantiateContractProposal(t *testing.T) { wasmCode, err := os.ReadFile("./testdata/hackatom.wasm") require.NoError(t, err) - checksum, err := hex.DecodeString("13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5") + checksum, err := hex.DecodeString("beb3de5e9b93b52e514c74ce87ccddb594b9bcd33b7f1af1bb6da63fc883917b") require.NoError(t, err) var ( diff --git a/x/wasm/keeper/query_plugins.go b/x/wasm/keeper/query_plugins.go index e7985610fd..ef82f99020 100644 --- a/x/wasm/keeper/query_plugins.go +++ b/x/wasm/keeper/query_plugins.go @@ -59,10 +59,10 @@ func (q QueryHandler) Query(request wasmvmtypes.QueryRequest, gasLimit uint64) ( return res, nil } - // special mappings to system error (which are not redacted) - var noSuchContract *types.ErrNoSuchContract - if ok := errors.As(err, &noSuchContract); ok { - err = wasmvmtypes.NoSuchContract{Addr: noSuchContract.Addr} + // special mappings to wasmvm system error (which are not redacted) + var wasmvmErr types.WasmVMErrorable + if ok := errors.As(err, &wasmvmErr); ok { + err = wasmvmErr.ToWasmVMError() } // Issue #759 - we don't return error string for worries of non-determinism @@ -90,6 +90,7 @@ type contractMetaDataSource interface { type wasmQueryKeeper interface { contractMetaDataSource + GetCodeInfo(ctx sdk.Context, codeID uint64) *types.CodeInfo QueryRaw(ctx sdk.Context, contractAddress sdk.AccAddress, key []byte) []byte QuerySmart(ctx sdk.Context, contractAddr sdk.AccAddress, req []byte) ([]byte, error) IsPinnedCode(ctx sdk.Context, codeID uint64) bool @@ -526,15 +527,16 @@ func WasmQuerier(k wasmQueryKeeper) func(ctx sdk.Context, request *wasmvmtypes.W } return k.QueryRaw(ctx, addr, request.Raw.Key), nil case request.ContractInfo != nil: - addr, err := sdk.AccAddressFromBech32(request.ContractInfo.ContractAddr) + contractAddr := request.ContractInfo.ContractAddr + addr, err := sdk.AccAddressFromBech32(contractAddr) if err != nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, request.ContractInfo.ContractAddr) + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, contractAddr) } info := k.GetContractInfo(ctx, addr) if info == nil { - return nil, &types.ErrNoSuchContract{Addr: request.ContractInfo.ContractAddr} + return nil, types.ErrNoSuchContractFn(contractAddr). + Wrapf("address %s", contractAddr) } - res := wasmvmtypes.ContractInfoResponse{ CodeID: info.CodeID, Creator: info.Creator, @@ -543,6 +545,22 @@ func WasmQuerier(k wasmQueryKeeper) func(ctx sdk.Context, request *wasmvmtypes.W IBCPort: info.IBCPortID, } return json.Marshal(res) + case request.CodeInfo != nil: + if request.CodeInfo.CodeID == 0 { + return nil, types.ErrEmpty.Wrap("code id") + } + info := k.GetCodeInfo(ctx, request.CodeInfo.CodeID) + if info == nil { + return nil, types.ErrNoSuchCodeFn(request.CodeInfo.CodeID). + Wrapf("code id %d", request.CodeInfo.CodeID) + } + + res := wasmvmtypes.CodeInfoResponse{ + CodeID: request.CodeInfo.CodeID, + Creator: info.Creator, + Checksum: info.CodeHash, + } + return json.Marshal(res) } return nil, wasmvmtypes.UnsupportedRequest{Kind: "unknown WasmQuery variant"} } diff --git a/x/wasm/keeper/query_plugins_test.go b/x/wasm/keeper/query_plugins_test.go index 5baba6d121..9b91fafcf0 100644 --- a/x/wasm/keeper/query_plugins_test.go +++ b/x/wasm/keeper/query_plugins_test.go @@ -460,6 +460,73 @@ func TestContractInfoWasmQuerier(t *testing.T) { } } +func TestCodeInfoWasmQuerier(t *testing.T) { + myCreatorAddr := keeper.RandomBech32AccountAddress(t) + var ctx sdk.Context + + myRawChecksum := []byte("myHash78901234567890123456789012") + specs := map[string]struct { + req *wasmvmtypes.WasmQuery + mock mockWasmQueryKeeper + expRes wasmvmtypes.CodeInfoResponse + expErr bool + }{ + "all good": { + req: &wasmvmtypes.WasmQuery{ + CodeInfo: &wasmvmtypes.CodeInfoQuery{CodeID: 1}, + }, + mock: mockWasmQueryKeeper{ + GetCodeInfoFn: func(ctx sdk.Context, codeID uint64) *types.CodeInfo { + return &types.CodeInfo{ + CodeHash: myRawChecksum, + Creator: myCreatorAddr, + InstantiateConfig: types.AccessConfig{ + Permission: types.AccessTypeNobody, + Addresses: []string{myCreatorAddr}, + }, + } + }, + }, + expRes: wasmvmtypes.CodeInfoResponse{ + CodeID: 1, + Creator: myCreatorAddr, + Checksum: myRawChecksum, + }, + }, + "empty code id": { + req: &wasmvmtypes.WasmQuery{ + CodeInfo: &wasmvmtypes.CodeInfoQuery{}, + }, + expErr: true, + }, + "unknown code id": { + req: &wasmvmtypes.WasmQuery{ + CodeInfo: &wasmvmtypes.CodeInfoQuery{CodeID: 1}, + }, + mock: mockWasmQueryKeeper{ + GetCodeInfoFn: func(ctx sdk.Context, codeID uint64) *types.CodeInfo { + return nil + }, + }, + expErr: true, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + q := keeper.WasmQuerier(spec.mock) + gotBz, gotErr := q(ctx, spec.req) + if spec.expErr { + require.Error(t, gotErr) + return + } + require.NoError(t, gotErr) + var gotRes wasmvmtypes.CodeInfoResponse + require.NoError(t, json.Unmarshal(gotBz, &gotRes), string(gotBz)) + assert.Equal(t, spec.expRes, gotRes) + }) + } +} + func TestQueryErrors(t *testing.T) { specs := map[string]struct { src error @@ -467,13 +534,21 @@ func TestQueryErrors(t *testing.T) { }{ "no error": {}, "no such contract": { - src: &types.ErrNoSuchContract{Addr: "contract-addr"}, + src: types.ErrNoSuchContractFn("contract-addr"), expErr: wasmvmtypes.NoSuchContract{Addr: "contract-addr"}, }, "no such contract - wrapped": { - src: sdkerrors.Wrap(&types.ErrNoSuchContract{Addr: "contract-addr"}, "my additional data"), + src: sdkerrors.Wrap(types.ErrNoSuchContractFn("contract-addr"), "my additional data"), expErr: wasmvmtypes.NoSuchContract{Addr: "contract-addr"}, }, + "no such code": { + src: types.ErrNoSuchCodeFn(123), + expErr: wasmvmtypes.NoSuchCode{CodeID: 123}, + }, + "no such code - wrapped": { + src: sdkerrors.Wrap(types.ErrNoSuchCodeFn(123), "my additional data"), + expErr: wasmvmtypes.NoSuchCode{CodeID: 123}, + }, } for name, spec := range specs { t.Run(name, func(t *testing.T) { @@ -558,6 +633,7 @@ type mockWasmQueryKeeper struct { QueryRawFn func(ctx sdk.Context, contractAddress sdk.AccAddress, key []byte) []byte QuerySmartFn func(ctx sdk.Context, contractAddr sdk.AccAddress, req types.RawContractMessage) ([]byte, error) IsPinnedCodeFn func(ctx sdk.Context, codeID uint64) bool + GetCodeInfoFn func(ctx sdk.Context, codeID uint64) *types.CodeInfo } func (m mockWasmQueryKeeper) GetContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress) *types.ContractInfo { @@ -588,6 +664,13 @@ func (m mockWasmQueryKeeper) IsPinnedCode(ctx sdk.Context, codeID uint64) bool { return m.IsPinnedCodeFn(ctx, codeID) } +func (m mockWasmQueryKeeper) GetCodeInfo(ctx sdk.Context, codeID uint64) *types.CodeInfo { + if m.GetCodeInfoFn == nil { + panic("not expected to be called") + } + return m.GetCodeInfoFn(ctx, codeID) +} + type bankKeeperMock struct { GetSupplyFn func(ctx sdk.Context, denom string) sdk.Coin GetBalanceFn func(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin diff --git a/x/wasm/keeper/recurse_test.go b/x/wasm/keeper/recurse_test.go index 0c0831aa49..5bfb20bdec 100644 --- a/x/wasm/keeper/recurse_test.go +++ b/x/wasm/keeper/recurse_test.go @@ -16,9 +16,8 @@ import ( ) type Recurse struct { - Depth uint32 `json:"depth"` - Work uint32 `json:"work"` - Contract sdk.AccAddress `json:"contract"` + Depth uint32 `json:"depth"` + Work uint32 `json:"work"` } type recurseWrapper struct { @@ -54,12 +53,12 @@ func initRecurseContract(t *testing.T) (contract sdk.AccAddress, creator sdk.Acc func TestGasCostOnQuery(t *testing.T) { const ( - GasNoWork uint64 = 63_958 + GasNoWork uint64 = 63_950 // Note: about 100 SDK gas (10k wasmer gas) for each round of sha256 - GasWork50 uint64 = 64_401 // this is a little shy of 50k gas - to keep an eye on the limit + GasWork50 uint64 = 64_218 // this is a little shy of 50k gas - to keep an eye on the limit - GasReturnUnhashed uint64 = 33 - GasReturnHashed uint64 = 25 + GasReturnUnhashed uint64 = 32 + GasReturnHashed uint64 = 27 ) cases := map[string]struct { @@ -92,7 +91,7 @@ func TestGasCostOnQuery(t *testing.T) { Depth: 1, Work: 50, }, - expectedGas: 2*GasWork50 + GasReturnHashed + 1, // +1 for rounding + expectedGas: 2*GasWork50 + GasReturnHashed, }, "recursion 4, some work": { gasLimit: 400_000, @@ -100,7 +99,7 @@ func TestGasCostOnQuery(t *testing.T) { Depth: 4, Work: 50, }, - expectedGas: 5*GasWork50 + 4*GasReturnHashed + 1, + expectedGas: 5*GasWork50 + 4*GasReturnHashed, }, } @@ -117,7 +116,6 @@ func TestGasCostOnQuery(t *testing.T) { // do the query recurse := tc.msg - recurse.Contract = contractAddr msg := buildRecurseQuery(t, recurse) data, err := keeper.QuerySmart(ctx, contractAddr, msg) require.NoError(t, err) @@ -186,7 +184,6 @@ func TestGasOnExternalQuery(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { recurse := tc.msg - recurse.Contract = contractAddr msg := buildRecurseQuery(t, recurse) querier := NewGrpcQuerier(keeper.cdc, keeper.storeKey, keeper, tc.gasLimit) @@ -211,9 +208,9 @@ func TestLimitRecursiveQueryGas(t *testing.T) { const ( // Note: about 100 SDK gas (10k wasmer gas) for each round of sha256 - GasWork2k uint64 = 84_236 // = NewContractInstanceCosts + x // we have 6x gas used in cpu than in the instance + GasWork2k uint64 = 77_206 // = NewContractInstanceCosts + x // we have 6x gas used in cpu than in the instance // This is overhead for calling into a sub-contract - GasReturnHashed uint64 = 26 + GasReturnHashed uint64 = 27 ) cases := map[string]struct { @@ -241,10 +238,10 @@ func TestLimitRecursiveQueryGas(t *testing.T) { }, expectQueriesFromContract: 5, // FIXME: why -1 ... confused a bit by calculations, seems like rounding issues - expectedGas: GasWork2k + 5*(GasWork2k+GasReturnHashed) - 1, + expectedGas: GasWork2k + 5*(GasWork2k+GasReturnHashed), }, // this is where we expect an error... - // it has enough gas to run 4 times and die on the 5th (4th time dispatching to sub-contract) + // it has enough gas to run 5 times and die on the 6th (5th time dispatching to sub-contract) // however, if we don't charge the cpu gas before sub-dispatching, we can recurse over 20 times "deep recursion, should die on 5th level": { gasLimit: 400_000, @@ -252,7 +249,7 @@ func TestLimitRecursiveQueryGas(t *testing.T) { Depth: 50, Work: 2000, }, - expectQueriesFromContract: 4, + expectQueriesFromContract: 5, expectOutOfGas: true, }, "very deep recursion, hits recursion limit": { @@ -264,7 +261,7 @@ func TestLimitRecursiveQueryGas(t *testing.T) { expectQueriesFromContract: 10, expectOutOfGas: false, expectError: "query wasm contract failed", // Error we get from the contract instance doing the failing query, not wasmd - expectedGas: 10*(GasWork2k+GasReturnHashed) - 264, + expectedGas: 10*(GasWork2k+GasReturnHashed) - 247, }, } @@ -281,7 +278,6 @@ func TestLimitRecursiveQueryGas(t *testing.T) { // prepare the query recurse := tc.msg - recurse.Contract = contractAddr msg := buildRecurseQuery(t, recurse) // if we expect out of gas, make sure this panics diff --git a/x/wasm/keeper/testdata/burner.wasm b/x/wasm/keeper/testdata/burner.wasm index 4e65059f7c..6639075611 100644 Binary files a/x/wasm/keeper/testdata/burner.wasm and b/x/wasm/keeper/testdata/burner.wasm differ diff --git a/x/wasm/keeper/testdata/hackatom.wasm b/x/wasm/keeper/testdata/hackatom.wasm index 183eef304c..baa03a853a 100644 Binary files a/x/wasm/keeper/testdata/hackatom.wasm and b/x/wasm/keeper/testdata/hackatom.wasm differ diff --git a/x/wasm/keeper/testdata/hackatom.wasm.gzip b/x/wasm/keeper/testdata/hackatom.wasm.gzip index 29b10c7d57..3c95e9b1d4 100644 Binary files a/x/wasm/keeper/testdata/hackatom.wasm.gzip and b/x/wasm/keeper/testdata/hackatom.wasm.gzip differ diff --git a/x/wasm/keeper/testdata/ibc_reflect.wasm b/x/wasm/keeper/testdata/ibc_reflect.wasm index ec1739d898..ec737104c4 100644 Binary files a/x/wasm/keeper/testdata/ibc_reflect.wasm and b/x/wasm/keeper/testdata/ibc_reflect.wasm differ diff --git a/x/wasm/keeper/testdata/ibc_reflect_send.wasm b/x/wasm/keeper/testdata/ibc_reflect_send.wasm index 497ceacac5..0f7d7e4593 100644 Binary files a/x/wasm/keeper/testdata/ibc_reflect_send.wasm and b/x/wasm/keeper/testdata/ibc_reflect_send.wasm differ diff --git a/x/wasm/keeper/testdata/reflect.wasm b/x/wasm/keeper/testdata/reflect.wasm index 412241177b..31735645df 100644 Binary files a/x/wasm/keeper/testdata/reflect.wasm and b/x/wasm/keeper/testdata/reflect.wasm differ diff --git a/x/wasm/keeper/testdata/staking.wasm b/x/wasm/keeper/testdata/staking.wasm index d73c1b19a8..015ae00ed7 100644 Binary files a/x/wasm/keeper/testdata/staking.wasm and b/x/wasm/keeper/testdata/staking.wasm differ diff --git a/x/wasm/keeper/testdata/version.txt b/x/wasm/keeper/testdata/version.txt index 820bf4362b..79127d85a4 100644 --- a/x/wasm/keeper/testdata/version.txt +++ b/x/wasm/keeper/testdata/version.txt @@ -1 +1 @@ -v1.0.0-beta +v1.2.0 diff --git a/x/wasm/types/errors.go b/x/wasm/types/errors.go index a7e0464612..70b854cf90 100644 --- a/x/wasm/types/errors.go +++ b/x/wasm/types/errors.go @@ -1,7 +1,8 @@ package types import ( - sdkErrors "github.com/cosmos/cosmos-sdk/types/errors" + sdkErrors "cosmossdk.io/errors" + wasmvmtypes "github.com/CosmWasm/wasmvm/types" ) // Codes for wasm contract errors @@ -70,27 +71,80 @@ var ( // ErrInvalidEvent error if an attribute/event from the contract is invalid ErrInvalidEvent = sdkErrors.Register(DefaultCodespace, 21, "invalid event") - // error if an address does not belong to a contract (just for registration) - _ = sdkErrors.Register(DefaultCodespace, 22, "no such contract") + // ErrNoSuchContractFn error factory for an error when an address does not belong to a contract + ErrNoSuchContractFn = WasmVMFlavouredErrorFactory(sdkErrors.Register(DefaultCodespace, 22, "no such contract"), + func(addr string) error { return wasmvmtypes.NoSuchContract{Addr: addr} }, + ) // code 23 -26 were used for json parser // ErrExceedMaxQueryStackSize error if max query stack size is exceeded ErrExceedMaxQueryStackSize = sdkErrors.Register(DefaultCodespace, 27, "max query stack size exceeded") + + // ErrNoSuchCodeFn factory for an error when a code id does not belong to a code info + ErrNoSuchCodeFn = WasmVMFlavouredErrorFactory(sdkErrors.Register(DefaultCodespace, 28, "no such code"), + func(id uint64) error { return wasmvmtypes.NoSuchCode{CodeID: id} }, + ) ) -type ErrNoSuchContract struct { - Addr string +// WasmVMErrorable mapped error type in wasmvm and are not redacted +type WasmVMErrorable interface { + // ToWasmVMError convert instance to wasmvm friendly error if possible otherwise root cause. never nil + ToWasmVMError() error +} + +var _ WasmVMErrorable = WasmVMFlavouredError{} + +// WasmVMFlavouredError wrapper for sdk error that supports wasmvm error types +type WasmVMFlavouredError struct { + sdkErr *sdkErrors.Error + wasmVMErr error } -func (m *ErrNoSuchContract) Error() string { - return "no such contract: " + m.Addr +// NewWasmVMFlavouredError constructor +func NewWasmVMFlavouredError(sdkErr *sdkErrors.Error, wasmVMErr error) WasmVMFlavouredError { + return WasmVMFlavouredError{sdkErr: sdkErr, wasmVMErr: wasmVMErr} } -func (m *ErrNoSuchContract) ABCICode() uint32 { - return 22 +// WasmVMFlavouredErrorFactory is a factory method to build a WasmVMFlavouredError type +func WasmVMFlavouredErrorFactory[T any](sdkErr *sdkErrors.Error, wasmVMErrBuilder func(T) error) func(T) WasmVMFlavouredError { + if wasmVMErrBuilder == nil { + panic("builder function required") + } + return func(d T) WasmVMFlavouredError { + return WasmVMFlavouredError{sdkErr: sdkErr, wasmVMErr: wasmVMErrBuilder(d)} + } } -func (m *ErrNoSuchContract) Codespace() string { - return DefaultCodespace +// ToWasmVMError implements WasmVMError-able +func (e WasmVMFlavouredError) ToWasmVMError() error { + if e.wasmVMErr != nil { + return e.wasmVMErr + } + return e.sdkErr +} + +// implements stdlib error +func (e WasmVMFlavouredError) Error() string { + return e.sdkErr.Error() +} + +// Unwrap implements the built-in errors.Unwrap +func (e WasmVMFlavouredError) Unwrap() error { + return e.sdkErr +} + +// Cause is the same as unwrap but used by errors.abci +func (e WasmVMFlavouredError) Cause() error { + return e.Unwrap() +} + +// Wrap extends this error with additional information. +// It's a handy function to call Wrap with sdk errors. +func (e WasmVMFlavouredError) Wrap(desc string) error { return sdkErrors.Wrap(e, desc) } + +// Wrapf extends this error with additional information. +// It's a handy function to call Wrapf with sdk errors. +func (e WasmVMFlavouredError) Wrapf(desc string, args ...interface{}) error { + return sdkErrors.Wrapf(e, desc, args...) } diff --git a/x/wasm/types/errors_test.go b/x/wasm/types/errors_test.go new file mode 100644 index 0000000000..76a1c0239c --- /dev/null +++ b/x/wasm/types/errors_test.go @@ -0,0 +1,86 @@ +package types + +import ( + "errors" + "testing" + + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestWasmVMFlavouredError(t *testing.T) { + myErr := ErrNoSuchCodeFn(1) + specs := map[string]struct { + exec func(t *testing.T) + }{ + "IsOf": { + exec: func(t *testing.T) { + assert.True(t, sdkerrors.IsOf(myErr, myErr.sdkErr)) + assert.Equal(t, myErr.sdkErr, myErr.Unwrap()) + }, + }, + "unwrapped": { + exec: func(t *testing.T) { + assert.Equal(t, myErr.sdkErr, myErr.Unwrap()) + }, + }, + "caused": { + exec: func(t *testing.T) { + assert.Equal(t, myErr.sdkErr, myErr.Cause()) + }, + }, + "wrapped supports WasmVMErrorable": { + exec: func(t *testing.T) { + var wasmvmErr WasmVMErrorable + require.True(t, errors.As(myErr.Wrap("my description"), &wasmvmErr)) + gotErr := wasmvmErr.ToWasmVMError() + assert.Equal(t, wasmvmtypes.NoSuchCode{CodeID: 1}, gotErr) + }, + }, + "wrappedf supports WasmVMErrorable": { + exec: func(t *testing.T) { + var wasmvmErr WasmVMErrorable + require.True(t, errors.As(myErr.Wrapf("my description: %d", 1), &wasmvmErr)) + gotErr := wasmvmErr.ToWasmVMError() + assert.Equal(t, wasmvmtypes.NoSuchCode{CodeID: 1}, gotErr) + }, + }, + "supports WasmVMErrorable": { + exec: func(t *testing.T) { + var wasmvmErr WasmVMErrorable + require.True(t, errors.As(myErr, &wasmvmErr)) + gotErr := wasmvmErr.ToWasmVMError() + assert.Equal(t, wasmvmtypes.NoSuchCode{CodeID: 1}, gotErr) + }, + }, + "fallback to sdk error when wasmvm error unset": { + exec: func(t *testing.T) { + var wasmvmErr WasmVMErrorable + require.True(t, errors.As(WasmVMFlavouredError{sdkErr: ErrEmpty}, &wasmvmErr)) + gotErr := wasmvmErr.ToWasmVMError() + assert.Equal(t, ErrEmpty, gotErr) + }, + }, + "abci info": { + exec: func(t *testing.T) { + codespace, code, log := sdkerrors.ABCIInfo(myErr, false) + assert.Equal(t, DefaultCodespace, codespace) + assert.Equal(t, uint32(28), code) + assert.Equal(t, "no such code", log) + }, + }, + "abci info - wrapped": { + exec: func(t *testing.T) { + codespace, code, log := sdkerrors.ABCIInfo(myErr.Wrap("my description"), false) + assert.Equal(t, DefaultCodespace, codespace) + assert.Equal(t, uint32(28), code) + assert.Equal(t, "my description: no such code", log) + }, + }, + } + for name, spec := range specs { + t.Run(name, spec.exec) + } +}