Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Add handle epoch for price feed contract #195

Merged
merged 2 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/band_price_source.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,13 @@ Create channel
```bash
rly tx channel {$PATH_NAME} --src-port wasm.$price_feed --dst-port oracle --order unordered --version bandchain-1 --home $relayer_home --override
```

## Register handle epoch for price feed contract

Currently, the price feed contract only allow to call handle epoch via SudoMsg. Therefore, we need to register price feed contract to the chain.

```bash
meshconsumerd tx meshsecurity submit-proposal set-price-feed $price_feed --title "Title" --summary "Summary" --from $addr --keyring-backend test --home=$home --node $node --chain-id $chainid -y --deposit 10000000stake
```

After submitting proposal, we vote for the proposal to pass. Meshsecurity module will automatically set the handle_epoch task for price feed contract.
30 changes: 30 additions & 0 deletions docs/proto/proto-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -287,6 +289,33 @@ ValidatorAddress payload data to be used with the scheduler



<a name="osmosis.meshsecurity.v1beta1.MsgSetPriceFeedContract"></a>

### 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. |






<a name="osmosis.meshsecurity.v1beta1.MsgSetPriceFeedContractResponse"></a>

### MsgSetPriceFeedContractResponse
MsgSetPriceFeedContractResponse returns result data.






<a name="osmosis.meshsecurity.v1beta1.MsgSetVirtualStakingMaxCap"></a>

### MsgSetVirtualStakingMaxCap
Expand Down Expand Up @@ -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 | |

<!-- end services -->

Expand Down
21 changes: 21 additions & 0 deletions proto/osmosis/meshsecurity/v1beta1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {}
3 changes: 1 addition & 2 deletions scripts/mesh/testibc/rly.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 3 additions & 2 deletions scripts/mesh/testibc/rly_band.sh
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ init_band_price_feed=$(cat <<EOF
"prepare_gas": "40000",
"execute_gas": "300000",
"minimum_sources": 2,
"epoch_in_secs": 30,
"price_info_ttl_in_secs": 60
}
EOF
Expand All @@ -66,7 +67,7 @@ echo "price feed contract: $price_feed"

rly tx channel demo-band --src-port wasm.$price_feed --dst-port oracle --order unordered --version bandchain-1 --home ./scripts/relayer --override

sleep 5
sleep 7

screen -S relayer -t relayer -d -m rly start demo-band --home ./scripts/relayer --debug-addr localhost:5184
screen -S relayer-band -t relayer-band -d -m rly start demo-band --home ./scripts/relayer
sleep 5
2 changes: 1 addition & 1 deletion tests/testdata/copy_local_wasm_aarch64.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ command -v shellcheck > /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_remote_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
Expand Down
Binary file modified tests/testdata/mesh_band_price_feed.wasm.gz
Binary file not shown.
Binary file modified tests/testdata/mesh_converter.wasm.gz
Binary file not shown.
Binary file modified tests/testdata/mesh_external_staking.wasm.gz
Binary file not shown.
Binary file modified tests/testdata/mesh_native_staking.wasm.gz
Binary file not shown.
Binary file modified tests/testdata/mesh_native_staking_proxy.wasm.gz
Binary file not shown.
Binary file modified tests/testdata/mesh_osmosis_price_feed.wasm.gz
Binary file not shown.
Binary file modified tests/testdata/mesh_simple_price_feed.wasm.gz
Binary file not shown.
Binary file modified tests/testdata/mesh_vault.wasm.gz
Binary file not shown.
Binary file modified tests/testdata/mesh_virtual_staking.wasm.gz
Binary file not shown.
2 changes: 1 addition & 1 deletion tests/testdata/version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
da560f398b59d3a7430efbc1e71cbb0bcf62ad7a
44c6e65b581a08b5994a86792978f34ce9c51d5d
58 changes: 58 additions & 0 deletions x/meshsecurity/client/cli/gov_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ func SubmitProposalCmd() *cobra.Command {
}
cmd.AddCommand(
ProposalSetVirtualStakingMaxCapCmd(),
ProposalSetPriceFeedContractCmd(),
)
return cmd
}
Expand Down Expand Up @@ -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 {
Expand All @@ -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")
Expand Down
27 changes: 26 additions & 1 deletion x/meshsecurity/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
73 changes: 73 additions & 0 deletions x/meshsecurity/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
}
}
4 changes: 2 additions & 2 deletions x/meshsecurity/keeper/scheduler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
Expand Down
2 changes: 2 additions & 0 deletions x/meshsecurity/types/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ import (
// RegisterLegacyAminoCodec register types with legacy amino
func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {
cdc.RegisterConcrete(&MsgSetVirtualStakingMaxCap{}, "meshsecurity/MsgSetVirtualStakingMaxCap", nil)
cdc.RegisterConcrete(&MsgSetPriceFeedContract{}, "meshsecurity/MsgSetPriceFeedContract", nil)
}

// RegisterInterfaces register types with interface registry
func RegisterInterfaces(registry codectypes.InterfaceRegistry) {
registry.RegisterImplementations(
(*sdk.Msg)(nil),
&MsgSetVirtualStakingMaxCap{},
&MsgSetPriceFeedContract{},
)
msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc)
}
Expand Down
1 change: 1 addition & 0 deletions x/meshsecurity/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ var (
ErrMaxCapExceeded = errorsmod.Register(ModuleName, 2, "max cap exceeded")
ErrUnsupported = errorsmod.Register(ModuleName, 3, "unsupported")
ErrUnknown = errorsmod.Register(ModuleName, 4, "unknown")
ErrDuplicate = errorsmod.Register(ModuleName, 5, "contract duplicated")
)
24 changes: 23 additions & 1 deletion x/meshsecurity/types/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ func (msg MsgSetVirtualStakingMaxCap) GetSignBytes() []byte {
return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&msg))
}

// GetSigners returns the expected signers for MsgSoftwareUpgrade.
// GetSigners returns the expected signers for MsgSetVirtualStakingMaxCap.
func (msg MsgSetVirtualStakingMaxCap) GetSigners() []sdk.AccAddress {
addr, _ := sdk.AccAddressFromBech32(msg.Authority)
return []sdk.AccAddress{addr}
Expand All @@ -31,3 +31,25 @@ func (msg MsgSetVirtualStakingMaxCap) ValidateBasic() error {
}
return nil
}

// GetSignBytes implements the LegacyMsg interface.
func (msg MsgSetPriceFeedContract) GetSignBytes() []byte {
return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&msg))
}

// GetSigners returns the expected signers for MsgSetPriceFeedContract.
func (msg MsgSetPriceFeedContract) GetSigners() []sdk.AccAddress {
addr, _ := sdk.AccAddressFromBech32(msg.Authority)
return []sdk.AccAddress{addr}
}

// ValidateBasic validate basic constraints
func (msg MsgSetPriceFeedContract) ValidateBasic() error {
if _, err := sdk.AccAddressFromBech32(msg.Authority); err != nil {
return sdkerrors.ErrInvalidAddress.Wrapf("invalid authority address: %s", err)
}
if _, err := sdk.AccAddressFromBech32(msg.Contract); err != nil {
return errorsmod.Wrap(err, "contract")
}
return nil
}
Loading
Loading