diff --git a/docs/proto/proto-docs.md b/docs/proto/proto-docs.md index dcd66663..ac8f77bc 100644 --- a/docs/proto/proto-docs.md +++ b/docs/proto/proto-docs.md @@ -26,6 +26,8 @@ - [ValidatorAddress](#osmosis.meshsecurity.v1beta1.ValidatorAddress) - [osmosis/meshsecurity/v1beta1/tx.proto](#osmosis/meshsecurity/v1beta1/tx.proto) + - [MsgSetPriceFeedContract](#osmosis.meshsecurity.v1beta1.MsgSetPriceFeedContract) + - [MsgSetPriceFeedContractResponse](#osmosis.meshsecurity.v1beta1.MsgSetPriceFeedContractResponse) - [MsgSetVirtualStakingMaxCap](#osmosis.meshsecurity.v1beta1.MsgSetVirtualStakingMaxCap) - [MsgSetVirtualStakingMaxCapResponse](#osmosis.meshsecurity.v1beta1.MsgSetVirtualStakingMaxCapResponse) @@ -287,6 +289,33 @@ ValidatorAddress payload data to be used with the scheduler + + +### MsgSetPriceFeedContract +MsgSetPriceFeedContract sets the price feed contract to the chain +to trigger handle epoch task + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `authority` | [string](#string) | | Authority is the address that controls the module (defaults to x/gov unless overwritten). | +| `contract` | [string](#string) | | Contract is the address of the price feed smart contract. | + + + + + + + + +### MsgSetPriceFeedContractResponse +MsgSetPriceFeedContractResponse returns result data. + + + + + + ### MsgSetVirtualStakingMaxCap @@ -329,6 +358,7 @@ Msg defines the wasm Msg service. | Method Name | Request Type | Response Type | Description | HTTP Verb | Endpoint | | ----------- | ------------ | ------------- | ------------| ------- | -------- | | `SetVirtualStakingMaxCap` | [MsgSetVirtualStakingMaxCap](#osmosis.meshsecurity.v1beta1.MsgSetVirtualStakingMaxCap) | [MsgSetVirtualStakingMaxCapResponse](#osmosis.meshsecurity.v1beta1.MsgSetVirtualStakingMaxCapResponse) | SetVirtualStakingMaxCap creates or updates a maximum cap limit for virtual staking coins | | +| `SetPriceFeedContract` | [MsgSetPriceFeedContract](#osmosis.meshsecurity.v1beta1.MsgSetPriceFeedContract) | [MsgSetPriceFeedContractResponse](#osmosis.meshsecurity.v1beta1.MsgSetPriceFeedContractResponse) | SetPriceFeedContract sets the price feed contract to the chain to trigger handle epoch task | | diff --git a/proto/osmosis/meshsecurity/v1beta1/tx.proto b/proto/osmosis/meshsecurity/v1beta1/tx.proto index 90434bb1..99f88966 100644 --- a/proto/osmosis/meshsecurity/v1beta1/tx.proto +++ b/proto/osmosis/meshsecurity/v1beta1/tx.proto @@ -15,6 +15,10 @@ service Msg { // staking coins rpc SetVirtualStakingMaxCap(MsgSetVirtualStakingMaxCap) returns (MsgSetVirtualStakingMaxCapResponse); + // SetPriceFeedContract sets the price feed contract to the chain + // to trigger handle epoch task + rpc SetPriceFeedContract(MsgSetPriceFeedContract) + returns (MsgSetPriceFeedContractResponse); } // MsgSetVirtualStakingMaxCap creates or updates a maximum cap limit for virtual @@ -37,3 +41,20 @@ message MsgSetVirtualStakingMaxCap { // MsgSetVirtualStakingMaxCap returns result data. message MsgSetVirtualStakingMaxCapResponse {} + +// MsgSetPriceFeedContract sets the price feed contract to the chain +// to trigger handle epoch task +message MsgSetPriceFeedContract { + option (amino.name) = "meshsecurity/MsgSetPriceFeedContract"; + option (cosmos.msg.v1.signer) = "authority"; + + // Authority is the address that controls the module (defaults to x/gov unless + // overwritten). + string authority = 1; + + // Contract is the address of the price feed smart contract. + string contract = 2; +} + +// MsgSetPriceFeedContractResponse returns result data. +message MsgSetPriceFeedContractResponse {} diff --git a/scripts/mesh/testibc/rly.sh b/scripts/mesh/testibc/rly.sh index 5ee649a5..4a98b673 100755 --- a/scripts/mesh/testibc/rly.sh +++ b/scripts/mesh/testibc/rly.sh @@ -33,6 +33,5 @@ rly tx channel demo --src-port wasm.$converter --dst-port wasm.$ext_staking --or sleep 5 -echo "abcxyz" -# screen -S relayer -t relayer -d -m rly start demo +screen -S relayer -t relayer -d -m rly start demo sleep 5 \ No newline at end of file diff --git a/scripts/mesh/testibc/rly_band.sh b/scripts/mesh/testibc/rly_band.sh index dd860707..b31c77de 100755 --- a/scripts/mesh/testibc/rly_band.sh +++ b/scripts/mesh/testibc/rly_band.sh @@ -53,6 +53,7 @@ init_band_price_feed=$(cat < /dev/null && shellcheck "$0" echo "DEV-only: copy from local built instead of downloading" -for contract in mesh_external_staking mesh_converter mesh_native_staking mesh_native_staking_proxy mesh_osmosis_price_provider mesh_osmosis_price_feed mesh_simple_price_feed \ +for contract in mesh_external_staking mesh_converter mesh_native_staking mesh_native_staking_proxy mesh_band_price_feed mesh_osmosis_price_feed mesh_simple_price_feed \ mesh_vault mesh_virtual_staking ; do cp -f ../../../mesh-security/artifacts/${contract}-aarch64.wasm . gzip -fk ${contract}-aarch64.wasm diff --git a/tests/testdata/mesh_band_price_feed.wasm.gz b/tests/testdata/mesh_band_price_feed.wasm.gz index 357ec578..2fcbbe12 100644 Binary files a/tests/testdata/mesh_band_price_feed.wasm.gz and b/tests/testdata/mesh_band_price_feed.wasm.gz differ diff --git a/tests/testdata/mesh_converter.wasm.gz b/tests/testdata/mesh_converter.wasm.gz index 8a550046..c92116b6 100644 Binary files a/tests/testdata/mesh_converter.wasm.gz and b/tests/testdata/mesh_converter.wasm.gz differ diff --git a/tests/testdata/mesh_external_staking.wasm.gz b/tests/testdata/mesh_external_staking.wasm.gz index 56865646..6e460d93 100644 Binary files a/tests/testdata/mesh_external_staking.wasm.gz and b/tests/testdata/mesh_external_staking.wasm.gz differ diff --git a/tests/testdata/mesh_native_staking.wasm.gz b/tests/testdata/mesh_native_staking.wasm.gz index ed30a596..9459baa8 100644 Binary files a/tests/testdata/mesh_native_staking.wasm.gz and b/tests/testdata/mesh_native_staking.wasm.gz differ diff --git a/tests/testdata/mesh_native_staking_proxy.wasm.gz b/tests/testdata/mesh_native_staking_proxy.wasm.gz index 0edf24eb..855a9501 100644 Binary files a/tests/testdata/mesh_native_staking_proxy.wasm.gz and b/tests/testdata/mesh_native_staking_proxy.wasm.gz differ diff --git a/tests/testdata/mesh_osmosis_price_feed.wasm.gz b/tests/testdata/mesh_osmosis_price_feed.wasm.gz index b1c9d32c..59e2684e 100644 Binary files a/tests/testdata/mesh_osmosis_price_feed.wasm.gz and b/tests/testdata/mesh_osmosis_price_feed.wasm.gz differ diff --git a/tests/testdata/mesh_simple_price_feed.wasm.gz b/tests/testdata/mesh_simple_price_feed.wasm.gz index c2d9af91..f9d75ebc 100644 Binary files a/tests/testdata/mesh_simple_price_feed.wasm.gz and b/tests/testdata/mesh_simple_price_feed.wasm.gz differ diff --git a/tests/testdata/mesh_vault.wasm.gz b/tests/testdata/mesh_vault.wasm.gz index 607a76ab..8303570b 100644 Binary files a/tests/testdata/mesh_vault.wasm.gz and b/tests/testdata/mesh_vault.wasm.gz differ diff --git a/tests/testdata/mesh_virtual_staking.wasm.gz b/tests/testdata/mesh_virtual_staking.wasm.gz index f37871cd..4e17c3f2 100644 Binary files a/tests/testdata/mesh_virtual_staking.wasm.gz and b/tests/testdata/mesh_virtual_staking.wasm.gz differ diff --git a/tests/testdata/version.txt b/tests/testdata/version.txt index 7f1d16f4..10142f3f 100644 --- a/tests/testdata/version.txt +++ b/tests/testdata/version.txt @@ -1 +1 @@ -1c1f84d56570fda573ba3aa56263e2e01be12c57 +44c6e65b581a08b5994a86792978f34ce9c51d5d diff --git a/x/meshsecurity/client/cli/gov_tx.go b/x/meshsecurity/client/cli/gov_tx.go index 22186946..2c660ee5 100644 --- a/x/meshsecurity/client/cli/gov_tx.go +++ b/x/meshsecurity/client/cli/gov_tx.go @@ -33,6 +33,7 @@ func SubmitProposalCmd() *cobra.Command { } cmd.AddCommand( ProposalSetVirtualStakingMaxCapCmd(), + ProposalSetPriceFeedContractCmd(), ) return cmd } @@ -86,6 +87,55 @@ $ %s tx meshsecurity submit-proposal set-virtual-staking-max-cap %s1l94ptufswr6v return cmd } +func ProposalSetPriceFeedContractCmd() *cobra.Command { + bech32Prefix := sdk.GetConfig().GetBech32AccountAddrPrefix() + cmd := &cobra.Command{ + Use: "set-price-feed [contract_addr_bech32] --title [text] --summary [text] --authority [address]", + Short: "Submit a set virtual staking max cap proposal", + Args: cobra.ExactArgs(1), + Long: strings.TrimSpace( + fmt.Sprintf(`Submit a proposal to set price feed contract to chain. + +Example: +$ %s tx meshsecurity submit-proposal set-price-feed %s1l94ptufswr6v7qntax4m7nvn3jgf6k4gn2rknq --title "a title" --summary "a summary" --authority %s +`, version.AppName, bech32Prefix, DefaultGovAuthority.String())), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, proposalTitle, summary, metadata, deposit, err := getProposalInfo(cmd) + if err != nil { + return err + } + 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") + } + + src, err := parseSetPriceFeedContractArgs(args, authority) + if err != nil { + return err + } + + proposalMsg, err := v1.NewMsgSubmitProposal([]sdk.Msg{&src}, deposit, clientCtx.GetFromAddress().String(), metadata, proposalTitle, summary) + if err != nil { + return err + } + if err = proposalMsg.ValidateBasic(); err != nil { + return err + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), proposalMsg) + }, + SilenceUsage: true, + } + + // proposal flags + addCommonProposalFlags(cmd) + return cmd +} + func parseSetVirtualStakingMaxCapArgs(args []string, authority string) (types.MsgSetVirtualStakingMaxCap, error) { maxCap, err := sdk.ParseCoinNormalized(args[1]) if err != nil { @@ -100,6 +150,14 @@ func parseSetVirtualStakingMaxCapArgs(args []string, authority string) (types.Ms return msg, nil } +func parseSetPriceFeedContractArgs(args []string, authority string) (types.MsgSetPriceFeedContract, error) { + msg := types.MsgSetPriceFeedContract{ + Authority: authority, + Contract: args[0], + } + return msg, nil +} + func addCommonProposalFlags(cmd *cobra.Command) { flags.AddTxFlagsToCmd(cmd) cmd.Flags().String(cli.FlagTitle, "", "Title of proposal") diff --git a/x/meshsecurity/keeper/msg_server.go b/x/meshsecurity/keeper/msg_server.go index 1d81decb..412b5a12 100644 --- a/x/meshsecurity/keeper/msg_server.go +++ b/x/meshsecurity/keeper/msg_server.go @@ -42,10 +42,35 @@ func (m msgServer) SetVirtualStakingMaxCap(goCtx context.Context, req *types.Msg return nil, err } if !m.k.HasScheduledTask(ctx, types.SchedulerTaskHandleEpoch, acc, true) { - if err := m.k.ScheduleRegularRebalanceTask(ctx, acc); err != nil { + if err := m.k.ScheduleRegularHandleEpochTask(ctx, acc); err != nil { return nil, errorsmod.Wrap(err, "schedule regular rebalance task") } return &types.MsgSetVirtualStakingMaxCapResponse{}, nil } return &types.MsgSetVirtualStakingMaxCapResponse{}, nil } + +// SetPriceFeedContract sets the price feed contract to the chain to trigger handle epoch task +func (m msgServer) SetPriceFeedContract(goCtx context.Context, req *types.MsgSetPriceFeedContract) (*types.MsgSetPriceFeedContractResponse, error) { + if err := req.ValidateBasic(); err != nil { + return nil, err + } + + if authority := m.k.GetAuthority(); authority != req.Authority { + return nil, govtypes.ErrInvalidSigner.Wrapf("invalid authority; expected %s, got %s", authority, req.Authority) + } + + acc, err := sdk.AccAddressFromBech32(req.Contract) + if err != nil { + return nil, errorsmod.Wrap(err, "contract") + } + ctx := sdk.UnwrapSDKContext(goCtx) + if !m.k.HasScheduledTask(ctx, types.SchedulerTaskHandleEpoch, acc, true) { + if err := m.k.ScheduleRegularHandleEpochTask(ctx, acc); err != nil { + return nil, errorsmod.Wrap(err, "schedule regular rebalance task") + } + return &types.MsgSetPriceFeedContractResponse{}, nil + } else { + return nil, types.ErrDuplicate + } +} diff --git a/x/meshsecurity/keeper/msg_server_test.go b/x/meshsecurity/keeper/msg_server_test.go index 74bd70e8..c56107d4 100644 --- a/x/meshsecurity/keeper/msg_server_test.go +++ b/x/meshsecurity/keeper/msg_server_test.go @@ -89,3 +89,76 @@ func TestSetVirtualStakingMaxCap(t *testing.T) { }) } } + +func TestSetPriceFeedContract(t *testing.T) { + pCtx, keepers := CreateDefaultTestInput(t) + k := keepers.MeshKeeper + myContract := sdk.AccAddress(rand.Bytes(32)) + denom := keepers.StakingKeeper.BondDenom(pCtx) + myAmount := sdk.NewInt64Coin(denom, 123) + + k.wasm = MockWasmKeeper{HasContractInfoFn: func(ctx sdk.Context, contractAddress sdk.AccAddress) bool { + return contractAddress.Equals(myContract) + }} + m := NewMsgServer(k) + + specs := map[string]struct { + src types.MsgSetPriceFeedContract + setup func(ctx sdk.Context) + expErr bool + expLimit sdk.Coin + expSchedule func(t *testing.T, ctx sdk.Context) + }{ + "limit stored with scheduler for existing contract": { + setup: func(ctx sdk.Context) {}, + src: types.MsgSetPriceFeedContract{ + Authority: k.GetAuthority(), + Contract: myContract.String(), + }, + expLimit: myAmount, + expSchedule: func(t *testing.T, ctx sdk.Context) { + assert.True(t, k.HasScheduledTask(ctx, types.SchedulerTaskHandleEpoch, myContract, true)) + }, + }, + "fails for non existing contract": { + setup: func(ctx sdk.Context) {}, + src: types.MsgSetPriceFeedContract{ + Authority: k.GetAuthority(), + Contract: sdk.AccAddress(rand.Bytes(32)).String(), + }, + expErr: true, + }, + "unauthorized rejected": { + setup: func(ctx sdk.Context) {}, + src: types.MsgSetPriceFeedContract{ + Authority: myContract.String(), + Contract: myContract.String(), + }, + expErr: true, + }, + "invalid data rejected": { + setup: func(ctx sdk.Context) {}, + src: types.MsgSetPriceFeedContract{}, + expErr: true, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + ctx, _ := pCtx.CacheContext() + spec.setup(ctx) + + // when + gotRsp, gotErr := m.SetPriceFeedContract(sdk.WrapSDKContext(ctx), &spec.src) + + // then + if spec.expErr { + require.Error(t, gotErr) + return + } + require.NoError(t, gotErr) + assert.NotNil(t, gotRsp) + // and scheduled + spec.expSchedule(t, ctx) + }) + } +} diff --git a/x/meshsecurity/keeper/scheduler.go b/x/meshsecurity/keeper/scheduler.go index 194058c2..6fe4ec6d 100644 --- a/x/meshsecurity/keeper/scheduler.go +++ b/x/meshsecurity/keeper/scheduler.go @@ -13,8 +13,8 @@ import ( "github.com/osmosis-labs/mesh-security-sdk/x/meshsecurity/types" ) -// ScheduleRegularRebalanceTask schedule a rebalance task for the given virtual staking contract using params defined epoch length -func (k Keeper) ScheduleRegularRebalanceTask(ctx sdk.Context, contract sdk.AccAddress) error { +// ScheduleRegularHandleEpochTask schedule a handle epoch task for the given virtual staking contract using params defined epoch length +func (k Keeper) ScheduleRegularHandleEpochTask(ctx sdk.Context, contract sdk.AccAddress) error { if !k.wasm.HasContractInfo(ctx, contract) { return types.ErrUnknown.Wrapf("contract: %s", contract.String()) }