diff --git a/go.mod b/go.mod index e053ac1ef..434db7659 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.13 require ( github.com/CosmWasm/go-cosmwasm v0.8.0 - github.com/cosmos/cosmos-sdk v0.38.3 + github.com/cosmos/cosmos-sdk v0.38.4 github.com/gorilla/mux v1.7.3 github.com/otiai10/copy v1.0.2 github.com/otiai10/curr v0.0.0-20190513014714-f5a3d24e5776 // indirect diff --git a/go.sum b/go.sum index ed89d3bfe..90df7df40 100644 --- a/go.sum +++ b/go.sum @@ -71,8 +71,8 @@ github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cosmos/cosmos-sdk v0.38.3 h1:qIBTiw+2T9POaSUJ5rvbBbXeq8C8btBlJxnSegPBd3Y= -github.com/cosmos/cosmos-sdk v0.38.3/go.mod h1:rzWOofbKfRt3wxiylmYWEFHnxxGj0coyqgWl2I9obAw= +github.com/cosmos/cosmos-sdk v0.38.4 h1:jPZOvhMQkm7wwwzcLxuluhVpKfuIgddNGt999pAiz/Y= +github.com/cosmos/cosmos-sdk v0.38.4/go.mod h1:rzWOofbKfRt3wxiylmYWEFHnxxGj0coyqgWl2I9obAw= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d h1:49RLWk1j44Xu4fjHb6JFYmeUnDORVwHNkDxaQ0ctCVU= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= github.com/cosmos/ledger-cosmos-go v0.11.1 h1:9JIYsGnXP613pb2vPjFeMMjBI5lEDsEaF6oYorTy6J4= diff --git a/x/wasm/alias.go b/x/wasm/alias.go index cd970e57f..4d47280f1 100644 --- a/x/wasm/alias.go +++ b/x/wasm/alias.go @@ -16,7 +16,6 @@ const ( TStoreKey = types.TStoreKey QuerierRoute = types.QuerierRoute RouterKey = types.RouterKey - MaxWasmSize = types.MaxWasmSize WasmMsgParserRouteBank = types.WasmMsgParserRouteBank WasmMsgParserRouteStaking = types.WasmMsgParserRouteStaking WasmMsgParserRouteWasm = types.WasmMsgParserRouteWasm diff --git a/x/wasm/client/cli/tx.go b/x/wasm/client/cli/tx.go index 53f8524a4..9aebf27b6 100644 --- a/x/wasm/client/cli/tx.go +++ b/x/wasm/client/cli/tx.go @@ -65,9 +65,9 @@ func StoreCodeCmd(cdc *codec.Codec) *cobra.Command { } // limit the input size - if len(wasm) > types.MaxWasmSize { - return fmt.Errorf("input size exceeds the max size hard-cap (allowed:%d, actual: %d)", - types.MaxWasmSize, len(wasm)) + if wasmLen := uint64(len(wasm)); wasmLen > types.EnforcedMaxContractSize { + return fmt.Errorf("wasm code size exceeds the max size hard-cap (allowed:%d, actual: %d)", + types.EnforcedMaxContractSize, wasmLen) } // gzip the wasm file @@ -124,7 +124,13 @@ $ terracli instantiate 1 '{"arbiter": "terra~~"}' "1000000uluna" return err } - initMsg := args[1] + initMsgBz := []byte(args[1]) + + // limit the input size + if initMsgLen := uint64(len(initMsgBz)); initMsgLen > types.EnforcedMaxContractMsgSize { + return fmt.Errorf("init msg size exceeds the max size hard-cap (allowed:%d, actual: %d)", + types.EnforcedMaxContractMsgSize, initMsgLen) + } var coins sdk.Coins if len(args) == 3 { @@ -139,7 +145,7 @@ $ terracli instantiate 1 '{"arbiter": "terra~~"}' "1000000uluna" Sender: fromAddr, CodeID: codeID, InitCoins: coins, - InitMsg: []byte(initMsg), + InitMsg: initMsgBz, } return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) }, @@ -170,7 +176,13 @@ func ExecuteContractCmd(cdc *codec.Codec) *cobra.Command { return err } - execMsg := args[1] + execMsgBz := []byte(args[1]) + + // limit the input size + if execMsgLen := uint64(len(execMsgBz)); execMsgLen > types.EnforcedMaxContractMsgSize { + return fmt.Errorf("exec msg size exceeds the max size hard-cap (allowed:%d, actual: %d)", + types.EnforcedMaxContractMsgSize, execMsgLen) + } var coins sdk.Coins if len(args) == 3 { @@ -185,7 +197,7 @@ func ExecuteContractCmd(cdc *codec.Codec) *cobra.Command { Sender: fromAddr, Contract: contractAddr, Coins: coins, - Msg: []byte(execMsg), + Msg: execMsgBz, } return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) }, diff --git a/x/wasm/handler.go b/x/wasm/handler.go index 18425ef50..54826486e 100644 --- a/x/wasm/handler.go +++ b/x/wasm/handler.go @@ -16,16 +16,10 @@ func NewHandler(k Keeper) sdk.Handler { switch msg := msg.(type) { case MsgStoreCode: return handleStoreCode(ctx, k, &msg) - case *MsgStoreCode: - return handleStoreCode(ctx, k, msg) case MsgInstantiateContract: return handleInstantiate(ctx, k, &msg) - case *MsgInstantiateContract: - return handleInstantiate(ctx, k, msg) case MsgExecuteContract: return handleExecute(ctx, k, &msg) - case *MsgExecuteContract: - return handleExecute(ctx, k, msg) default: return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized distribution message type: %T", msg) diff --git a/x/wasm/handler_test.go b/x/wasm/handler_test.go index 4e82c4be0..2c6e2d62f 100644 --- a/x/wasm/handler_test.go +++ b/x/wasm/handler_test.go @@ -276,7 +276,7 @@ func TestHandleExecuteEscrow(t *testing.T) { Sender: creator, WASMByteCode: testContract, } - _, err := h(data.ctx, &msg) + _, err := h(data.ctx, msg) require.NoError(t, err) bytecode, sdkErr := data.keeper.GetByteCode(data.ctx, 1) diff --git a/x/wasm/internal/keeper/contract.go b/x/wasm/internal/keeper/contract.go index 40ca6ada9..e781abf74 100644 --- a/x/wasm/internal/keeper/contract.go +++ b/x/wasm/internal/keeper/contract.go @@ -12,6 +12,10 @@ import ( // StoreCode uploads and compiles a WASM contract bytecode, returning a short identifier for the stored code func (k Keeper) StoreCode(ctx sdk.Context, creator sdk.AccAddress, wasmCode []byte) (codeID uint64, err error) { + if uint64(len(wasmCode)) > k.MaxContractSize(ctx) { + return 0, sdkerrors.Wrap(types.ErrStoreCodeFailed, "contract size is too huge") + } + wasmCode, err = k.uncompress(ctx, wasmCode) if err != nil { return 0, sdkerrors.Wrap(types.ErrStoreCodeFailed, err.Error()) @@ -38,6 +42,10 @@ func (k Keeper) StoreCode(ctx sdk.Context, creator sdk.AccAddress, wasmCode []by // InstantiateContract creates an instance of a WASM contract func (k Keeper) InstantiateContract(ctx sdk.Context, codeID uint64, creator sdk.AccAddress, initMsg []byte, deposit sdk.Coins) (contractAddress sdk.AccAddress, err error) { + if uint64(len(initMsg)) > k.MaxContractMsgSize(ctx) { + return nil, sdkerrors.Wrap(types.ErrInstantiateFailed, "init msg size is too huge") + } + instanceID, err := k.GetLastInstanceID(ctx) if err != nil { return nil, err @@ -107,7 +115,11 @@ func (k Keeper) InstantiateContract(ctx sdk.Context, codeID uint64, creator sdk. } // ExecuteContract executes the contract instance -func (k Keeper) ExecuteContract(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, msg []byte, coins sdk.Coins) (sdk.Result, error) { +func (k Keeper) ExecuteContract(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, exeMsg []byte, coins sdk.Coins) (sdk.Result, error) { + if uint64(len(exeMsg)) > k.MaxContractMsgSize(ctx) { + return sdk.Result{}, sdkerrors.Wrap(types.ErrInstantiateFailed, "execute msg size is too huge") + } + codeInfo, storePrefix, sdkerr := k.getContractDetails(ctx, contractAddress) if sdkerr != nil { return sdk.Result{}, sdkerr @@ -124,7 +136,7 @@ func (k Keeper) ExecuteContract(ctx sdk.Context, contractAddress sdk.AccAddress, apiParams := types.NewWasmAPIParams(ctx, caller, coins, contractAddress) gas := k.gasForContract(ctx) - res, err := k.wasmer.Execute(codeInfo.CodeHash.Bytes(), apiParams, msg, storePrefix, cosmwasmAPI, k.querier.WithCtx(ctx), gas) + res, err := k.wasmer.Execute(codeInfo.CodeHash.Bytes(), apiParams, exeMsg, storePrefix, cosmwasmAPI, k.querier.WithCtx(ctx), gas) if err != nil { // TODO: wasmer doesn't return wasm gas used on error. we should consume it (for error on metering failure) // Note: OutOfGas panics (from storage) are caught by go-cosmwasm, subtract one more gas to check if diff --git a/x/wasm/internal/keeper/contract_test.go b/x/wasm/internal/keeper/contract_test.go index e4ffe706a..ab5773db6 100644 --- a/x/wasm/internal/keeper/contract_test.go +++ b/x/wasm/internal/keeper/contract_test.go @@ -44,6 +44,24 @@ func TestStoreCode(t *testing.T) { require.Equal(t, wasmCode, storedCode) } +func TestStoreCodeWithHugeCode(t *testing.T) { + // Create & set temp as home + tempDir, err := ioutil.TempDir("", "wasmtest") + require.NoError(t, err) + defer os.RemoveAll(tempDir) + viper.Set(flags.FlagHome, tempDir) + + input := CreateTestInput(t) + ctx, keeper := input.Ctx, input.WasmKeeper + + _, _, creator := keyPubAddr() + wasmCode := make([]byte, keeper.MaxContractSize(ctx)+1) + _, err = keeper.StoreCode(ctx, creator, wasmCode) + + require.Error(t, err) + require.Contains(t, err.Error(), "contract size is too huge") +} + func TestCreateWithGzippedPayload(t *testing.T) { // Create & set temp as home tempDir, err := ioutil.TempDir("", "wasmtest") @@ -127,6 +145,31 @@ func TestInstantiateWithNonExistingCodeID(t *testing.T) { require.Error(t, err, sdkerrors.Wrapf(types.ErrNotFound, "codeID %d", nonExistingCodeID)) } +func TestInstantiateWithBigInitMsg(t *testing.T) { + // Create & set temp as home + tempDir, err := ioutil.TempDir("", "wasmtest") + require.NoError(t, err) + defer os.RemoveAll(tempDir) + viper.Set(flags.FlagHome, tempDir) + input := CreateTestInput(t) + ctx, accKeeper, keeper := input.Ctx, input.AccKeeper, input.WasmKeeper + + deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) + creator := createFakeFundedAccount(ctx, accKeeper, deposit) + + wasmCode, err := ioutil.ReadFile("./testdata/contract.wasm") + require.NoError(t, err) + + codeID, err := keeper.StoreCode(ctx, creator, wasmCode) + require.NoError(t, err) + + // test max init msg size + initMsgBz := make([]byte, keeper.MaxContractMsgSize(ctx)+1) + _, err = keeper.InstantiateContract(ctx, codeID, creator, initMsgBz, deposit) + require.Error(t, err) + require.Contains(t, err.Error(), "init msg size is too huge") +} + func TestExecute(t *testing.T) { // Create & set temp as home tempDir, err := ioutil.TempDir("", "wasmtest") @@ -191,7 +234,7 @@ func TestExecute(t *testing.T) { // make sure gas is properly deducted from ctx gasAfter := ctx.GasMeter().GasConsumed() - require.Equal(t, uint64(0x8b2d), gasAfter-gasBefore) + require.Equal(t, uint64(0x8f21), gasAfter-gasBefore) // ensure bob now exists and got both payments released bobAcct = accKeeper.GetAccount(ctx, bob) @@ -225,6 +268,44 @@ func TestExecuteWithNonExistingContractAddress(t *testing.T) { require.Error(t, err, sdkerrors.Wrapf(types.ErrNotFound, "contract %s", nonExistingContractAddress)) } +func TestExecuteWithHugeMsg(t *testing.T) { + // Create & set temp as home + tempDir, err := ioutil.TempDir("", "wasmtest") + require.NoError(t, err) + defer os.RemoveAll(tempDir) + viper.Set(flags.FlagHome, tempDir) + input := CreateTestInput(t) + ctx, accKeeper, keeper := input.Ctx, input.AccKeeper, input.WasmKeeper + + deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) + topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000)) + creator := createFakeFundedAccount(ctx, accKeeper, deposit.Add(deposit...)) + fred := createFakeFundedAccount(ctx, accKeeper, topUp) + + wasmCode, err := ioutil.ReadFile("./testdata/contract.wasm") + require.NoError(t, err) + + codeID, err := keeper.StoreCode(ctx, creator, wasmCode) + require.NoError(t, err) + + _, _, bob := keyPubAddr() + initMsg := InitMsg{ + Verifier: fred, + Beneficiary: bob, + } + initMsgBz, err := json.Marshal(initMsg) + require.NoError(t, err) + + addr, err := keeper.InstantiateContract(ctx, codeID, creator, initMsgBz, deposit) + require.NoError(t, err) + require.Equal(t, "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", addr.String()) + + msgBz := make([]byte, keeper.MaxContractMsgSize(ctx)+1) + _, err = keeper.ExecuteContract(ctx, addr, fred, msgBz, topUp) + require.Error(t, err) + require.Contains(t, err.Error(), "execute msg size is too huge") +} + func TestExecuteWithPanic(t *testing.T) { tempDir, err := ioutil.TempDir("", "wasm") require.NoError(t, err) diff --git a/x/wasm/internal/keeper/ioutil.go b/x/wasm/internal/keeper/ioutil.go index d40ae71c1..abd54f94e 100644 --- a/x/wasm/internal/keeper/ioutil.go +++ b/x/wasm/internal/keeper/ioutil.go @@ -30,5 +30,5 @@ func (k Keeper) uncompress(ctx sdk.Context, src []byte) ([]byte, error) { } zr.Multistream(false) - return ioutil.ReadAll(io.LimitReader(zr, k.MaxContractSize(ctx))) + return ioutil.ReadAll(io.LimitReader(zr, int64(k.MaxContractSize(ctx)))) } diff --git a/x/wasm/internal/keeper/params.go b/x/wasm/internal/keeper/params.go index 59cb183cb..a1c999173 100644 --- a/x/wasm/internal/keeper/params.go +++ b/x/wasm/internal/keeper/params.go @@ -6,7 +6,7 @@ import ( ) // MaxContractSize defines maximum bytes size of a contract -func (k Keeper) MaxContractSize(ctx sdk.Context) (res int64) { +func (k Keeper) MaxContractSize(ctx sdk.Context) (res uint64) { k.paramSpace.Get(ctx, types.ParamStoreKeyMaxContractSize, &res) return } @@ -17,6 +17,12 @@ func (k Keeper) MaxContractGas(ctx sdk.Context) (res uint64) { return } +// MaxContractMsgSize defines maximum bytes size of a contract +func (k Keeper) MaxContractMsgSize(ctx sdk.Context) (res uint64) { + k.paramSpace.Get(ctx, types.ParamStoreKeyMaxContractMsgSize, &res) + return +} + // GasMultiplier defines how many cosmwasm gas points = 1 sdk gas point func (k Keeper) GasMultiplier(ctx sdk.Context) (res uint64) { k.paramSpace.Get(ctx, types.ParamStoreKeyGasMultiplier, &res) diff --git a/x/wasm/internal/keeper/test_utils.go b/x/wasm/internal/keeper/test_utils.go index b81c3077e..32246a787 100644 --- a/x/wasm/internal/keeper/test_utils.go +++ b/x/wasm/internal/keeper/test_utils.go @@ -1,3 +1,4 @@ +// nolint:deadcode unused noalias package keeper import ( diff --git a/x/wasm/internal/types/genesis.go b/x/wasm/internal/types/genesis.go index a7308f1c3..4cd2d4329 100644 --- a/x/wasm/internal/types/genesis.go +++ b/x/wasm/internal/types/genesis.go @@ -1,5 +1,11 @@ package types +import ( + "bytes" + + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + // GenesisState is the struct representation of the export genesis type GenesisState struct { Params Params `json:"params" yaml:"params"` @@ -46,5 +52,27 @@ func DefaultGenesisState() GenesisState { // ValidateGenesis performs basic validation of wasm genesis data returning an // error for any failed validation criteria. func ValidateGenesis(data GenesisState) error { - return nil + + if uint64(len(data.Codes)) != data.LastCodeID { + return sdkerrors.Wrap(ErrInvalidGenesis, "the number of codes is not met with LastCodeID") + } + + if uint64(len(data.Contracts)) != data.LastInstanceID { + return sdkerrors.Wrap(ErrInvalidGenesis, "the number of contracts is not met with LastInstanceID") + } + + return data.Params.Validate() +} + +// Equal checks whether 2 GenesisState structs are equivalent. +func (data GenesisState) Equal(data2 GenesisState) bool { + b1 := ModuleCdc.MustMarshalBinaryBare(data) + b2 := ModuleCdc.MustMarshalBinaryBare(data2) + return bytes.Equal(b1, b2) +} + +// IsEmpty returns if a GenesisState is empty or has data in it +func (data GenesisState) IsEmpty() bool { + emptyGenState := GenesisState{} + return data.Equal(emptyGenState) } diff --git a/x/wasm/internal/types/genesis_test.go b/x/wasm/internal/types/genesis_test.go new file mode 100644 index 000000000..29b919e0b --- /dev/null +++ b/x/wasm/internal/types/genesis_test.go @@ -0,0 +1,55 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGenesisValidation(t *testing.T) { + genState := DefaultGenesisState() + require.NoError(t, ValidateGenesis(genState)) + + genState.Params.MaxContractSize = EnforcedMaxContractSize + 1 + require.Error(t, ValidateGenesis(genState)) + + genState = DefaultGenesisState() + genState.Params.MaxContractGas = EnforcedMaxContractGas + 1 + require.Error(t, ValidateGenesis(genState)) + + genState = DefaultGenesisState() + genState.Params.MaxContractMsgSize = EnforcedMaxContractMsgSize + 1 + require.Error(t, ValidateGenesis(genState)) + + genState = DefaultGenesisState() + genState.Params.GasMultiplier = 0 + require.Error(t, ValidateGenesis(genState)) + + genState = DefaultGenesisState() + genState.Codes = []Code{{}, {}} + genState.LastCodeID = 2 + require.NoError(t, ValidateGenesis(genState)) + + genState.LastCodeID = 1 + require.Error(t, ValidateGenesis(genState)) + + genState = DefaultGenesisState() + genState.Contracts = []Contract{{}, {}} + genState.LastInstanceID = 2 + require.NoError(t, ValidateGenesis(genState)) + + genState.LastInstanceID = 1 + require.Error(t, ValidateGenesis(genState)) +} + +func TestGenesisEqual(t *testing.T) { + genState1 := DefaultGenesisState() + genState2 := DefaultGenesisState() + + require.True(t, genState1.Equal(genState2)) +} + +func TestGenesisEmpty(t *testing.T) { + genState := GenesisState{} + require.True(t, genState.IsEmpty()) +} diff --git a/x/wasm/internal/types/msg.go b/x/wasm/internal/types/msg.go index 96c70e510..87ec8a000 100644 --- a/x/wasm/internal/types/msg.go +++ b/x/wasm/internal/types/msg.go @@ -7,11 +7,6 @@ import ( core "github.com/terra-project/core/types" ) -const ( - // MaxWasmSize 500 KB (hard-cap) - MaxWasmSize = 500 * 1024 -) - // MsgStoreCode - struct for upload contract wasm byte codes type MsgStoreCode struct { Sender sdk.AccAddress `json:"sender" yaml:"sender"` @@ -49,13 +44,18 @@ func (msg MsgStoreCode) GetSigners() []sdk.AccAddress { // ValidateBasic Implements sdk.Msg func (msg MsgStoreCode) ValidateBasic() error { - if len(msg.WASMByteCode) == 0 { + if msg.Sender.Empty() { + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "empty sender") + } + if len(msg.WASMByteCode) == 0 { return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "empty wasm code") } - if len(msg.WASMByteCode) > MaxWasmSize { + + if uint64(len(msg.WASMByteCode)) > EnforcedMaxContractSize { return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "wasm code too large") } + return nil } @@ -89,9 +89,18 @@ func (msg MsgInstantiateContract) Type() string { // ValidateBasic implements sdk.Msg func (msg MsgInstantiateContract) ValidateBasic() error { + if msg.Sender.Empty() { + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "empty sender") + } + if !msg.InitCoins.IsValid() { return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, msg.InitCoins.String()) } + + if uint64(len(msg.InitMsg)) > EnforcedMaxContractMsgSize { + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "wasm msg byte size is too huge") + } + return nil } @@ -135,9 +144,22 @@ func (msg MsgExecuteContract) Type() string { // ValidateBasic implements sdk.Msg func (msg MsgExecuteContract) ValidateBasic() error { + if msg.Sender.Empty() { + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "empty sender") + } + + if msg.Contract.Empty() { + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "empty contract") + } + if !msg.Coins.IsValid() { return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, msg.Coins.String()) } + + if uint64(len(msg.Msg)) > EnforcedMaxContractMsgSize { + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "wasm msg byte size is too huge") + } + return nil } diff --git a/x/wasm/internal/types/msg_test.go b/x/wasm/internal/types/msg_test.go new file mode 100644 index 000000000..34f879338 --- /dev/null +++ b/x/wasm/internal/types/msg_test.go @@ -0,0 +1,88 @@ +package types + +import ( + "testing" + + core "github.com/terra-project/core/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/mock" + "github.com/stretchr/testify/require" +) + +func TestMsgStoreCode(t *testing.T) { + _, addrs, _, _ := mock.CreateGenAccounts(1, sdk.Coins{}) + + tests := []struct { + sender sdk.AccAddress + wasmByteCode core.HexBytes + expectPass bool + }{ + {addrs[0], []byte{}, false}, + {sdk.AccAddress{}, []byte{1,2, 3}, false}, + {addrs[0], make([]byte, EnforcedMaxContractSize + 1), false}, + {addrs[0], []byte{1, 2, 3}, true}, + } + + for i, tc := range tests { + msg := NewMsgStoreCode(tc.sender, tc.wasmByteCode) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test: %v", i) + } else { + require.NotNil(t, msg.ValidateBasic(), "test: %v", i) + } + } +} + +func TestMsgInstantiateCode(t *testing.T) { + _, addrs, _, _ := mock.CreateGenAccounts(1, sdk.Coins{}) + + tests := []struct { + creator sdk.AccAddress + codeID uint64 + initMsg core.HexBytes + initCoins sdk.Coins + expectPass bool + }{ + {sdk.AccAddress{}, 0, []byte{}, sdk.Coins{}, false}, + {addrs[0], 0, make([]byte, EnforcedMaxContractMsgSize + 1), sdk.Coins{}, false}, + {addrs[0], 0, []byte{}, sdk.Coins{{Amount: sdk.NewInt(1)}}, false}, + {addrs[0], 0, []byte{}, sdk.Coins{}, true}, + } + + for i, tc := range tests { + msg := NewMsgInstantiateContract(tc.creator, tc.codeID, tc.initMsg, tc.initCoins) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test: %v", i) + } else { + require.NotNil(t, msg.ValidateBasic(), "test: %v", i) + } + } +} + +func TestMsgExecuteCode(t *testing.T) { + _, addrs, _, _ := mock.CreateGenAccounts(2, sdk.Coins{}) + + tests := []struct { + sender sdk.AccAddress + contract sdk.AccAddress + msg core.HexBytes + coins sdk.Coins + expectPass bool + }{ + {sdk.AccAddress{}, addrs[1], []byte{}, sdk.Coins{}, false}, + {addrs[0], sdk.AccAddress{}, []byte{}, sdk.Coins{}, false}, + {addrs[0], addrs[1], make([]byte, EnforcedMaxContractMsgSize + 1), sdk.Coins{}, false}, + {addrs[0], addrs[1], []byte{}, sdk.Coins{{Amount: sdk.NewInt(1)}}, false}, + {addrs[0], addrs[1], []byte{}, sdk.Coins{}, true}, + } + + for i, tc := range tests { + msg := NewMsgExecuteContract(tc.sender, tc.contract, tc.msg, tc.coins) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test: %v", i) + } else { + require.NotNil(t, msg.ValidateBasic(), "test: %v", i) + } + } +} diff --git a/x/wasm/internal/types/params.go b/x/wasm/internal/types/params.go index cfaaa96b7..65039d32e 100644 --- a/x/wasm/internal/types/params.go +++ b/x/wasm/internal/types/params.go @@ -11,17 +11,26 @@ import ( // DefaultParamspace defines default space for treasury params const DefaultParamspace = ModuleName +// Max params for static check +const ( + EnforcedMaxContractSize = uint64(500 * 1024) // 500KB + EnforcedMaxContractGas = uint64(900_000_000) // 900,000,000 + EnforcedMaxContractMsgSize = uint64(10 * 1024) // 10KB +) + // Parameter keys var ( - ParamStoreKeyMaxContractSize = []byte("maxcontractsize") - ParamStoreKeyMaxContractGas = []byte("maxcontractgas") - ParamStoreKeyGasMultiplier = []byte("gasmultiplier") + ParamStoreKeyMaxContractSize = []byte("maxcontractsize") + ParamStoreKeyMaxContractGas = []byte("maxcontractgas") + ParamStoreKeyMaxContractMsgSize = []byte("maxcontractmsgsize") + ParamStoreKeyGasMultiplier = []byte("gasmultiplier") ) // Default parameter values -var ( - DefaultMaxContractSize = int64(500 * 1024) // 500 KB - DefaultMaxContractGas = uint64(900_000_000) // 900,000,000 +const ( + DefaultMaxContractSize = EnforcedMaxContractSize // 500 KB + DefaultMaxContractGas = uint64(EnforcedMaxContractGas) // 900,000,000 + DefaultMaxContractMsgSize = uint64(1 * 1024) // 1KB // SDK reference costs can be found here: https://github.com/cosmos/cosmos-sdk/blob/02c6c9fafd58da88550ab4d7d494724a477c8a68/store/types/gas.go#L153-L164 // A write at ~3000 gas and ~200us = 10 gas per us (microsecond) cpu/io // Rough timing have 88k gas at 90us, which is equal to 1k sdk gas... (one read) @@ -32,17 +41,19 @@ var _ params.ParamSet = &Params{} // Params wasm parameters type Params struct { - MaxContractSize int64 `json:"max_contract_size" yaml:"max_contract_size"` // allowed max contract bytes size - MaxContractGas uint64 `json:"max_contract_gas" yaml:"max_contract_gas"` // allowed max gas usages per each contract execution - GasMultiplier uint64 `json:"gas_multiplier" yaml:"gas_multiplier"` // defines how many cosmwasm gas points = 1 sdk gas point + MaxContractSize uint64 `json:"max_contract_size" yaml:"max_contract_size"` // allowed max contract bytes size + MaxContractGas uint64 `json:"max_contract_gas" yaml:"max_contract_gas"` // allowed max gas usages per each contract execution + MaxContractMsgSize uint64 `json:"max_contract_msg_size" yaml:"max_contract_msg_size"` // allowed max contract exe msg bytes size + GasMultiplier uint64 `json:"gas_multiplier" yaml:"gas_multiplier"` // defines how many cosmwasm gas points = 1 sdk gas point } // DefaultParams creates default treasury module parameters func DefaultParams() Params { return Params{ - MaxContractSize: DefaultMaxContractSize, - MaxContractGas: DefaultMaxContractGas, - GasMultiplier: DefaultGasMultiplier, + MaxContractSize: DefaultMaxContractSize, + MaxContractGas: DefaultMaxContractGas, + MaxContractMsgSize: DefaultMaxContractMsgSize, + GasMultiplier: DefaultGasMultiplier, } } @@ -53,6 +64,7 @@ func (p *Params) ParamSetPairs() params.ParamSetPairs { return params.ParamSetPairs{ params.NewParamSetPair(ParamStoreKeyMaxContractSize, &p.MaxContractSize, validateMaxContractSize), params.NewParamSetPair(ParamStoreKeyMaxContractGas, &p.MaxContractGas, validateMaxContractGas), + params.NewParamSetPair(ParamStoreKeyMaxContractMsgSize, &p.MaxContractMsgSize, validateMaxContractMsgSize), params.NewParamSetPair(ParamStoreKeyGasMultiplier, &p.GasMultiplier, validateGasMultiplier), } } @@ -70,29 +82,33 @@ func (p Params) String() string { // Validate params func (p Params) Validate() error { - if p.MaxContractSize < 1024 || p.MaxContractSize > 500*1024 { - return fmt.Errorf("max contract byte size %d must be between [1KB, 500KB]", p.MaxContractSize) + if p.MaxContractSize > EnforcedMaxContractSize { + return fmt.Errorf("max contract byte size %d must be equal or smaller than 500KB", p.MaxContractSize) } if p.GasMultiplier <= 0 { return fmt.Errorf("gas multiplier %d must be positive", p.GasMultiplier) } - if p.MaxContractGas > 900_000_000 { + if p.MaxContractGas > EnforcedMaxContractGas { return fmt.Errorf("max contract gas %d must be equal or smaller than 900,000,000 (enforced in rust)", p.MaxContractGas) } + if p.MaxContractMsgSize > EnforcedMaxContractMsgSize { + return fmt.Errorf("max contract msg byte size %d must be equal or smaller than 10KB", p.MaxContractMsgSize) + } + return nil } func validateMaxContractSize(i interface{}) error { - v, ok := i.(int64) + v, ok := i.(uint64) if !ok { return fmt.Errorf("invalid parameter type: %T", i) } - if v < 1024 || v > 500*1024 { - return fmt.Errorf("max contract byte size %d must be between [1KB, 500KB]", v) + if v > EnforcedMaxContractSize { + return fmt.Errorf("max contract byte size %d must be equal or smaller than 500KB", v) } return nil @@ -117,9 +133,22 @@ func validateMaxContractGas(i interface{}) error { return fmt.Errorf("invalid parameter type: %T", i) } - if v > 900_000_000 { + if v > EnforcedMaxContractGas { return fmt.Errorf("max contract gas %d must be equal or smaller than 900,000,000 (enforced in rust)", v) } return nil } + +func validateMaxContractMsgSize(i interface{}) error { + v, ok := i.(uint64) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + if v > EnforcedMaxContractMsgSize { + return fmt.Errorf("max contract msg byte size %d must be equal or smaller than 10KB", v) + } + + return nil +} diff --git a/x/wasm/simulation/genesis.go b/x/wasm/simulation/genesis.go index 59332516f..2bb66c58f 100644 --- a/x/wasm/simulation/genesis.go +++ b/x/wasm/simulation/genesis.go @@ -13,14 +13,15 @@ import ( // Simulation parameter constants const ( - maxContractSizeKey = "max_contract_size" - maxContractGasKey = "max_contract_gas" - gasMultiplierKey = "gas_multiplier" + maxContractSizeKey = "max_contract_size" + maxContractGasKey = "max_contract_gas" + maxContractMsgSizeKey = "max_contract_msg_size" + gasMultiplierKey = "gas_multiplier" ) // GenMaxContractSize randomized MaxContractSize -func GenMaxContractSize(r *rand.Rand) int64 { - return int64(1024 + r.Intn(499*1024)) +func GenMaxContractSize(r *rand.Rand) uint64 { + return uint64(1024 + r.Intn(499*1024)) } // GenMaxContractGas randomized MaxContractGas @@ -28,6 +29,11 @@ func GenMaxContractGas(r *rand.Rand) uint64 { return uint64(10000 + r.Intn(500_000_000)) } +// GenMaxContractMsgSize randomized MaxContractMsgSize +func GenMaxContractMsgSize(r *rand.Rand) uint64 { + return uint64(128 + r.Intn(9*1024)) +} + // GenGasMultiplier randomized GasMultiplier func GenGasMultiplier(r *rand.Rand) uint64 { return uint64(1 + r.Intn(99)) @@ -36,7 +42,7 @@ func GenGasMultiplier(r *rand.Rand) uint64 { // RandomizedGenState generates a random GenesisState for wasm func RandomizedGenState(simState *module.SimulationState) { - var maxContractSize int64 + var maxContractSize uint64 simState.AppParams.GetOrGenerate( simState.Cdc, maxContractSizeKey, &maxContractSize, simState.Rand, func(r *rand.Rand) { maxContractSize = GenMaxContractSize(r) }, @@ -48,6 +54,12 @@ func RandomizedGenState(simState *module.SimulationState) { func(r *rand.Rand) { maxContractGas = GenMaxContractGas(r) }, ) + var maxContractMsgSize uint64 + simState.AppParams.GetOrGenerate( + simState.Cdc, maxContractMsgSizeKey, &maxContractMsgSize, simState.Rand, + func(r *rand.Rand) { maxContractMsgSize = GenMaxContractMsgSize(r) }, + ) + var gasMultiplier uint64 simState.AppParams.GetOrGenerate( simState.Cdc, gasMultiplierKey, &gasMultiplier, simState.Rand, @@ -56,9 +68,10 @@ func RandomizedGenState(simState *module.SimulationState) { wasmGenesis := types.NewGenesisState( types.Params{ - MaxContractSize: maxContractSize, - MaxContractGas: maxContractGas, - GasMultiplier: gasMultiplier, + MaxContractSize: maxContractSize, + MaxContractGas: maxContractGas, + MaxContractMsgSize: maxContractMsgSize, + GasMultiplier: gasMultiplier, }, 0, 0, diff --git a/x/wasm/simulation/params.go b/x/wasm/simulation/params.go index 9460318ea..78f04b5ae 100644 --- a/x/wasm/simulation/params.go +++ b/x/wasm/simulation/params.go @@ -25,6 +25,11 @@ func ParamChanges(r *rand.Rand) []simulation.ParamChange { return fmt.Sprintf("\"%d\"", GenMaxContractGas(r)) }, ), + simulation.NewSimParamChange(types.ModuleName, string(types.ParamStoreKeyMaxContractMsgSize), + func(r *rand.Rand) string { + return fmt.Sprintf("\"%d\"", GenMaxContractMsgSize(r)) + }, + ), simulation.NewSimParamChange(types.ModuleName, string(types.ParamStoreKeyGasMultiplier), func(r *rand.Rand) string { return fmt.Sprintf("\"%d\"", GenGasMultiplier(r)) diff --git a/x/wasm/test_utils.go b/x/wasm/test_utils.go index 64b382f72..157179ef4 100644 --- a/x/wasm/test_utils.go +++ b/x/wasm/test_utils.go @@ -1,3 +1,4 @@ +// nolint:deadcode unused noalias package wasm import (