Skip to content

Commit

Permalink
Feat: Add proposal unset gasless (CosmWasm#53)
Browse files Browse the repository at this point in the history
* add msg unset gasless

* add test

* cli

* add msg unset gasless

* add test

* cli

* fix: wrong out and opt path protobuf gen

* add unauthorized case

* sign

---------

Co-authored-by: ducphamle2 <ducphamle212@gmail.com>
  • Loading branch information
GNaD13 and ducphamle2 authored Dec 18, 2024
1 parent b916208 commit ac1be19
Show file tree
Hide file tree
Showing 9 changed files with 671 additions and 126 deletions.
4 changes: 2 additions & 2 deletions proto/buf.gen.doc.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
version: v1
plugins:
- name: doc
out: ../docs/proto
opt: ../docs/proto/protodoc-markdown.tmpl,proto-docs.md
out: ../client/docs/proto
opt: ../client/docs/proto/protodoc-markdown.tmpl,proto-docs.md
18 changes: 18 additions & 0 deletions proto/cosmwasm/wasm/v1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ service Msg {
// SetGaslessContract set contracts are gasless
rpc SetGaslessContracts(MsgSetGaslessContracts)
returns (MsgSetGaslessContractsResponse);
// UnsetGaslessContract unset the gasless contract
rpc UnsetGaslessContracts(MsgUnsetGaslessContracts)
returns (MsgUnsetGaslessContractsResponse);
}

// MsgStoreCode submit Wasm code to the system
Expand Down Expand Up @@ -539,3 +542,18 @@ message MsgSetGaslessContracts {

// MsgSetGaslessContractsResponse returns empty data
message MsgSetGaslessContractsResponse {}

message MsgUnsetGaslessContracts {
option (amino.name) = "wasm/MsgUnsetGaslessContracts";
option (cosmos.msg.v1.signer) = "authority";

// Authority is the address of the governance account.
string authority = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ];
// Contracts are the addresses of the smart contracts
repeated string contracts = 2 [
(cosmos_proto.scalar) = "cosmos.AddressString",
(gogoproto.moretags) = "yaml:\"contracts\""
];
}

message MsgUnsetGaslessContractsResponse {}
4 changes: 2 additions & 2 deletions scripts/protocgen.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ done

protoc_install_proto_gen_doc

echo "Generating proto docs"
buf generate --template buf.gen.doc.yml
#echo "Generating proto docs"
#buf generate --template buf.gen.doc.yml

cd ..

Expand Down
32 changes: 24 additions & 8 deletions x/wasm/client/cli/gov_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ func SubmitProposalCmd() *cobra.Command {
ProposalAddCodeUploadParamsAddresses(),
ProposalRemoveCodeUploadParamsAddresses(),
ProposalStoreAndMigrateContractCmd(),
ProposalSetGaslessContractsCmd(),
ProposalUnsetGaslessContractsCmd(),
)
return cmd
}
Expand Down Expand Up @@ -719,10 +721,17 @@ func ProposalSetGaslessContractsCmd() *cobra.Command {
return err
}

msg := types.SetGasLessContractsProposal{
Title: proposalTitle,
Description: summary,
ContractAddresses: args,
authority, err := cmd.Flags().GetString(flagAuthority)
if err != nil {
return fmt.Errorf("authority: %s", err)
}

if len(authority) == 0 {
return errors.New("authority address is required")
}

msg := types.MsgSetGaslessContracts{
Contracts: args,
}
if err = msg.ValidateBasic(); err != nil {
return err
Expand Down Expand Up @@ -753,10 +762,17 @@ func ProposalUnsetGaslessContractsCmd() *cobra.Command {
return err
}

msg := types.UnsetGasLessContractsProposal{
Title: proposalTitle,
Description: summary,
ContractAddresses: args,
authority, err := cmd.Flags().GetString(flagAuthority)
if err != nil {
return fmt.Errorf("authority: %s", err)
}

if len(authority) == 0 {
return errors.New("authority address is required")
}

msg := types.MsgUnsetGaslessContracts{
Contracts: args,
}
if err = msg.ValidateBasic(); err != nil {
return err
Expand Down
25 changes: 25 additions & 0 deletions x/wasm/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -508,3 +508,28 @@ func (m msgServer) SetGaslessContracts(ctx context.Context, msg *types.MsgSetGas

return &types.MsgSetGaslessContractsResponse{}, nil
}

func (m msgServer) UnsetGaslessContracts(goCtx context.Context, req *types.MsgUnsetGaslessContracts) (*types.MsgUnsetGaslessContractsResponse, error) {
if err := req.ValidateBasic(); err != nil {
return nil, err
}

authority := m.keeper.GetAuthority()
if authority != req.Authority {
return nil, errorsmod.Wrapf(types.ErrInvalid, "invalid authority; expected %s, got %s", authority, req.Authority)
}

ctx := sdk.UnwrapSDKContext(goCtx)

for _, v := range req.Contracts {
contractAddr, err := sdk.AccAddressFromBech32(v)
if err != nil {
return nil, errorsmod.Wrap(err, "contract")
}
if err := m.keeper.unsetGasless(ctx, contractAddr); err != nil {
return nil, errorsmod.Wrapf(err, "contract address: %s", v)
}
}

return &types.MsgUnsetGaslessContractsResponse{}, nil
}
89 changes: 89 additions & 0 deletions x/wasm/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
package keeper

import (
"encoding/json"
"os"
"testing"

tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
dbm "github.com/cosmos/cosmos-db"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"cosmossdk.io/log"
"cosmossdk.io/store"
storemetrics "cosmossdk.io/store/metrics"

sdk "github.com/cosmos/cosmos-sdk/types"
sdktx "github.com/cosmos/cosmos-sdk/types/tx"

"github.com/CosmWasm/wasmd/x/wasm/types"
)
Expand Down Expand Up @@ -57,3 +61,88 @@ func TestSelectAuthorizationPolicy(t *testing.T) {
})
}
}

func TestSetGaslessAndUnsetGasLessProposal(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
wasmKeeper := keepers.WasmKeeper
govKeeper := keepers.GovKeeper

myAddress := DeterministicAccountAddress(t, 1)

wasmKeeper.SetParams(ctx, types.Params{
CodeUploadAccess: types.AllowEverybody,
InstantiateDefaultPermission: types.AccessTypeEverybody,
})

wasmCode, err := os.ReadFile("./testdata/hackatom.wasm")
require.NoError(t, err)

codeInfo := types.CodeInfoFixture(types.WithSHA256CodeHash(wasmCode), func(codeInfo *types.CodeInfo) {
codeInfo.Creator = sdk.AccAddress(myAddress).String()
})
err = wasmKeeper.importCode(ctx, 1, codeInfo, wasmCode)
require.NoError(t, err)

// instantiate contract
_, bob := keyPubAddr()
initMsg := HackatomExampleInitMsg{
Verifier: myAddress,
Beneficiary: bob,
}
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
contractAddress, _, err := wasmKeeper.instantiate(ctx, 1, myAddress, myAddress, initMsgBz, "labels", nil, wasmKeeper.ClassicAddressGenerator(), DefaultAuthorizationPolicy{})
require.NoError(t, err)

// Test unauthorized case
unauthorizedMsgSetGasLessProposal := &types.MsgSetGaslessContracts{
Authority: myAddress.String(),
Contracts: []string{contractAddress.String()},
}
_, err = govKeeper.SubmitProposal(ctx, []sdk.Msg{unauthorizedMsgSetGasLessProposal}, "metadata", "title", "sumary", myAddress, true)
require.Error(t, err)

// Test SetGasLess
// store proposal
em := sdk.NewEventManager()
msgSetGasLessProposal := &types.MsgSetGaslessContracts{
Authority: wasmKeeper.GetAuthority(),
Contracts: []string{contractAddress.String()},
}
storedProposal, err := govKeeper.SubmitProposal(ctx, []sdk.Msg{msgSetGasLessProposal}, "metadata", "title", "sumary", myAddress, true)
require.NoError(t, err)

// execute proposal
msgs, err := sdktx.GetMsgs(storedProposal.Messages, "sdk.MsgProposal")
require.NoError(t, err)

handler := govKeeper.Router().Handler(msgs[0])
result, err := handler(ctx.WithEventManager(em), msgs[0])
require.NoError(t, err)
require.NotEmpty(t, result)

// check store
isGasLess := wasmKeeper.IsGasless(ctx, contractAddress)
require.True(t, isGasLess)

// Test UnsetGasLess
msgUnsetGasLessProposal := &types.MsgUnsetGaslessContracts{
Authority: wasmKeeper.GetAuthority(),
Contracts: []string{contractAddress.String()},
}
storedProposal, err = govKeeper.SubmitProposal(ctx, []sdk.Msg{msgUnsetGasLessProposal}, "metadata", "title", "sumary", myAddress, true)
require.NoError(t, err)

// execute proposal
msgs, err = sdktx.GetMsgs(storedProposal.Messages, "sdk.MsgProposal")
require.NoError(t, err)

handler = govKeeper.Router().Handler(msgs[0])
result, err = handler(ctx.WithEventManager(em), msgs[0])
require.NoError(t, err)
require.NotEmpty(t, result)

// check store
isGasLess = wasmKeeper.IsGasless(ctx, contractAddress)
require.False(t, isGasLess)
}
1 change: 1 addition & 0 deletions x/wasm/types/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ func RegisterInterfaces(registry types.InterfaceRegistry) {
&MsgStoreAndMigrateContract{},
&MsgUpdateContractLabel{},
&MsgSetGaslessContracts{},
&MsgUnsetGaslessContracts{},
)
registry.RegisterInterface("cosmwasm.wasm.v1.ContractInfoExtension", (*ContractInfoExtension)(nil))

Expand Down
23 changes: 23 additions & 0 deletions x/wasm/types/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -576,3 +576,26 @@ func (msg MsgSetGaslessContracts) ValidateBasic() error {
}
return nil
}

func (msg MsgUnsetGaslessContracts) Route() string {
return RouterKey
}

func (msg MsgUnsetGaslessContracts) Type() string {
return "unset-gasless-contracts"
}

func (msg MsgUnsetGaslessContracts) ValidateBasic() error {
if _, err := sdk.AccAddressFromBech32(msg.Authority); err != nil {
return errorsmod.Wrap(err, "authority")
}
for i, c := range msg.Contracts {
if _, err := sdk.AccAddressFromBech32(c); err != nil {
return errorsmod.Wrapf(err, "invalid contract address at %d", i)
}
}
if hasDuplicates(msg.Contracts) {
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "duplicate contract addresses")
}
return nil
}
Loading

0 comments on commit ac1be19

Please sign in to comment.