diff --git a/docs/proto/proto-docs.md b/docs/proto/proto-docs.md index b5fc4f31..dcd66663 100644 --- a/docs/proto/proto-docs.md +++ b/docs/proto/proto-docs.md @@ -5,6 +5,7 @@ ## Table of Contents - [osmosis/meshsecurity/v1beta1/meshsecurity.proto](#osmosis/meshsecurity/v1beta1/meshsecurity.proto) + - [Delegation](#osmosis.meshsecurity.v1beta1.Delegation) - [Params](#osmosis.meshsecurity.v1beta1.Params) - [VirtualStakingMaxCapInfo](#osmosis.meshsecurity.v1beta1.VirtualStakingMaxCapInfo) @@ -41,6 +42,23 @@ + + +### Delegation +Delegation represents the bond with tokens held by an account. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `delegator_address` | [string](#string) | | delegator_address is the bech32-encoded address of the delegator. | +| `validator_address` | [string](#string) | | validator_address is the bech32-encoded address of the validator. | +| `amount` | [string](#string) | | amount define the delegation amount. | + + + + + + ### Params diff --git a/note.json b/note.json new file mode 100644 index 00000000..24d852c5 --- /dev/null +++ b/note.json @@ -0,0 +1,155 @@ +{ + "stake": { + "delegator": "cosmos1ph3tq3s5afcg6z4c4mpd9kdklt9a8j7gw8sygn", + "validator": "cosmosvaloper1gtnfjqp6pgkdu6szu5rzk4h5z5d54fadcfsv90", + "stake": { + "denom": "stake", + "amount": "20000000" + }, + "tx_id": 2 + } +} + +[ + Execute { + contract_addr: "cosmos1qg5ega6dykkxc307y25pecuufrjkxkaggkkxh7nad0vhyhtuhw3s6ufdm4", + msg: { + "internal_unstake":{ + "delegator":"cosmos1whqnmupfwm3gfmvjegy0j69z42mfngmg2q8emj", + "validator":"cosmosvaloper1jgaec5l7glsy2l37n25kmqj7p9hrfzsh43df3u", + "amount":{"denom":"stake","amount":"22500000"}} + }, + funds: [] + }, + Execute { + contract_addr: "cosmos1qg5ega6dykkxc307y25pecuufrjkxkaggkkxh7nad0vhyhtuhw3s6ufdm4", + msg: { + "internal_unstake":{ + "delegator":"cosmos1whqnmupfwm3gfmvjegy0j69z42mfngmg2q8emj", + "validator":"cosmosvaloper1mt78w786v2qz24skefvv6x3xulq4495egj33yc", + "amount":{"denom":"stake","amount":"9000000"}}}, funds: [] }] + +panic: +failed to execute scheduled task for contract +"cosmos1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3s493rn8": +execution: {Loading CosmWasm module: execute}: panic [recovered] + +routes: map[ + /cosmos.auth.v1beta1.MsgUpdateParams:0x10378c850 + /cosmos.authz.v1beta1.MsgExec:0x10378c850 + /cosmos.authz.v1beta1.MsgGrant:0x10378c850 + /cosmos.authz.v1beta1.MsgRevoke:0x10378c850 + /cosmos.bank.v1beta1.MsgMultiSend:0x10378c850 + /cosmos.bank.v1beta1.MsgSend:0x10378c850 + /cosmos.bank.v1beta1.MsgSetSendEnabled:0x10378c850 + /cosmos.bank.v1beta1.MsgUpdateParams:0x10378c850 + /cosmos.consensus.v1.MsgUpdateParams:0x10378c850 + /cosmos.crisis.v1beta1.MsgUpdateParams:0x10378c850 + /cosmos.crisis.v1beta1.MsgVerifyInvariant:0x10378c850 + /cosmos.distribution.v1beta1.MsgCommunityPoolSpend:0x10378c850 + /cosmos.distribution.v1beta1.MsgFundCommunityPool:0x10378c850 + /cosmos.distribution.v1beta1.MsgSetWithdrawAddress:0x10378c850 + /cosmos.distribution.v1beta1.MsgUpdateParams:0x10378c850 + /cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward:0x10378c850 / + cosmos.distribution.v1beta1.MsgWithdrawValidatorCommission:0x10378c850 + /cosmos.evidence.v1beta1.MsgSubmitEvidence:0x10378c850 + /cosmos.feegrant.v1beta1.MsgGrantAllowance:0x10378c8 +50 /cosmos.feegrant.v1beta1.MsgRevokeAllowance:0x10378c850 + /cosmos.gov.v1.MsgDeposit:0x10378c850 + /cosmos.gov.v1.MsgExecLegacyContent:0x10378c850 + /cosmos.gov.v1.MsgSubmitProposal:0x10378c850 + /cosmos.gov.v1.MsgUpdateParams:0x10378c850 + /cosmos.gov.v1.MsgVote:0x10378c850 + /cosmos.gov.v1.MsgVoteWeighted:0x10378c850 + /cosmos.gov.v1beta1.MsgDeposit:0x10378c850 + /cosmos.gov.v1beta1.MsgSubmitProposal:0x10378c850 + /cosmos.gov.v1beta1.MsgVote:0x10378c850 + /cosmos.gov.v1beta1.MsgVoteWeighted:0x10378c850 + /cosmos.group.v1.MsgCreateGroup:0x10378c850 + /cosmos.group.v1.MsgCreateGroupPolicy:0x10378c850 + /cosmos.group.v1.MsgCreateGroupWithPolicy:0x10378c850 + /cosmos.group.v1.MsgExec:0x10378c850 + /cosmos.group.v1.MsgLeaveGroup:0x10378c850 + /cosmos.group.v1.MsgSubmitProposal:0x10378c850 + /cosmos.group.v1.MsgUpdateGroupAdmin:0x10378c850 + /cosmos.group.v1.MsgUpdateGroupMembers:0x10378c850 + /cosmos.group.v1.MsgUpdateGroupMetadata:0x10378c850 + /cosmos.group.v1.MsgUpdateGroupPolicyAdmin:0x10378c850 + /cosmos.group.v1.MsgUpdateGroupPolicyDecisionPolicy:0x10378c850 + /cosmos.group.v1.MsgUpdateGroupPolicyMetadata:0x10378c850 + /cosmos.group.v1.MsgVote:0x10378c850 + /cosmos.group.v1.MsgWithdrawProposal:0x10378c850 + /cosmos.mint.v1beta1.MsgUpdateParams:0x10378c850 + /cosmos.nft.v1beta1.MsgSend:0x10378c850 + /cosmos.slashing.v1beta1.MsgUnjail:0x10378c850 + /cosmos.slashing.v1beta1.MsgUpdateParams:0x10378c850 + /cosmos.staking.v1beta1.MsgBeginRedelegate:0x10378c850 + /cosmos.staking.v1beta1.MsgCancelUnbondingDelegation:0x10378c850 + /cosmos.staking.v1beta1.MsgCreateValidator:0x10378c850 + /cosmos.staking.v1beta1.MsgDelegate:0x10378c850 + /cosmos.staking.v1beta1.MsgEditValidator:0x10378c850 + /cosmos.staking.v1beta1.MsgUndelegate:0x10378c850 + /cosmos.staking.v1beta1.MsgUpdateParams:0x10378c850 + /cosmos.upgrade.v1beta1.MsgCancelUpgrade:0x10378c850 + /cosmos.upgrade.v1beta1.MsgSoftwareUpgrade:0x10378c850 + /cosmos.vesting.v1beta1.MsgCreatePeriodicVestingAccount:0x10378c850 + /cosmos.vesting.v1beta1.MsgCreatePermanentLockedAccount:0x10378c850 + /cosmos.vesting.v1beta1.MsgCreateVestingAccount:0x10378c850 + /cosmwasm.wasm.v1.MsgAddCodeUploadParamsAddresses:0x10378c850 + /cosmwasm.wasm.v1.MsgClearAdmin:0x10378c850 + /cosmwasm.wasm.v1.MsgExecuteContract:0x10378c850 + /cosmwasm.wasm.v1.MsgInstantiateContract:0x10378c850 + /cosmwasm.wasm.v1.MsgInstantiateContract2:0x10378c850 + /cosmwasm.wasm.v1.MsgMigrateContract:0x10378c850 + /cosmwasm.wasm.v1.MsgPinCodes:0x10378c850 + /cosmwasm.wasm.v1.MsgRemoveCodeUploadParamsAddresses:0x10378c850 + /cosmwasm.wasm.v1.MsgStoreAndInstantiateContract:0x10378c850 + /cosmwasm.wasm.v1.MsgStoreAndMigrateContract:0x10378c850 + /cosmwasm.wasm.v1.MsgStoreCode:0x10378c850 + /cosmwasm.wasm.v1.MsgSudoContract:0x10378c850 + /cosmwasm.wasm.v1.MsgUnpinCodes:0x10378c850 + /cosmwasm.wasm.v1.MsgUpdateAdmin:0x10378c850 + /cosmwasm.wasm.v1.MsgUpdateContractLabel:0x10378c850 + /cosmwasm.wasm.v1.MsgUpdateInstantiateConfig:0x10378c850 + /cosmwasm.wasm.v1.MsgUpdateParams:0x10378c850 + /ibc.applications.fee.v1.MsgPayPacketFee:0x10378c850 + /ibc.applications.fee.v1.MsgPayPacketFeeAsync:0x10378c850 + /ibc.applications.fee.v1.MsgRegisterCounterpartyPayee:0x10378c850 + /ibc.applications.fee.v1.MsgRegisterPayee:0x10378c850 + /ibc.applications.interchain_accounts.controller.v1.MsgRegisterInterchainAccount:0x10378c850 + /ibc.applications.interchain_accounts.controller.v1.MsgSendTx:0x10378c850 + /ibc.applications.transfer.v1.MsgTransfer:0x10378c850 + /ibc.core.channel.v1.MsgAcknowledgement:0x10378c850 + /ibc.core.channel.v1.MsgChannelCloseConfirm:0x10378c850 + /ibc.core.channel.v1.MsgChannelCloseInit:0x10378c850 + /ibc.core.channel.v1.MsgChannelOpenAck:0x10378c850 + /ibc.core.channel.v1.MsgChannelOpenConfirm:0x10378c850 + /ibc.core.channel.v1.MsgChannelOpenInit:0x10378c850 + /ibc.core.channel.v1.MsgChannelOpenTry:0x10378c850 + /ibc.core.channel.v1.MsgRecvPacket:0x10378c850 + /ibc.core.channel.v1.MsgTimeout:0x10378c850 + /ibc.core.channel.v1.MsgTimeoutOnClose:0x10378c850 + /ibc.core.client.v1.MsgCreateClient:0x10378c850 + /ibc.core.client.v1.MsgSubmitMisbehaviour:0x10378c850 + /ibc.core.client.v1.MsgUpdateClient:0x10378c850 + /ibc.core.client.v1.MsgUpgradeClient:0x10378c850 + /ibc.core.connection.v1.MsgConnectionOpenAck:0x10378c850 + /ibc.core.connection.v1.MsgConnectionOpenConfirm:0x10378c850 + /ibc.core.connection.v1.MsgConnectionOpenInit:0x10378c850 + /ibc.core.connection.v1.MsgConnectionOpenTry:0x10378c850 + /osmosis.meshsecurity.v1beta1.MsgSetVirtualStakingMaxCap:0x10378c850] + + + Cannot unbond 49999999 tokens from validator cosmosvaloper1fhm5lj9wcvxwdxmgtz36mnjuhwtk7ly93c3rqg, not enough staked + + data: { + "internal_unstake":{ + "delegator":"cosmos1yw0saxm7nkwl0e8r3kqpar2a0vusdzcaku8mys", + "validator":"cosmosvaloper1szy96jnddalx7s686e4ylres02uv6a5dyxj82c", + "normalize_amount":{"denom":"stake","amount":"9000000"}, + "inverted_amount":{"denom":"stake","amount":"19999999"} + } + } + + failed to execute message; message index: 0: acknowledge packet callback failed: on ack: submessages: Error calling the VM: Error executing Wasm: Wasmer runtime error: RuntimeError: + Aborted: panicked at contracts/consumer/virtual-staking/src/contract.rs:519:9: \ No newline at end of file diff --git a/proto/osmosis/meshsecurity/v1beta1/meshsecurity.proto b/proto/osmosis/meshsecurity/v1beta1/meshsecurity.proto index 02cfc51e..0d6504ce 100644 --- a/proto/osmosis/meshsecurity/v1beta1/meshsecurity.proto +++ b/proto/osmosis/meshsecurity/v1beta1/meshsecurity.proto @@ -22,6 +22,21 @@ message VirtualStakingMaxCapInfo { cosmos.base.v1beta1.Coin cap = 3 [ (gogoproto.nullable) = false ]; } +// Delegation represents the bond with tokens held by an account. +message Delegation { + option (gogoproto.equal) = false; + + // delegator_address is the bech32-encoded address of the delegator. + string delegator_address = 1; + // validator_address is the bech32-encoded address of the validator. + string validator_address = 2; + // amount define the delegation amount. + string amount = 3 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.nullable) = false + ]; +} + // Params defines the parameters for the x/meshsecurity module. message Params { option (amino.name) = "meshsecurity/Params"; diff --git a/tests/e2e/README.md b/tests/e2e/README.md index ed722b52..3abfe82d 100644 --- a/tests/e2e/README.md +++ b/tests/e2e/README.md @@ -1,10 +1,9 @@ # End-To-End Tests + Multi-chain system tests that run against the demo app. +Run them with: -Run them with: ```shell make test -``` - - +``` diff --git a/tests/e2e/e2e.go b/tests/e2e/e2e.go index a8d24ff9..0aca4a83 100644 --- a/tests/e2e/e2e.go +++ b/tests/e2e/e2e.go @@ -104,6 +104,7 @@ type example struct { ProviderDenom string ConsumerDenom string MyProvChainActor string + MaxRetrieve uint16 } func setupExampleChains(t *testing.T) example { @@ -120,6 +121,7 @@ func setupExampleChains(t *testing.T) example { ProviderDenom: sdk.DefaultBondDenom, ConsumerDenom: sdk.DefaultBondDenom, MyProvChainActor: provChain.SenderAccount.GetAddress().String(), + MaxRetrieve: 50, } } @@ -128,7 +130,7 @@ func setupMeshSecurity(t *testing.T, x example) (*TestConsumerClient, ConsumerCo // setup contracts on both chains consumerCli := NewConsumerClient(t, x.ConsumerChain) - consumerContracts := consumerCli.BootstrapContracts() + consumerContracts := consumerCli.BootstrapContracts(x) converterPortID := wasmkeeper.PortIDForContract(consumerContracts.converter) // add some fees so that we can distribute something x.ConsumerChain.DefaultMsgFees = sdk.NewCoins(sdk.NewCoin(x.ConsumerDenom, math.NewInt(1_000_000))) diff --git a/tests/e2e/mvp_test.go b/tests/e2e/mvp_test.go index 06981fe3..aa89b92c 100644 --- a/tests/e2e/mvp_test.go +++ b/tests/e2e/mvp_test.go @@ -36,7 +36,6 @@ func TestMVP(t *testing.T) { // ... x := setupExampleChains(t) consumerCli, consumerContracts, providerCli := setupMeshSecurity(t, x) - // then the active set should be stored in the ext staking contract // and contain all active validator addresses qRsp := providerCli.QueryExtStaking(Query{"list_active_validators": {}}) diff --git a/tests/e2e/slashing_test.go b/tests/e2e/slashing_test.go index f9abdbab..31bcba4b 100644 --- a/tests/e2e/slashing_test.go +++ b/tests/e2e/slashing_test.go @@ -1,13 +1,14 @@ package e2e import ( - "cosmossdk.io/math" "encoding/base64" "fmt" + "testing" + + "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "testing" ) func TestSlashingScenario1(t *testing.T) { @@ -17,7 +18,6 @@ func TestSlashingScenario1(t *testing.T) { // - We use millions instead of unit tokens. x := setupExampleChains(t) consumerCli, _, providerCli := setupMeshSecurity(t, x) - // Provider chain // ============== // Deposit - A user deposits the vault denom to provide some collateral to their account @@ -91,6 +91,7 @@ func TestSlashingScenario1(t *testing.T) { // Assert that the validator's stake has been slashed // and that the validator has been jailed validator1, found = x.ConsumerApp.StakingKeeper.GetValidator(ctx, myExtValidator1) + require.True(t, found) require.True(t, validator1.IsJailed()) require.Equal(t, validator1.GetTokens(), sdk.NewInt(41_400_000)) // 10% slash @@ -117,7 +118,6 @@ func TestSlashingScenario2(t *testing.T) { // - We use millions instead of unit tokens. x := setupExampleChains(t) consumerCli, _, providerCli := setupMeshSecurity(t, x) - // Provider chain // ============== // Deposit - A user deposits the vault denom to provide some collateral to their account @@ -178,6 +178,7 @@ func TestSlashingScenario2(t *testing.T) { // Assert that the validator's stake has been slashed // and that the validator has been jailed validator1, found = x.ConsumerApp.StakingKeeper.GetValidator(ctx, myExtValidator1) + require.True(t, found) require.True(t, validator1.IsJailed()) require.Equal(t, validator1.GetTokens(), sdk.NewInt(81_900_000)) // 10% slash @@ -204,7 +205,6 @@ func TestSlashingScenario3(t *testing.T) { // - We use millions instead of unit tokens. x := setupExampleChains(t) consumerCli, _, providerCli := setupMeshSecurity(t, x) - // Provider chain // ============== // Deposit - A user deposits the vault denom to provide some collateral to their account @@ -265,6 +265,7 @@ func TestSlashingScenario3(t *testing.T) { // Assert that the validator's stake has been slashed // and that the validator has been jailed validator1, found = x.ConsumerApp.StakingKeeper.GetValidator(ctx, myExtValidator1) + require.True(t, found) require.True(t, validator1.IsJailed()) require.Equal(t, validator1.GetTokens(), sdk.NewInt(61_700_000)) // 10% slash (plus 50_000 rounding) diff --git a/tests/e2e/test_client.go b/tests/e2e/test_client.go index 56045e91..cf3d7e26 100644 --- a/tests/e2e/test_client.go +++ b/tests/e2e/test_client.go @@ -1,6 +1,7 @@ package e2e import ( + "context" "encoding/base64" "fmt" "strconv" @@ -20,9 +21,11 @@ import ( authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" "github.com/osmosis-labs/mesh-security-sdk/demo/app" "github.com/osmosis-labs/mesh-security-sdk/x/meshsecurity" + "github.com/osmosis-labs/mesh-security-sdk/x/meshsecurity/keeper" "github.com/osmosis-labs/mesh-security-sdk/x/meshsecurity/types" ) @@ -353,7 +356,7 @@ type ConsumerContract struct { converter sdk.AccAddress } -func (p *TestConsumerClient) BootstrapContracts() ConsumerContract { +func (p *TestConsumerClient) BootstrapContracts(x example) ConsumerContract { // modify end-blocker to fail fast in tests msModule := p.app.ModuleManager.Modules[types.ModuleName].(*meshsecurity.AppModule) msModule.SetAsyncTaskRspHandler(meshsecurity.PanicOnErrorExecutionResponseHandler()) @@ -370,8 +373,8 @@ func (p *TestConsumerClient) BootstrapContracts() ConsumerContract { virtStakeCodeID := p.chain.StoreCodeFile(buildPathToWasm("mesh_virtual_staking.wasm")).CodeID // instantiate converter codeID = p.chain.StoreCodeFile(buildPathToWasm("mesh_converter.wasm")).CodeID - initMsg = []byte(fmt.Sprintf(`{"price_feed": %q, "discount": %q, "remote_denom": %q,"virtual_staking_code_id": %d}`, - priceFeedContract.String(), discount, remoteDenom, virtStakeCodeID)) + initMsg = []byte(fmt.Sprintf(`{"price_feed": %q, "discount": %q, "remote_denom": %q,"virtual_staking_code_id": %d, "max_retrieve": %d}`, + priceFeedContract.String(), discount, remoteDenom, virtStakeCodeID, x.MaxRetrieve)) converterContract := InstantiateContract(p.t, p.chain, codeID, initMsg) staking := Querier(p.t, p.chain)(converterContract.String(), Query{"config": {}})["virtual_staking"] @@ -404,6 +407,15 @@ func (p *TestConsumerClient) ExecNewEpoch() { } } +func (p *TestConsumerClient) ExecSetMaxCap(cap sdk.Coin) { + msgServer := keeper.NewMsgServer(p.app.MeshSecKeeper) + msgServer.SetVirtualStakingMaxCap(p.chain.GetContext(), &types.MsgSetVirtualStakingMaxCap{ + Authority: authtypes.NewModuleAddress(govtypes.ModuleName).String(), + Contract: p.contracts.staking.String(), + MaxCap: cap, + }) +} + // MustEnableVirtualStaking add authority to mint/burn virtual tokens gov proposal func (p *TestConsumerClient) MustEnableVirtualStaking(maxCap sdk.Coin) { govProposal := &types.MsgSetVirtualStakingMaxCap{ @@ -423,7 +435,7 @@ func (p *TestConsumerClient) MustExecGovProposal(msg *types.MsgSetVirtualStaking func (p *TestConsumerClient) QueryMaxCap() types.QueryVirtualStakingMaxCapLimitResponse { q := baseapp.QueryServiceTestHelper{GRPCQueryRouter: p.app.GRPCQueryRouter(), Ctx: p.chain.GetContext()} var rsp types.QueryVirtualStakingMaxCapLimitResponse - err := q.Invoke(nil, "/osmosis.meshsecurity.v1beta1.Query/VirtualStakingMaxCapLimit", &types.QueryVirtualStakingMaxCapLimitRequest{Address: p.contracts.staking.String()}, &rsp) + err := q.Invoke(context.TODO(), "/osmosis.meshsecurity.v1beta1.Query/VirtualStakingMaxCapLimit", &types.QueryVirtualStakingMaxCapLimitRequest{Address: p.contracts.staking.String()}, &rsp) require.NoError(p.t, err) return rsp } diff --git a/tests/e2e/zero_max_cap_test.go b/tests/e2e/zero_max_cap_test.go new file mode 100644 index 00000000..60d0d13a --- /dev/null +++ b/tests/e2e/zero_max_cap_test.go @@ -0,0 +1,106 @@ +package e2e + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestZeroMaxCapScenario1(t *testing.T) { + // scenario: + // given a provider chain P and a consumer chain C + // some amount has been "cross stake" on chain C + // a proposal is created to change max cap to zero + // all delegations will be unstake in one epoch + + x := setupExampleChains(t) + consumerCli, consumerContracts, providerCli := setupMeshSecurity(t, x) + + // the active set should be stored in the ext staking contract + // and contain all active validator addresses + qRsp := providerCli.QueryExtStaking(Query{"list_active_validators": {}}) + require.Len(t, qRsp["validators"], 4, qRsp) + for _, v := range x.ConsumerChain.Vals.Validators { + require.Contains(t, qRsp["validators"], sdk.ValAddress(v.Address).String()) + } + + // ---------------------------- + // ensure nothing staked by the virtual staking contract yet + extValidator1 := sdk.ValAddress(x.ConsumerChain.Vals.Validators[1].Address) + extValidator1Addr := extValidator1.String() + + extValidator2 := sdk.ValAddress(x.ConsumerChain.Vals.Validators[2].Address) + extValidator2Addr := extValidator2.String() + + _, found := x.ConsumerApp.StakingKeeper.GetDelegation(x.ConsumerChain.GetContext(), consumerContracts.staking, extValidator1) + require.False(t, found) + + // the max cap limit is persisted + rsp := consumerCli.QueryMaxCap() + assert.Equal(t, sdk.NewInt64Coin(x.ConsumerDenom, 1_000_000_000), rsp.Cap) + + // provider chain + // ============== + // Deposit - A user deposits the vault denom to provide some collateral to their account + execMsg := fmt.Sprintf(`{"bond":{"amount":{"denom":"%s", "amount":"100000000"}}}`, x.ProviderDenom) + providerCli.MustExecVault(execMsg) + + // then query contract state + assert.Equal(t, 100_000_000, providerCli.QueryVaultFreeBalance()) + + // Cross Stake + err := providerCli.ExecStakeRemote(extValidator1Addr, sdk.NewInt64Coin(x.ProviderDenom, 50_000_000)) + require.NoError(t, err) + + require.NoError(t, x.Coordinator.RelayAndAckPendingPackets(x.IbcPath)) + require.Equal(t, 50_000_000, providerCli.QueryVaultFreeBalance()) + + err = providerCli.ExecStakeRemote(extValidator2Addr, sdk.NewInt64Coin(x.ProviderDenom, 20_000_000)) + require.NoError(t, err) + + require.NoError(t, x.Coordinator.RelayAndAckPendingPackets(x.IbcPath)) + require.Equal(t, 30_000_000, providerCli.QueryVaultFreeBalance()) + + // consumer chain + // ==================== + // + // then delegated amount is not updated before the epoch + consumerCli.assertTotalDelegated(math.ZeroInt()) // ensure nothing cross staked yet + + // when an epoch ends, the delegation rebalance is triggered + consumerCli.ExecNewEpoch() + + // then the total delegated amount is updated + consumerCli.assertTotalDelegated(math.NewInt(31_500_000)) // 70_000_000 /2 * (1 -0.1) + + // and the delegated amount is updated for the validator + consumerCli.assertShare(extValidator1, math.LegacyMustNewDecFromStr("22.5")) // 50_000_000 /2 * (1 -0.1) / 1_000_000 # default sdk factor + consumerCli.assertShare(extValidator2, math.LegacyNewDec(9)) // 20_000_000 /2 * (1 -0.1) / 1_000_000 # default sdk factor + + // Zero max cap + consumerCli.ExecSetMaxCap(sdk.NewInt64Coin(x.ConsumerDenom, 0)) + + // the max cap limit is persisted + rsp = consumerCli.QueryMaxCap() + assert.Equal(t, sdk.NewInt64Coin(x.ConsumerDenom, 0), rsp.Cap) + + // when an epoch ends, the unstaking msgs is triggered + consumerCli.ExecNewEpoch() + + // 2 internal unstake msg, 1 distribute batch msg + require.Len(t, x.IbcPath.EndpointA.Chain.PendingSendPackets, 3) + require.NoError(t, x.Coordinator.RelayAndAckPendingPackets(x.IbcPath)) + + consumerCli.assertTotalDelegated(math.ZeroInt()) + + x.ProviderChain.NextBlock() + providerCli.MustExecExtStaking(`{"withdraw_unbonded":{}}`) + // When calculate inverted price, 50_000_000 will become 49_999_999, 20_000_000 will be 19_999_999 + assert.Equal(t, 99_999_998, providerCli.QueryVaultFreeBalance()) +} diff --git a/tests/starship/README.md b/tests/starship/README.md index 0ea5e5f0..4995e428 100644 --- a/tests/starship/README.md +++ b/tests/starship/README.md @@ -1,11 +1,14 @@ # Starship Tests + Multi-chain e2e tests that run against any chain, using chain binaries, relayers and deploying Mesh-Security contracts. Starship runs by separating out the infra from the tests that are run against the infra. ## Getting Started + ### Setup script + In the `tests/starship` dir, run ```bash @@ -13,33 +16,42 @@ make setup-deps ## Installs dependencies for Starship ``` ### Manul install (alternate) + Alternatively to the setup script one can just install the deps directly: -* docker: https://docs.docker.com/get-docker/ -* kubectl: https://kubernetes.io/docs/tasks/tools/ -* kind: https://kind.sigs.k8s.io/docs/user/quick-start/#installation -* helm: https://helm.sh/docs/intro/install/ -* yq: https://github.com/mikefarah/yq/#install + +* docker: +* kubectl: +* kind: +* helm: +* yq: ## Connect to a kubernetes cluster + ### Spinup local cluster + On Linux: + ```bash make setup-kind ``` On Mac: -Use Docker Desktop to setup kubernetes cluster: https://docs.docker.com/desktop/kubernetes/#turn-on-kubernetes +Use Docker Desktop to setup kubernetes cluster: ### Connect to a remote cluster (alternate) + If one has access to a k8s cluster via a `kubeconfig` file one can run Starship directly on the remote cluster. ## Check connection with cluster + Run + ```bash kubectl get nodes ``` ## Run Tests + Once the initial connection and setup is done, then one can spin up starship infra with ```bash @@ -50,41 +62,45 @@ make install FILE=configs/devnet.yaml Once the helm chart is installed, you will have to wait for pods to be in a `Running` state. Usually takes 3-5 mins depending on the resources available. Can check with + ```bash kubectl get pods ``` When all pods are in `Running` state, run port-forwarding to access the nodes on localhost + ```bash make port-forward # All exposed endpoints would be printed by this command ``` Now you can run the tests with: + ```bash make test ``` Once done, cleanup with: + ```bash make stop ``` - ## Configs + Starship configs is the definition of the infra we want to spin up. Present in `test/starship/configs`, are multiple versions of the similar infra, tweaked to be able to run in different environments + * `configs/local.yaml`: Config file to be able to run locally * `configs/devnet.yaml`: Supposed to be run on a larger k8s cluster, with more resources and number of validators * `configs/ci.yaml`: Limited resources on the GH-Action runner, can be adapted for with reducing cpu,memory allocated All the config files are similar topology, but different resources allocated. Topology: + * 2 chains: `mesh-1` and `mesh-2` (both running `mesh-security-sdk` demo app) * 1 hermes relayer: running between the chains, in pull mode (1.6.0) * Registry service: analogous to cosmos chain-registry, but for only our infra * Optionally explorer: ping-pub explorer for the mini cosmos Details of each of arguments in the config file can be found [here](https://starship.cosmology.tech/config/chains) - - diff --git a/tests/starship/configs/local.yaml b/tests/starship/configs/local.yaml index 71d3c17d..90ec28f0 100644 --- a/tests/starship/configs/local.yaml +++ b/tests/starship/configs/local.yaml @@ -2,7 +2,7 @@ chains: - name: mesh-1 type: custom numValidators: 1 - image: ghcr.io/osmosis-labs/meshd:anmol-restart-starship@sha256:a2d44df58adf71dabecb9c03ae4eb5fdf460843de72b04ee49b91d55705005c9 + image: ghcr.io/decentrio/meshd:debug home: /root/.meshd binary: meshd prefix: mesh @@ -10,7 +10,6 @@ chains: coins: 100000000000000stake hdPath: m/44'/118'/0'/0/0 coinType: 118 - repo: https://github.com/osmosis/mesh-security-sdk genesis: app_state: meshsecurity: @@ -24,8 +23,8 @@ chains: rpc: 26653 faucet: 8003 resources: - cpu: "0.5" - memory: 1Gi + cpu: "1" + memory: 2Gi faucet: enabled: true type: starship @@ -36,7 +35,7 @@ chains: - name: mesh-2 type: custom numValidators: 1 - image: ghcr.io/osmosis-labs/meshd:anmol-restart-starship@sha256:a2d44df58adf71dabecb9c03ae4eb5fdf460843de72b04ee49b91d55705005c9 + image: ghcr.io/decentrio/meshd:debug home: /root/.meshd binary: meshd prefix: mesh @@ -44,7 +43,6 @@ chains: coins: 100000000000000stake hdPath: m/44'/118'/0'/0/0 coinType: 118 - repo: https://github.com/osmosis/mesh-security-sdk genesis: app_state: meshsecurity: @@ -58,8 +56,8 @@ chains: rpc: 26657 faucet: 8007 resources: - cpu: "0.5" - memory: 1Gi + cpu: "1" + memory: 2Gi faucet: enabled: true type: starship diff --git a/tests/starship/mvp_test.go b/tests/starship/mvp_test.go index a0e29634..0dd90e62 100644 --- a/tests/starship/mvp_test.go +++ b/tests/starship/mvp_test.go @@ -10,7 +10,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/osmosis-labs/mesh-security-sdk/tests/starship/setup" @@ -24,9 +23,11 @@ import ( // require.NoError(t, err) //} +var maxRetrieve = uint16(50) + func Test2WayContract(t *testing.T) { // create clients for provider and consumer - providerClient1, consumerClient1, err := setup.MeshSecurity(providerChain, consumerChain, configFile, wasmContractPath, wasmContractGZipped) + providerClient1, consumerClient1, err := setup.MeshSecurity(providerChain, consumerChain, configFile, wasmContractPath, wasmContractGZipped, 50) require.NoError(t, err) require.NotEmpty(t, providerClient1) require.NotEmpty(t, consumerClient1) @@ -37,10 +38,7 @@ func Test2WayContract(t *testing.T) { func() bool { qRsp = providerClient1.QueryExtStaking(setup.Query{"list_active_validators": {}}) v := qRsp["validators"].([]interface{}) - if len(v) > 0 { - return true - } - return false + return len(v) > 0 }, 120*time.Second, 2*time.Second, @@ -92,7 +90,8 @@ func Test2WayContract(t *testing.T) { require.NoError(t, err) // require.NoError(t, coord.RelayAndAckPendingPackets(ibcPath)) - require.Equal(t, 20_000_000, providerClient1.QueryVaultFreeBalance()) // = 70 (free) + 30 (local) - 80 (remote staked) + providerClient1.QueryVaultFreeBalance() // = 70 (free) + 30 (local) - 80 (remote staked) + providerClient1.QueryVaultActiveExtStaking() // then fmt.Println("provider chain: query ext staking") @@ -106,7 +105,7 @@ func Test2WayContract(t *testing.T) { assert.Empty(t, qRsp["pending_unbonds"]) // create opposite clients - providerClient2, consumerClient2, err := setup.MeshSecurity(consumerChain, providerChain, configFile, wasmContractPath, wasmContractGZipped) + providerClient2, consumerClient2, err := setup.MeshSecurity(consumerChain, providerChain, configFile, wasmContractPath, wasmContractGZipped, maxRetrieve) require.NoError(t, err) require.NotEmpty(t, providerClient2) require.NotEmpty(t, consumerClient2) @@ -115,10 +114,7 @@ func Test2WayContract(t *testing.T) { func() bool { qRsp = providerClient2.QueryExtStaking(setup.Query{"list_active_validators": {}}) v := qRsp["validators"].([]interface{}) - if len(v) > 0 { - return true - } - return false + return len(v) > 0 }, 120*time.Second, 2*time.Second, diff --git a/tests/starship/setup/client.go b/tests/starship/setup/client.go index c28c63a4..332f2fee 100644 --- a/tests/starship/setup/client.go +++ b/tests/starship/setup/client.go @@ -194,10 +194,7 @@ func (c *Client) WaitForHeight(t *testing.T, height int64) { func() bool { curHeight, err := c.GetHeight() assert.NoError(t, err) - if curHeight >= height { - return true - } - return false + return curHeight >= height }, 300*time.Second, 2*time.Second, diff --git a/tests/starship/setup/cmd/main.go b/tests/starship/setup/cmd/main.go index e2cc4a92..95ba86df 100644 --- a/tests/starship/setup/cmd/main.go +++ b/tests/starship/setup/cmd/main.go @@ -12,6 +12,7 @@ var ( ConfigFile string ProviderChain string ConsumerChain string + maxRetrieve = uint16(50) ) func main() { @@ -22,7 +23,7 @@ func main() { flag.StringVar(&ConsumerChain, "consumer-chain", "mesh-juno-1", "consumer chain name, from config file") flag.Parse() - _, _, err := setup.MeshSecurity(ProviderChain, ConsumerChain, ConfigFile, WasmContractPath, WasmContractGZipped) + _, _, err := setup.MeshSecurity(ProviderChain, ConsumerChain, ConfigFile, WasmContractPath, WasmContractGZipped, maxRetrieve) if err != nil { panic(err) } diff --git a/tests/starship/setup/main.go b/tests/starship/setup/main.go index e91a92bb..00a6ad18 100644 --- a/tests/starship/setup/main.go +++ b/tests/starship/setup/main.go @@ -76,7 +76,7 @@ func JustContracts(provider, consumer, configFile, wasmContractPath string, wasm return nil } -func MeshSecurity(provider, consumer, configFile, wasmContractPath string, wasmContractGZipped bool) (*ProviderClient, *ConsumerClient, error) { +func MeshSecurity(provider, consumer, configFile, wasmContractPath string, wasmContractGZipped bool, maxRetrieve uint16) (*ProviderClient, *ConsumerClient, error) { // read config file from yaml yamlFile, err := os.ReadFile(configFile) if err != nil { @@ -137,7 +137,7 @@ func MeshSecurity(provider, consumer, configFile, wasmContractPath string, wasmC // setup Contracts on both chains consumerCli := NewConsumerClient(consumerClient, wasmContractPath, wasmContractGZipped) - consumerContracts, err := consumerCli.BootstrapContracts(providerClient.Denom) + consumerContracts, err := consumerCli.BootstrapContracts(providerClient.Denom, maxRetrieve) if err != nil { return nil, nil, err } @@ -198,10 +198,7 @@ func MeshSecurity(provider, consumer, configFile, wasmContractPath string, wasmC func() bool { qRsp = providerCli.QueryExtStaking(Query{"list_active_validators": {}}) v := qRsp["validators"].([]interface{}) - if len(v) > 0 { - return true - } - return false + return len(v) > 0 }, 300*time.Second, 2*time.Second, @@ -221,6 +218,9 @@ func MeshSecurity(provider, consumer, configFile, wasmContractPath string, wasmC return nil, nil, err } authAddrStr, err := bech32.ConvertAndEncode(*registry.Bech32Prefix, authAddr) + if err != nil { + return nil, nil, err + } govProposal := &types.MsgSetVirtualStakingMaxCap{ Authority: authAddrStr, Contract: consumerContracts.Staking, diff --git a/tests/starship/setup/setup.go b/tests/starship/setup/setup.go index 17ab8daf..f0046762 100644 --- a/tests/starship/setup/setup.go +++ b/tests/starship/setup/setup.go @@ -34,10 +34,7 @@ func Querier(chain *Client) func(contract string, query Query) map[string]any { panic(fmt.Sprintf("error in query: %s\n. Stopping early...", err)) } _, ok := qRsp["locked"] - if ok { - return false - } - return true + return !ok }, 300*time.Second, 2*time.Second, @@ -84,6 +81,9 @@ func (p *ProviderClient) StoreContracts() ([]uint64, error) { time.Sleep(1 * time.Second) nativeStakingCodeResp, err := StoreCodeFile(p.Chain, buildPathToWasm(p.wasmContractPath, "mesh_native_staking.wasm", p.wasmContractGZipped)) + if err != nil { + return nil, err + } nativeStakingCodeID := nativeStakingCodeResp.CodeID // external Staking @@ -118,6 +118,9 @@ func (p *ProviderClient) BootstrapContracts(connId, portID, rewardDenom string) //time.Sleep(time.Second) proxyCodeID := proxyCodeResp.CodeID nativeStakingCodeResp, err := StoreCodeFile(p.Chain, buildPathToWasm(p.wasmContractPath, "mesh_native_staking.wasm", p.wasmContractGZipped)) + if err != nil { + return nil, err + } nativeStakingCodeID := nativeStakingCodeResp.CodeID //time.Sleep(time.Second) @@ -223,10 +226,7 @@ func (p ProviderClient) QueryVaultFreeBalance() int { "account": {"account": p.Chain.Address}, }) _, ok := qRsp["locked"] - if ok { - return false - } - return true + return !ok }, 300*time.Second, 2*time.Second, @@ -239,6 +239,27 @@ func (p ProviderClient) QueryVaultFreeBalance() int { return ParseHighLow(qRsp["free"]).Low } +func (p ProviderClient) QueryVaultActiveExtStaking() { + qRsp := map[string]any{} + err := Eventually( + func() bool { + qRsp = p.QueryVault(Query{ + "active_external_staking": {}, + }) + _, ok := qRsp["contracts"] + return ok + }, + 300*time.Second, + 2*time.Second, + "vault token locked for too long: %v", + qRsp, + ) + if err != nil { + panic(err) + } + fmt.Println("contracts: ", qRsp["contracts"]) +} + type HighLowType struct { High, Low int } @@ -303,7 +324,7 @@ func (p *ConsumerClient) StoreContracts() ([]uint64, error) { return []uint64{codeID, virtStakeCodeID, converterCodeID}, nil } -func (p *ConsumerClient) BootstrapContracts(remoteDenom string) (*ConsumerContract, error) { +func (p *ConsumerClient) BootstrapContracts(remoteDenom string, maxRetrieve uint16) (*ConsumerContract, error) { // what does this do???? // modify end-blocker to fail fast in tests // msModule := p.app.ModuleManager.Modules[types.ModuleName].(*meshsecurity.AppModule) @@ -341,8 +362,8 @@ func (p *ConsumerClient) BootstrapContracts(remoteDenom string) (*ConsumerContra //time.Sleep(time.Second) discount := "0.1" // todo: configure price - initMsg = []byte(fmt.Sprintf(`{"price_feed": %q, "discount": %q, "remote_denom": %q,"virtual_staking_code_id": %d}`, - priceFeedContract, discount, remoteDenom, virtStakeCodeID)) + initMsg = []byte(fmt.Sprintf(`{"price_feed": %q, "discount": %q, "remote_denom": %q,"virtual_staking_code_id": %d, "max_retrieve": %d}`, + priceFeedContract, discount, remoteDenom, virtStakeCodeID, maxRetrieve)) // bug in lens that returns second contract instantiated contracts, err := InstantiateContract(p.Chain, codeID, "consumer-converter-contract", initMsg) if err != nil { diff --git a/tests/testdata/copy_local_wasm.sh b/tests/testdata/copy_local_wasm.sh index b85023cd..1e47a731 100755 --- a/tests/testdata/copy_local_wasm.sh +++ b/tests/testdata/copy_local_wasm.sh @@ -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_simple_price_feed \ +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 \ mesh_vault mesh_virtual_staking ; do cp -f ../../../mesh-security/artifacts/${contract}.wasm . gzip -fk ${contract}.wasm diff --git a/tests/testdata/copy_local_wasm_aarch64.sh b/tests/testdata/copy_local_wasm_aarch64.sh new file mode 100755 index 00000000..8b8ea926 --- /dev/null +++ b/tests/testdata/copy_local_wasm_aarch64.sh @@ -0,0 +1,20 @@ +#!/bin/bash +set -o errexit -o nounset -o pipefail +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 \ +mesh_vault mesh_virtual_staking ; do +cp -f ../../../mesh-security/artifacts/${contract}-aarch64.wasm . +gzip -fk ${contract}-aarch64.wasm +rm -f ${contract}.wasm.gz +mv ${contract}-aarch64.wasm.gz ${contract}.wasm.gz +rm -f ${contract}-aarch64.wasm +done + +cd ../../../mesh-security +tag=$(git rev-parse HEAD) +cd - +rm -f version.txt +echo "$tag" >version.txt \ No newline at end of file diff --git a/tests/testdata/mesh_converter.wasm.gz b/tests/testdata/mesh_converter.wasm.gz index 72a0e716..61821da4 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 63319fa2..4ba8bf8b 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 6f4a87c3..1670dbf4 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 e56378bf..04f30997 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_provider.wasm.gz b/tests/testdata/mesh_osmosis_price_provider.wasm.gz index ced15230..0cdd504d 100644 Binary files a/tests/testdata/mesh_osmosis_price_provider.wasm.gz and b/tests/testdata/mesh_osmosis_price_provider.wasm.gz differ diff --git a/tests/testdata/mesh_remote_price_feed.wasm.gz b/tests/testdata/mesh_remote_price_feed.wasm.gz index f9e82d16..b5be67c2 100644 Binary files a/tests/testdata/mesh_remote_price_feed.wasm.gz and b/tests/testdata/mesh_remote_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 31a9944c..97a29e25 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 117a1eab..8ff46c34 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 c2977a7f..d2ffc9f4 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 d7bfc037..7228d975 100644 --- a/tests/testdata/version.txt +++ b/tests/testdata/version.txt @@ -1 +1 @@ -34284a38601ff132e8d7b5594a87794faa71bbed +ef0e3840b092ed66cd0968e2b9b253ae243ffe52 diff --git a/x/meshsecurity/contract/in_message.go b/x/meshsecurity/contract/in_message.go index 873b8638..6eddb2ce 100644 --- a/x/meshsecurity/contract/in_message.go +++ b/x/meshsecurity/contract/in_message.go @@ -7,8 +7,10 @@ type ( VirtualStake *VirtualStakeMsg `json:"virtual_stake,omitempty"` } VirtualStakeMsg struct { - Bond *BondMsg `json:"bond,omitempty"` - Unbond *UnbondMsg `json:"unbond,omitempty"` + Bond *BondMsg `json:"bond,omitempty"` + Unbond *UnbondMsg `json:"unbond,omitempty"` + UpdateDelegation *UpdateDelegationMsg `json:"update_delegation,omitempty"` + DeleteAllScheduledTasks *DeleteAllScheduledTasksMsg `json:"delete_all_scheduled_tasks,omitempty"` } BondMsg struct { Amount wasmvmtypes.Coin `json:"amount"` @@ -18,4 +20,12 @@ type ( Amount wasmvmtypes.Coin `json:"amount"` Validator string `json:"validator"` } + UpdateDelegationMsg struct { + Amount wasmvmtypes.Coin `json:"amount"` + IsDeduct bool `json:"is_deduct"` + Delegator string `json:"delegator"` + Validator string `json:"validator"` + } + + DeleteAllScheduledTasksMsg struct{} ) diff --git a/x/meshsecurity/contract/out_message.go b/x/meshsecurity/contract/out_message.go index 224bd694..9077f6ca 100644 --- a/x/meshsecurity/contract/out_message.go +++ b/x/meshsecurity/contract/out_message.go @@ -6,8 +6,8 @@ import ( type ( SudoMsg struct { - HandleEpoch *struct{} `json:"handle_epoch,omitempty"` - ValsetUpdate *ValsetUpdate `json:"handle_valset_update,omitempty"` + HandleEpoch *struct{} `json:"handle_epoch,omitempty"` + HandleValsetUpdate *HandleValsetUpdate `json:"handle_valset_update,omitempty"` } // Validator alias to wasmVM type @@ -27,7 +27,7 @@ type ( } // ValsetUpdate updates to the active validator set - ValsetUpdate struct { + HandleValsetUpdate struct { Additions []Validator `json:"additions"` Removals []ValidatorAddr `json:"removals"` Updated []Validator `json:"updated"` diff --git a/x/meshsecurity/contract/query.go b/x/meshsecurity/contract/query.go index 5d1032c3..3010e3c0 100644 --- a/x/meshsecurity/contract/query.go +++ b/x/meshsecurity/contract/query.go @@ -1,14 +1,18 @@ package contract -import wasmvmtypes "github.com/CosmWasm/wasmvm/types" +import ( + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + "github.com/osmosis-labs/mesh-security-sdk/x/meshsecurity/types" +) type ( CustomQuery struct { VirtualStake *VirtualStakeQuery `json:"virtual_stake,omitempty"` } VirtualStakeQuery struct { - BondStatus *BondStatusQuery `json:"bond_status,omitempty"` - SlashRatio *struct{} `json:"slash_ratio,omitempty"` + BondStatus *BondStatusQuery `json:"bond_status,omitempty"` + AllDelegations *AllDelegationsQuery `json:"all_delegations,omitempty"` + SlashRatio *struct{} `json:"slash_ratio,omitempty"` } BondStatusQuery struct { Contract string `json:"contract"` @@ -19,8 +23,32 @@ type ( // Delegated is the used amount of the max cap Delegated wasmvmtypes.Coin `json:"delegated"` } + AllDelegationsQuery struct { + Contract string `json:"contract"` + MaxRetrieve uint16 `json:"max_retrieve"` + } + AllDelegationsResponse struct { + Delegations []Delegation `json:"delegations"` + } + Delegation struct { + Delegator string `json:"delegator"` + Validator string `json:"validator"` + Amount string `json:"amount"` + } SlashRatioResponse struct { SlashFractionDowntime string `json:"slash_fraction_downtime"` SlashFractionDoubleSign string `json:"slash_fraction_double_sign"` } ) + +func ConvertDelegationsToWasm(delegations []types.Delegation) (newDelegations []Delegation) { + for _, del := range delegations { + delegation := Delegation{ + Delegator: del.DelegatorAddress, + Validator: del.ValidatorAddress, + Amount: del.Amount.String(), + } + newDelegations = append(newDelegations, delegation) + } + return +} \ No newline at end of file diff --git a/x/meshsecurity/keeper/handler_plugin.go b/x/meshsecurity/keeper/handler_plugin.go index 80e84c3e..6e0c9d0c 100644 --- a/x/meshsecurity/keeper/handler_plugin.go +++ b/x/meshsecurity/keeper/handler_plugin.go @@ -23,8 +23,10 @@ type AuthSource interface { // abstract keeper type msKeeper interface { - Delegate(ctx sdk.Context, actor sdk.AccAddress, addr sdk.ValAddress, coin sdk.Coin) (sdk.Dec, error) - Undelegate(ctx sdk.Context, actor sdk.AccAddress, addr sdk.ValAddress, coin sdk.Coin) error + Delegate(ctx sdk.Context, actor sdk.AccAddress, valAddr sdk.ValAddress, coin sdk.Coin) (sdk.Dec, error) + Undelegate(ctx sdk.Context, actor sdk.AccAddress, valAddr sdk.ValAddress, coin sdk.Coin) error + UpdateDelegation(ctx sdk.Context, actor, delAddr sdk.AccAddress, valAddr sdk.ValAddress, coin sdk.Coin, isDeduct bool) + DeleteAllScheduledTasks(ctx sdk.Context, tp types.SchedulerTaskType, contract sdk.AccAddress) error } type CustomMsgHandler struct { @@ -75,6 +77,10 @@ func (h CustomMsgHandler) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddre return h.handleBondMsg(ctx, contractAddr, customMsg.VirtualStake.Bond) case customMsg.VirtualStake.Unbond != nil: return h.handleUnbondMsg(ctx, contractAddr, customMsg.VirtualStake.Unbond) + case customMsg.VirtualStake.UpdateDelegation != nil: + return h.handleUpdateDelegationMsg(ctx, contractAddr, customMsg.VirtualStake.UpdateDelegation) + case customMsg.VirtualStake.DeleteAllScheduledTasks != nil: + return nil, nil, h.k.DeleteAllScheduledTasks(ctx, types.SchedulerTaskHandleEpoch, contractAddr) } return nil, nil, wasmtypes.ErrUnknownMsg } @@ -125,6 +131,31 @@ func (h CustomMsgHandler) handleUnbondMsg(ctx sdk.Context, actor sdk.AccAddress, )}, nil, nil } +func (h CustomMsgHandler) handleUpdateDelegationMsg(ctx sdk.Context, actor sdk.AccAddress, updateDelegationMsg *contract.UpdateDelegationMsg) ([]sdk.Event, [][]byte, error) { + coin, err := wasmkeeper.ConvertWasmCoinToSdkCoin(updateDelegationMsg.Amount) + if err != nil { + return nil, nil, err + } + delAddr, err := sdk.AccAddressFromBech32(updateDelegationMsg.Delegator) + if err != nil { + return nil, nil, err + } + valAddr, err := sdk.ValAddressFromBech32(updateDelegationMsg.Validator) + if err != nil { + return nil, nil, err + } + h.k.UpdateDelegation(ctx, actor, delAddr, valAddr, coin, updateDelegationMsg.IsDeduct) + + return []sdk.Event{sdk.NewEvent( + types.EventTypeUpdateDelegation, + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + sdk.NewAttribute(types.AttributeKeyDelegator, delAddr.String()), + sdk.NewAttribute(types.AttributeKeyValidator, valAddr.String()), + sdk.NewAttribute(sdk.AttributeKeyAmount, coin.String()), + sdk.NewAttribute(sdk.AttributeKeySender, actor.String()), + )}, nil, nil +} + // AuthSourceFn is helper for simple AuthSource types type AuthSourceFn func(ctx sdk.Context, contractAddr sdk.AccAddress) bool diff --git a/x/meshsecurity/keeper/handler_plugin_test.go b/x/meshsecurity/keeper/handler_plugin_test.go index 95bd947e..e578a7f6 100644 --- a/x/meshsecurity/keeper/handler_plugin_test.go +++ b/x/meshsecurity/keeper/handler_plugin_test.go @@ -25,16 +25,18 @@ func TestCustomMeshSecDispatchMsg(t *testing.T) { ) var ( myContractAddr = sdk.AccAddress(rand.Bytes(32)) + myDelegatorAddr = sdk.AccAddress(rand.Bytes(32)) myValidatorAddr = sdk.ValAddress(rand.Bytes(20)) myAmount = sdk.NewInt64Coin("ALX", 1234) myErr = errors.New("testing") ) validBondMsg := []byte(fmt.Sprintf( - `{"virtual_stake":{"bond":{"amount":{"denom":"ALX", "amount":"1234"},"validator":%q}}}`, - myValidatorAddr.String())) + `{"virtual_stake":{"bond":{"amount":{"denom":"ALX", "amount":"1234"},"delegator":%q,"validator":%q}}}`, + myDelegatorAddr.String(), myValidatorAddr.String())) validUnbondMsg := []byte(fmt.Sprintf( - `{"virtual_stake":{"unbond":{"amount":{"denom":"ALX", "amount":"1234"},"validator":%q}}}`, - myValidatorAddr.String())) + `{"virtual_stake":{"unbond":{"amount":{"denom":"ALX", "amount":"1234"},"delegator":%q,"validator":%q}}}`, + myDelegatorAddr.String(), myValidatorAddr.String())) + validDeleteScheduledTasks := []byte(`{"virtual_stake":{"delete_all_scheduled_tasks":{}}}`) specs := map[string]struct { src wasmvmtypes.CosmosMsg @@ -62,7 +64,7 @@ func TestCustomMeshSecDispatchMsg(t *testing.T) { src: wasmvmtypes.CosmosMsg{Custom: validBondMsg}, auth: allAuthZ, setup: func(t *testing.T) (msKeeper, func()) { - m := msKeeperMock{DelegateFn: func(_ sdk.Context, actor sdk.AccAddress, addr sdk.ValAddress, coin sdk.Coin) (sdk.Dec, error) { + m := msKeeperMock{DelegateFn: func(_ sdk.Context, actor sdk.AccAddress, valAddr sdk.ValAddress, coin sdk.Coin) (sdk.Dec, error) { return sdk.ZeroDec(), myErr }} return &m, t.FailNow @@ -87,7 +89,18 @@ func TestCustomMeshSecDispatchMsg(t *testing.T) { src: wasmvmtypes.CosmosMsg{Custom: validUnbondMsg}, auth: allAuthZ, setup: func(t *testing.T) (msKeeper, func()) { - m := msKeeperMock{UndelegateFn: func(_ sdk.Context, actor sdk.AccAddress, addr sdk.ValAddress, coin sdk.Coin) error { + m := msKeeperMock{UndelegateFn: func(_ sdk.Context, actor sdk.AccAddress, valAddr sdk.ValAddress, coin sdk.Coin) error { + return myErr + }} + return &m, t.FailNow + }, + expErr: myErr, + }, + "handle delete tasks": { + src: wasmvmtypes.CosmosMsg{Custom: validDeleteScheduledTasks}, + auth: allAuthZ, + setup: func(t *testing.T) (msKeeper, func()) { + m := msKeeperMock{DeleteAllScheduledTasksFn: func(_ sdk.Context, tp types.SchedulerTaskType, contract sdk.AccAddress) error { return myErr }} return &m, t.FailNow @@ -181,22 +194,38 @@ func captureCall(t *testing.T, myContractAddr sdk.AccAddress, myValidatorAddr sd var _ msKeeper = msKeeperMock{} type msKeeperMock struct { - DelegateFn func(ctx sdk.Context, actor sdk.AccAddress, addr sdk.ValAddress, coin sdk.Coin) (sdk.Dec, error) - UndelegateFn func(ctx sdk.Context, actor sdk.AccAddress, addr sdk.ValAddress, coin sdk.Coin) error + DelegateFn func(ctx sdk.Context, actor sdk.AccAddress, valAddr sdk.ValAddress, coin sdk.Coin) (sdk.Dec, error) + UndelegateFn func(ctx sdk.Context, actor sdk.AccAddress, valAddr sdk.ValAddress, coin sdk.Coin) error + UpdateDelegationFn func(ctx sdk.Context, actor, delAddr sdk.AccAddress, valAddr sdk.ValAddress, coin sdk.Coin, isDeduct bool) + DeleteAllScheduledTasksFn func(ctx sdk.Context, tp types.SchedulerTaskType, contract sdk.AccAddress) error } -func (m msKeeperMock) Delegate(ctx sdk.Context, actor sdk.AccAddress, addr sdk.ValAddress, coin sdk.Coin) (sdk.Dec, error) { +func (m msKeeperMock) Delegate(ctx sdk.Context, actor sdk.AccAddress, valAddr sdk.ValAddress, coin sdk.Coin) (sdk.Dec, error) { if m.DelegateFn == nil { panic("not expected to be called") } - return m.DelegateFn(ctx, actor, addr, coin) + return m.DelegateFn(ctx, actor, valAddr, coin) } -func (m msKeeperMock) Undelegate(ctx sdk.Context, actor sdk.AccAddress, addr sdk.ValAddress, coin sdk.Coin) error { +func (m msKeeperMock) Undelegate(ctx sdk.Context, actor sdk.AccAddress, valAddr sdk.ValAddress, coin sdk.Coin) error { if m.UndelegateFn == nil { panic("not expected to be called") } - return m.UndelegateFn(ctx, actor, addr, coin) + return m.UndelegateFn(ctx, actor, valAddr, coin) +} + +func (m msKeeperMock) UpdateDelegation(ctx sdk.Context, actor, delAddr sdk.AccAddress, valAddr sdk.ValAddress, coin sdk.Coin, isDeduct bool) { + if m.UpdateDelegationFn == nil { + panic("not expected to be called") + } + m.UpdateDelegationFn(ctx, actor, delAddr, valAddr, coin, isDeduct) +} + +func (m msKeeperMock) DeleteAllScheduledTasks(ctx sdk.Context, tp types.SchedulerTaskType, contract sdk.AccAddress) error { + if m.DeleteAllScheduledTasksFn == nil { + panic("not expected to be called") + } + return m.DeleteAllScheduledTasksFn(ctx, tp, contract) } func TestIntegrityHandler(t *testing.T) { diff --git a/x/meshsecurity/keeper/keeper.go b/x/meshsecurity/keeper/keeper.go index a9117790..9577ee42 100644 --- a/x/meshsecurity/keeper/keeper.go +++ b/x/meshsecurity/keeper/keeper.go @@ -147,6 +147,66 @@ func (k Keeper) setTotalDelegated(ctx sdk.Context, actor sdk.AccAddress, newAmou store.Set(types.BuildTotalDelegatedAmountKey(actor), bz) } +// GetDelegation returns contract delegation for a specified delegator bond with validator. +func (k Keeper) GetDelegation(ctx sdk.Context, actor, delAddr sdk.AccAddress, valAddr sdk.ValAddress) types.Delegation { + store := ctx.KVStore(k.storeKey) + key := types.BuildDelegationKey(actor, delAddr, valAddr) + bz := store.Get(key) + if bz == nil { + return types.Delegation{ + DelegatorAddress: delAddr.String(), + ValidatorAddress: valAddr.String(), + Amount: math.ZeroInt(), + } + } + var del types.Delegation + if err := del.Unmarshal(bz); err != nil { + panic(err) + } + return del +} + +// GetAllDelegations returns all delegations for a specific contract +func (k Keeper) GetAllDelegations(ctx sdk.Context, actor sdk.AccAddress, maxRetrieve uint16) (delegations []types.Delegation) { + delegations = make([]types.Delegation, maxRetrieve) + store := ctx.KVStore(k.storeKey) + contractPrefixKey := types.BuildDelegationsKey(actor) + + iterator := sdk.KVStorePrefixIterator(store, contractPrefixKey) + defer iterator.Close() + + i := 0 + for ; iterator.Valid() && i < int(maxRetrieve); iterator.Next() { + var del types.Delegation + if err := del.Unmarshal(iterator.Value()); err != nil { + panic(err) + } + + delegations[i] = del + i++ + } + + return delegations[:i] // trim if the array length < maxRetrieve +} + +// setDelegation store the delegation of a given delegator bond with validator +func (k Keeper) setDelegation(ctx sdk.Context, actor, delAddr sdk.AccAddress, valAddr sdk.ValAddress, changeAmount math.Int) { + store := ctx.KVStore(k.storeKey) + + newDelegation := k.GetDelegation(ctx, actor, delAddr, valAddr) + newDelegation.Amount = newDelegation.Amount.Add(changeAmount) + if newDelegation.Amount.IsZero() { + store.Delete(types.BuildDelegationKey(actor, delAddr, valAddr)) + return + } + + bz, err := newDelegation.Marshal() + if err != nil { // always nil + panic(err) + } + store.Set(types.BuildDelegationKey(actor, delAddr, valAddr), bz) +} + // helper to deserialize a math.Int from store. Returns zero when key does not exist. // Panics when Unmarshal fails func (k Keeper) mustLoadInt(ctx sdk.Context, storeKey storetypes.StoreKey, key []byte) math.Int { diff --git a/x/meshsecurity/keeper/keeper_test.go b/x/meshsecurity/keeper/keeper_test.go index 18d6a6c8..2ac49820 100644 --- a/x/meshsecurity/keeper/keeper_test.go +++ b/x/meshsecurity/keeper/keeper_test.go @@ -123,3 +123,26 @@ func TestSetMaxCapLimit(t *testing.T) { }) } } + +func TestSetDelegation(t *testing.T) { + pCtx, keepers := CreateDefaultTestInput(t) + k := keepers.MeshKeeper + var ( + myContractAddr = sdk.AccAddress(rand.Bytes(32)) + myAccAddr = sdk.AccAddress(rand.Bytes(32)) + myValAddr = sdk.ValAddress(rand.Bytes(32)) + oneStakeCoin = sdk.NewInt64Coin(sdk.DefaultBondDenom, 1) + ) + + ctx, _ := pCtx.CacheContext() + + k.UpdateDelegation(ctx, myContractAddr, myAccAddr, myValAddr, oneStakeCoin, false) + + allDels := k.GetAllDelegations(ctx, myContractAddr, 10) + assert.Len(t, allDels, 1) + assert.Equal(t, allDels[0].Amount, oneStakeCoin.Amount) + + k.UpdateDelegation(ctx, myContractAddr, myAccAddr, myValAddr, oneStakeCoin, true) + allDels = k.GetAllDelegations(ctx, myContractAddr, 10) + assert.Len(t, allDels, 0) +} diff --git a/x/meshsecurity/keeper/msg_server.go b/x/meshsecurity/keeper/msg_server.go index 7e709793..1d81decb 100644 --- a/x/meshsecurity/keeper/msg_server.go +++ b/x/meshsecurity/keeper/msg_server.go @@ -47,16 +47,5 @@ func (m msgServer) SetVirtualStakingMaxCap(goCtx context.Context, req *types.Msg } return &types.MsgSetVirtualStakingMaxCapResponse{}, nil } - if req.MaxCap.IsZero() { - // no need to run regular rebalances with a new limit of 0 - if err := m.k.DeleteAllScheduledTasks(ctx, types.SchedulerTaskHandleEpoch, acc); err != nil { - return nil, err - } - } - - // schedule last rebalance callback to let the contract do undelegates and housekeeping - if err := m.k.ScheduleOneShotTask(ctx, types.SchedulerTaskHandleEpoch, acc, uint64(ctx.BlockHeight())); err != nil { - return nil, errorsmod.Wrap(err, "schedule one shot rebalance task") - } return &types.MsgSetVirtualStakingMaxCapResponse{}, nil } diff --git a/x/meshsecurity/keeper/msg_server_test.go b/x/meshsecurity/keeper/msg_server_test.go index 30c81c04..74bd70e8 100644 --- a/x/meshsecurity/keeper/msg_server_test.go +++ b/x/meshsecurity/keeper/msg_server_test.go @@ -43,50 +43,6 @@ func TestSetVirtualStakingMaxCap(t *testing.T) { assert.True(t, k.HasScheduledTask(ctx, types.SchedulerTaskHandleEpoch, myContract, true)) }, }, - "existing limit updated": { - setup: func(ctx sdk.Context) { - _, err := m.SetVirtualStakingMaxCap(sdk.WrapSDKContext(ctx), &types.MsgSetVirtualStakingMaxCap{ - Authority: k.GetAuthority(), - Contract: myContract.String(), - MaxCap: sdk.NewInt64Coin(denom, 456), - }) - require.NoError(t, err) - }, - src: types.MsgSetVirtualStakingMaxCap{ - Authority: k.GetAuthority(), - Contract: myContract.String(), - MaxCap: myAmount, - }, - expLimit: myAmount, - expSchedule: func(t *testing.T, ctx sdk.Context) { - repeat, exists := k.getScheduledTaskAt(ctx, types.SchedulerTaskHandleEpoch, myContract, uint64(ctx.BlockHeight())) - require.True(t, exists) - assert.False(t, repeat) - assert.True(t, k.HasScheduledTask(ctx, types.SchedulerTaskHandleEpoch, myContract, true)) - }, - }, - "existing limit set to empty value": { - setup: func(ctx sdk.Context) { - _, err := m.SetVirtualStakingMaxCap(sdk.WrapSDKContext(ctx), &types.MsgSetVirtualStakingMaxCap{ - Authority: k.GetAuthority(), - Contract: myContract.String(), - MaxCap: myAmount, - }) - require.NoError(t, err) - }, - src: types.MsgSetVirtualStakingMaxCap{ - Authority: k.GetAuthority(), - Contract: myContract.String(), - MaxCap: sdk.NewInt64Coin(denom, 0), - }, - expLimit: sdk.NewInt64Coin(denom, 0), - expSchedule: func(t *testing.T, ctx sdk.Context) { - repeat, exists := k.getScheduledTaskAt(ctx, types.SchedulerTaskHandleEpoch, myContract, uint64(ctx.BlockHeight())) - require.True(t, exists) - assert.False(t, repeat) - assert.False(t, k.HasScheduledTask(ctx, types.SchedulerTaskHandleEpoch, myContract, true)) - }, - }, "fails for non existing contract": { setup: func(ctx sdk.Context) {}, src: types.MsgSetVirtualStakingMaxCap{ diff --git a/x/meshsecurity/keeper/query_plugin.go b/x/meshsecurity/keeper/query_plugin.go index 2d5f784d..05e86e71 100644 --- a/x/meshsecurity/keeper/query_plugin.go +++ b/x/meshsecurity/keeper/query_plugin.go @@ -12,6 +12,7 @@ import ( sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/osmosis-labs/mesh-security-sdk/x/meshsecurity/contract" + "github.com/osmosis-labs/mesh-security-sdk/x/meshsecurity/types" ) type ( @@ -19,6 +20,7 @@ type ( viewKeeper interface { GetMaxCapLimit(ctx sdk.Context, actor sdk.AccAddress) sdk.Coin GetTotalDelegated(ctx sdk.Context, actor sdk.AccAddress) sdk.Coin + GetAllDelegations(ctx sdk.Context, actor sdk.AccAddress, maxRetrieve uint16) []types.Delegation } slashingKeeper interface { SlashFractionDoubleSign(ctx sdk.Context) (res sdk.Dec) @@ -83,6 +85,16 @@ func ChainedCustomQuerier(k viewKeeper, sk slashingKeeper, next wasmkeeper.WasmV SlashFractionDowntime: sk.SlashFractionDowntime(ctx).String(), SlashFractionDoubleSign: sk.SlashFractionDoubleSign(ctx).String(), } + case query.AllDelegations != nil: + contractAddr, err := sdk.AccAddressFromBech32(query.AllDelegations.Contract) + if err != nil { + return nil, sdkerrors.ErrInvalidAddress.Wrap(query.AllDelegations.Contract) + } + delegations := k.GetAllDelegations(ctx, contractAddr, query.AllDelegations.MaxRetrieve) + + res = contract.AllDelegationsResponse{ + Delegations: contract.ConvertDelegationsToWasm(delegations), + } default: return nil, wasmvmtypes.UnsupportedRequest{Kind: "unknown virtual_stake query variant"} } diff --git a/x/meshsecurity/keeper/query_plugin_test.go b/x/meshsecurity/keeper/query_plugin_test.go index fd439eae..05e9a160 100644 --- a/x/meshsecurity/keeper/query_plugin_test.go +++ b/x/meshsecurity/keeper/query_plugin_test.go @@ -6,6 +6,7 @@ import ( wasmvmtypes "github.com/CosmWasm/wasmvm/types" "github.com/cometbft/cometbft/libs/rand" + "github.com/osmosis-labs/mesh-security-sdk/x/meshsecurity/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -36,6 +37,9 @@ func TestChainedCustomQuerier(t *testing.T) { GetTotalDelegatedFn: func(ctx sdk.Context, actor sdk.AccAddress) sdk.Coin { return sdk.NewCoin("ALX", math.NewInt(456)) }, + GetAllDelegationsFn: func(ctx sdk.Context, actor sdk.AccAddress, maxRetrieve uint16) []types.Delegation { + return []types.Delegation{} + }, }, expData: []byte(`{"cap":{"denom":"ALX","amount":"123"},"delegated":{"denom":"ALX","amount":"456"}}`), }, @@ -87,6 +91,7 @@ var _ viewKeeper = &MockViewKeeper{} type MockViewKeeper struct { GetMaxCapLimitFn func(ctx sdk.Context, actor sdk.AccAddress) sdk.Coin GetTotalDelegatedFn func(ctx sdk.Context, actor sdk.AccAddress) sdk.Coin + GetAllDelegationsFn func(ctx sdk.Context, actor sdk.AccAddress, maxRetrieve uint16) []types.Delegation } func (m MockViewKeeper) GetMaxCapLimit(ctx sdk.Context, actor sdk.AccAddress) sdk.Coin { @@ -102,3 +107,10 @@ func (m MockViewKeeper) GetTotalDelegated(ctx sdk.Context, actor sdk.AccAddress) } return m.GetTotalDelegatedFn(ctx, actor) } + +func (m MockViewKeeper) GetAllDelegations(ctx sdk.Context, actor sdk.AccAddress, maxRetrieve uint16) []types.Delegation { + if m.GetAllDelegationsFn == nil { + panic("not expected to be called") + } + return m.GetAllDelegationsFn(ctx, actor, maxRetrieve) +} diff --git a/x/meshsecurity/keeper/stake.go b/x/meshsecurity/keeper/stake.go index a1fcfb41..d9b1234f 100644 --- a/x/meshsecurity/keeper/stake.go +++ b/x/meshsecurity/keeper/stake.go @@ -60,6 +60,7 @@ func (k Keeper) Delegate(pCtx sdk.Context, actor sdk.AccAddress, valAddr sdk.Val // and update our records k.setTotalDelegated(cacheCtx, actor, newTotalDelegatedAmount) + done() return newShares, err } @@ -115,3 +116,12 @@ func (k Keeper) Undelegate(pCtx sdk.Context, actor sdk.AccAddress, valAddr sdk.V done() return nil } + +func (k Keeper) UpdateDelegation(pCtx sdk.Context, actor, delAddr sdk.AccAddress, valAddr sdk.ValAddress, amt sdk.Coin, isDeduct bool) { + cacheCtx, done := pCtx.CacheContext() // work in a cached store (safety net?) + if isDeduct { + amt.Amount = amt.Amount.Neg() + } + k.setDelegation(cacheCtx, actor, delAddr, valAddr, amt.Amount) + done() +} diff --git a/x/meshsecurity/keeper/valset_updates.go b/x/meshsecurity/keeper/valset_updates.go index 50101d1a..992f3d79 100644 --- a/x/meshsecurity/keeper/valset_updates.go +++ b/x/meshsecurity/keeper/valset_updates.go @@ -10,8 +10,6 @@ import ( "github.com/osmosis-labs/mesh-security-sdk/x/meshsecurity/contract" "github.com/osmosis-labs/mesh-security-sdk/x/meshsecurity/types" - - outmessage "github.com/osmosis-labs/mesh-security-sdk/x/meshsecurity/contract" ) // ScheduleBonded store a validator update to bonded status for the valset update report @@ -77,7 +75,7 @@ func (k Keeper) sendAsync(ctx sdk.Context, op types.PipedValsetOperation, valAdd // ValsetUpdateReport aggregate all stored changes of the current block. Should be called by an end-blocker. // The events reported are categorized by type and not time. Conflicting events as Bonded/ Unbonded // are not supposed to happen within the same block -func (k Keeper) ValsetUpdateReport(ctx sdk.Context) (contract.ValsetUpdate, error) { +func (k Keeper) ValsetUpdateReport(ctx sdk.Context) (contract.HandleValsetUpdate, error) { var innerErr error appendValidator := func(set *[]wasmvmtypes.Validator, valAddr sdk.ValAddress) bool { val, ok := k.Staking.GetValidator(ctx, valAddr) @@ -88,9 +86,9 @@ func (k Keeper) ValsetUpdateReport(ctx sdk.Context) (contract.ValsetUpdate, erro *set = append(*set, ConvertSdkValidatorToWasm(val)) return false } - slashValidator := func(set *[]outmessage.ValidatorSlash, valAddr sdk.ValAddress, power int64, infractionHeight int64, + slashValidator := func(set *[]contract.ValidatorSlash, valAddr sdk.ValAddress, power int64, infractionHeight int64, infractionTime int64, slashAmount string, slashRatio string) bool { - valSlash := outmessage.ValidatorSlash{ + valSlash := contract.ValidatorSlash{ ValidatorAddr: valAddr.String(), Power: power, InfractionHeight: infractionHeight, @@ -103,7 +101,7 @@ func (k Keeper) ValsetUpdateReport(ctx sdk.Context) (contract.ValsetUpdate, erro *set = append(*set, valSlash) return false } - r := contract.ValsetUpdate{ // init with empty slices for contract that does not handle null or omitted fields + r := contract.HandleValsetUpdate{ // init with empty slices for contract that does not handle null or omitted fields Additions: make([]contract.Validator, 0), Removals: make([]contract.ValidatorAddr, 0), Updated: make([]contract.Validator, 0), diff --git a/x/meshsecurity/keeper/valset_updates_test.go b/x/meshsecurity/keeper/valset_updates_test.go index 5d794593..e48daa2b 100644 --- a/x/meshsecurity/keeper/valset_updates_test.go +++ b/x/meshsecurity/keeper/valset_updates_test.go @@ -148,7 +148,7 @@ func TestBuildValsetUpdateReport(t *testing.T) { got, err := k.ValsetUpdateReport(ctx) // then require.NoError(t, err) - exp := contract.ValsetUpdate{ + exp := contract.HandleValsetUpdate{ Additions: []contract.Validator{ { Address: val4.String(), diff --git a/x/meshsecurity/keeper/wasm.go b/x/meshsecurity/keeper/wasm.go index 8ee4c4f6..33fe9d19 100644 --- a/x/meshsecurity/keeper/wasm.go +++ b/x/meshsecurity/keeper/wasm.go @@ -5,6 +5,7 @@ import ( errorsmod "cosmossdk.io/errors" + storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/osmosis-labs/mesh-security-sdk/x/meshsecurity/contract" @@ -15,13 +16,14 @@ func (k Keeper) SendHandleEpoch(ctx sdk.Context, contractAddr sdk.AccAddress) er msg := contract.SudoMsg{ HandleEpoch: &struct{}{}, } + ctx = ctx.WithGasMeter(storetypes.NewInfiniteGasMeter()) return k.doSudoCall(ctx, contractAddr, msg) } // SendValsetUpdate submit the valset update report to the virtual staking contract via sudo -func (k Keeper) SendValsetUpdate(ctx sdk.Context, contractAddr sdk.AccAddress, v contract.ValsetUpdate) error { +func (k Keeper) SendValsetUpdate(ctx sdk.Context, contractAddr sdk.AccAddress, v contract.HandleValsetUpdate) error { msg := contract.SudoMsg{ - ValsetUpdate: &v, + HandleValsetUpdate: &v, } return k.doSudoCall(ctx, contractAddr, msg) } diff --git a/x/meshsecurity/types/events.go b/x/meshsecurity/types/events.go index 3343914e..20b2a370 100644 --- a/x/meshsecurity/types/events.go +++ b/x/meshsecurity/types/events.go @@ -11,6 +11,7 @@ const ( EventTypeSchedulerRegistered = "scheduler_registered" EventTypeMaxCapLimitUpdated = "max_cap_limit_updated" EventTypeUnbond = "instant_unbond" + EventTypeUpdateDelegation = "update_delegation" EventTypeDelegate = "instant_delegate" ) diff --git a/x/meshsecurity/types/keys.go b/x/meshsecurity/types/keys.go index fa21f06c..1534c089 100644 --- a/x/meshsecurity/types/keys.go +++ b/x/meshsecurity/types/keys.go @@ -2,6 +2,7 @@ package types import ( "encoding/binary" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/address" ) @@ -31,6 +32,8 @@ var ( SchedulerKeyPrefix = []byte{0x4} PipedValsetPrefix = []byte{0x5} + + DelegationKey = []byte{0x6} ) type PipedValsetOperation byte @@ -119,3 +122,15 @@ func BuildPipedValsetOpKey(op PipedValsetOperation, val sdk.ValAddress, slashInf } return r } + +// BuildDelegationsKey build the delegations's prefix for a contract +func BuildDelegationsKey(actor sdk.AccAddress) []byte { + return append(DelegationKey, address.MustLengthPrefix(actor)...) +} + +// BuildDelegationKey build the prefix for a delegator bond with validator +func BuildDelegationKey(actor, delAddr sdk.AccAddress, valAddr sdk.ValAddress) []byte { + key := append(BuildDelegationsKey(actor), address.MustLengthPrefix(delAddr)...) + key = append(key, address.MustLengthPrefix(valAddr)...) + return key +} diff --git a/x/meshsecurity/types/meshsecurity.pb.go b/x/meshsecurity/types/meshsecurity.pb.go index 6abc9723..bf28bcdb 100644 --- a/x/meshsecurity/types/meshsecurity.pb.go +++ b/x/meshsecurity/types/meshsecurity.pb.go @@ -5,6 +5,7 @@ package types import ( fmt "fmt" + github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" types "github.com/cosmos/cosmos-sdk/types" _ "github.com/cosmos/cosmos-sdk/types/tx/amino" _ "github.com/cosmos/gogoproto/gogoproto" @@ -69,6 +70,49 @@ func (m *VirtualStakingMaxCapInfo) XXX_DiscardUnknown() { var xxx_messageInfo_VirtualStakingMaxCapInfo proto.InternalMessageInfo +// Delegation represents the bond with tokens held by an account. +type Delegation struct { + // delegator_address is the bech32-encoded address of the delegator. + DelegatorAddress string `protobuf:"bytes,1,opt,name=delegator_address,json=delegatorAddress,proto3" json:"delegator_address,omitempty"` + // validator_address is the bech32-encoded address of the validator. + ValidatorAddress string `protobuf:"bytes,2,opt,name=validator_address,json=validatorAddress,proto3" json:"validator_address,omitempty"` + // amount define the delegation amount. + Amount github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,3,opt,name=amount,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"amount"` +} + +func (m *Delegation) Reset() { *m = Delegation{} } +func (m *Delegation) String() string { return proto.CompactTextString(m) } +func (*Delegation) ProtoMessage() {} +func (*Delegation) Descriptor() ([]byte, []int) { + return fileDescriptor_53771980e3e4256c, []int{1} +} +func (m *Delegation) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Delegation) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Delegation.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Delegation) XXX_Merge(src proto.Message) { + xxx_messageInfo_Delegation.Merge(m, src) +} +func (m *Delegation) XXX_Size() int { + return m.Size() +} +func (m *Delegation) XXX_DiscardUnknown() { + xxx_messageInfo_Delegation.DiscardUnknown(m) +} + +var xxx_messageInfo_Delegation proto.InternalMessageInfo + // Params defines the parameters for the x/meshsecurity module. type Params struct { // TotalContractsMaxCap is the maximum that the sum of all contract max caps @@ -85,7 +129,7 @@ func (m *Params) Reset() { *m = Params{} } func (m *Params) String() string { return proto.CompactTextString(m) } func (*Params) ProtoMessage() {} func (*Params) Descriptor() ([]byte, []int) { - return fileDescriptor_53771980e3e4256c, []int{1} + return fileDescriptor_53771980e3e4256c, []int{2} } func (m *Params) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -116,6 +160,7 @@ var xxx_messageInfo_Params proto.InternalMessageInfo func init() { proto.RegisterType((*VirtualStakingMaxCapInfo)(nil), "osmosis.meshsecurity.v1beta1.VirtualStakingMaxCapInfo") + proto.RegisterType((*Delegation)(nil), "osmosis.meshsecurity.v1beta1.Delegation") proto.RegisterType((*Params)(nil), "osmosis.meshsecurity.v1beta1.Params") } @@ -124,33 +169,39 @@ func init() { } var fileDescriptor_53771980e3e4256c = []byte{ - // 416 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x52, 0x41, 0x8b, 0xd3, 0x40, - 0x14, 0xce, 0xb8, 0xcb, 0xe2, 0xce, 0xba, 0xa0, 0xd9, 0x05, 0x63, 0x59, 0x66, 0xd7, 0x9e, 0x8a, - 0x90, 0x84, 0xea, 0xad, 0xa0, 0x87, 0x16, 0x11, 0x41, 0x41, 0x22, 0xf4, 0xe0, 0x25, 0xbe, 0x4c, - 0xc6, 0x64, 0x68, 0x32, 0x13, 0x32, 0x53, 0x49, 0xff, 0x82, 0x27, 0x7f, 0x82, 0x47, 0x4f, 0xe2, - 0xcf, 0xe8, 0xb1, 0x47, 0x4f, 0xa2, 0xe9, 0x41, 0x7f, 0x86, 0x64, 0xd2, 0x54, 0x72, 0xeb, 0x65, - 0x78, 0xf3, 0xbd, 0xf9, 0xbe, 0xf9, 0x3e, 0xde, 0xc3, 0xbe, 0x54, 0xb9, 0x54, 0x5c, 0xf9, 0x39, - 0x53, 0xa9, 0x62, 0x74, 0x59, 0x72, 0xbd, 0xf2, 0x3f, 0x8e, 0x23, 0xa6, 0x61, 0xdc, 0x03, 0xbd, - 0xa2, 0x94, 0x5a, 0xda, 0x57, 0x3b, 0x82, 0xd7, 0xeb, 0xed, 0x08, 0x03, 0x42, 0x4d, 0xdb, 0x8f, - 0x40, 0xb1, 0xbd, 0x0a, 0x95, 0x5c, 0xb4, 0xec, 0xc1, 0x65, 0x22, 0x13, 0x69, 0x4a, 0xbf, 0xa9, - 0x76, 0xe8, 0x3d, 0xc8, 0xb9, 0x90, 0xbe, 0x39, 0x5b, 0x68, 0xf8, 0x0d, 0x61, 0x67, 0xce, 0x4b, - 0xbd, 0x84, 0xec, 0xad, 0x86, 0x05, 0x17, 0xc9, 0x6b, 0xa8, 0x66, 0x50, 0xbc, 0x14, 0x1f, 0xa4, - 0x3d, 0xc0, 0xb7, 0xa9, 0x14, 0xba, 0x04, 0xaa, 0x1d, 0x74, 0x83, 0x46, 0xa7, 0xc1, 0xfe, 0x6e, - 0x3f, 0xc5, 0xa7, 0x31, 0xcb, 0x58, 0x02, 0x9a, 0xc5, 0xce, 0xad, 0x1b, 0x34, 0x3a, 0x7b, 0xfc, - 0xc0, 0x6b, 0x5d, 0x79, 0x8d, 0xab, 0xce, 0xaa, 0x37, 0x93, 0x5c, 0x4c, 0x8f, 0xd7, 0x3f, 0xaf, - 0xad, 0xe0, 0x3f, 0xc3, 0x1e, 0xe3, 0x23, 0x0a, 0x85, 0x73, 0x74, 0x18, 0xb1, 0x79, 0x3b, 0x39, - 0xfe, 0xfb, 0xe5, 0x1a, 0x0d, 0x37, 0x08, 0x9f, 0xbc, 0x81, 0x12, 0x72, 0x65, 0xcf, 0xf1, 0x7d, - 0x2d, 0x35, 0x64, 0x61, 0x67, 0x4a, 0x85, 0x39, 0x54, 0x61, 0xa3, 0x8b, 0x0e, 0xd3, 0xbd, 0x34, - 0xfc, 0x59, 0x47, 0x6f, 0xa3, 0xdb, 0x0f, 0xf1, 0x1d, 0x56, 0x48, 0x9a, 0x86, 0x19, 0x13, 0x89, - 0x4e, 0x4d, 0xba, 0xf3, 0xe0, 0xcc, 0x60, 0xaf, 0x0c, 0x64, 0xbb, 0xf8, 0xa2, 0xf9, 0x2a, 0x01, - 0x15, 0x32, 0x11, 0x87, 0x51, 0x26, 0xe9, 0x82, 0x95, 0x26, 0xce, 0x79, 0x70, 0x37, 0x87, 0xea, - 0x05, 0xa8, 0xe7, 0x22, 0x9e, 0xb6, 0xf8, 0xe4, 0xaa, 0xb1, 0xfe, 0xe9, 0xcf, 0xf7, 0x47, 0x17, - 0xbd, 0xf1, 0xb7, 0x39, 0xa6, 0xef, 0xd7, 0xbf, 0x89, 0xf5, 0xb5, 0x26, 0xd6, 0xba, 0x26, 0x68, - 0x53, 0x13, 0xf4, 0xab, 0x26, 0xe8, 0xf3, 0x96, 0x58, 0x9b, 0x2d, 0xb1, 0x7e, 0x6c, 0x89, 0xf5, - 0xee, 0x59, 0xc2, 0x75, 0xba, 0x8c, 0x3c, 0x2a, 0xf3, 0x6e, 0x91, 0xdc, 0x0c, 0xa2, 0x76, 0x9b, - 0xdc, 0x4e, 0xcf, 0x55, 0xf1, 0xc2, 0xaf, 0xfa, 0x1b, 0xa6, 0x57, 0x05, 0x53, 0xd1, 0x89, 0x19, - 0xf6, 0x93, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x98, 0xcb, 0x62, 0xae, 0x86, 0x02, 0x00, 0x00, + // 501 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x53, 0x4d, 0x6f, 0xd3, 0x40, + 0x10, 0xf5, 0xb6, 0x55, 0x45, 0xb6, 0x54, 0x6a, 0xdd, 0x4a, 0x84, 0xa8, 0x72, 0x4a, 0x0e, 0xa8, + 0x02, 0xc5, 0x56, 0xe0, 0x56, 0x09, 0x24, 0x12, 0x3e, 0x54, 0x09, 0x24, 0x64, 0xa4, 0x1e, 0xb8, + 0x98, 0xb1, 0xbd, 0x38, 0xab, 0xd8, 0x3b, 0x96, 0x77, 0x53, 0xa5, 0x7f, 0x81, 0x13, 0x3f, 0x81, + 0x23, 0x27, 0xc4, 0x81, 0x1f, 0x91, 0x63, 0x8e, 0x88, 0x43, 0x05, 0xc9, 0x01, 0x7e, 0x06, 0xf2, + 0xae, 0x1d, 0xf0, 0xad, 0x17, 0x7b, 0xfd, 0xe6, 0xbd, 0xf1, 0xdb, 0xb7, 0xb3, 0xd4, 0x43, 0x99, + 0xa1, 0xe4, 0xd2, 0xcb, 0x98, 0x1c, 0x4b, 0x16, 0x4d, 0x0b, 0xae, 0x2e, 0xbd, 0x8b, 0x41, 0xc8, + 0x14, 0x0c, 0x1a, 0xa0, 0x9b, 0x17, 0xa8, 0xd0, 0x3e, 0xaa, 0x04, 0x6e, 0xa3, 0x56, 0x09, 0x3a, + 0x4e, 0xa4, 0xcb, 0x5e, 0x08, 0x92, 0xad, 0xbb, 0x44, 0xc8, 0x85, 0x51, 0x77, 0x0e, 0x13, 0x4c, + 0x50, 0x2f, 0xbd, 0x72, 0x55, 0xa1, 0xfb, 0x90, 0x71, 0x81, 0x9e, 0x7e, 0x1a, 0xa8, 0xf7, 0x85, + 0xd0, 0xf6, 0x39, 0x2f, 0xd4, 0x14, 0xd2, 0x37, 0x0a, 0x26, 0x5c, 0x24, 0xaf, 0x60, 0x36, 0x82, + 0xfc, 0x4c, 0xbc, 0x47, 0xbb, 0x43, 0x6f, 0x44, 0x28, 0x54, 0x01, 0x91, 0x6a, 0x93, 0x63, 0x72, + 0xd2, 0xf2, 0xd7, 0xdf, 0xf6, 0x23, 0xda, 0x8a, 0x59, 0xca, 0x12, 0x50, 0x2c, 0x6e, 0x6f, 0x1c, + 0x93, 0x93, 0x9d, 0x07, 0xb7, 0x5d, 0xe3, 0xca, 0x2d, 0x5d, 0xd5, 0x56, 0xdd, 0x11, 0x72, 0x31, + 0xdc, 0x9a, 0x5f, 0x75, 0x2d, 0xff, 0x9f, 0xc2, 0x1e, 0xd0, 0xcd, 0x08, 0xf2, 0xf6, 0xe6, 0xf5, + 0x84, 0x25, 0xf7, 0x74, 0xeb, 0xcf, 0xa7, 0x2e, 0xe9, 0x7d, 0x23, 0x94, 0x3e, 0x35, 0x6d, 0x38, + 0x0a, 0xfb, 0x3e, 0xdd, 0xaf, 0x9a, 0x62, 0x11, 0x40, 0x1c, 0x17, 0x4c, 0xca, 0xca, 0xeb, 0xde, + 0xba, 0xf0, 0xc4, 0xe0, 0x25, 0xf9, 0x02, 0x52, 0x1e, 0x37, 0xc8, 0x1b, 0x86, 0xbc, 0x2e, 0xd4, + 0xe4, 0xe7, 0x74, 0x1b, 0x32, 0x9c, 0x0a, 0xa5, 0x4d, 0xb6, 0x86, 0x6e, 0xe9, 0xe4, 0xc7, 0x55, + 0xf7, 0x6e, 0xc2, 0xd5, 0x78, 0x1a, 0xba, 0x11, 0x66, 0x5e, 0x75, 0x0a, 0xe6, 0xd5, 0x97, 0xf1, + 0xc4, 0x53, 0x97, 0x39, 0x93, 0xee, 0x99, 0x50, 0x7e, 0xa5, 0xd6, 0xb6, 0xad, 0xde, 0x82, 0xd0, + 0xed, 0xd7, 0x50, 0x40, 0x26, 0xed, 0x73, 0x7a, 0x4b, 0xa1, 0x82, 0x34, 0xa8, 0xb3, 0x94, 0x41, + 0x06, 0xb3, 0xa0, 0x8c, 0x83, 0x5c, 0x2f, 0x8e, 0x43, 0xad, 0x1f, 0xd5, 0x72, 0x73, 0x62, 0xf6, + 0x1d, 0x7a, 0x93, 0xe5, 0x18, 0x8d, 0x83, 0x94, 0x89, 0x44, 0x8d, 0xf5, 0xc6, 0x76, 0xfd, 0x1d, + 0x8d, 0xbd, 0xd4, 0x90, 0xdd, 0xa7, 0x07, 0xe5, 0xaf, 0x12, 0x90, 0x01, 0x13, 0x71, 0x10, 0xa6, + 0x18, 0x4d, 0x58, 0xa1, 0x37, 0xb8, 0xeb, 0xef, 0x65, 0x30, 0x7b, 0x01, 0xf2, 0x99, 0x88, 0x87, + 0x06, 0x3f, 0x3d, 0x2a, 0x13, 0xff, 0xf0, 0xfb, 0xeb, 0xbd, 0x83, 0xc6, 0xd4, 0x9a, 0x7d, 0x0c, + 0xdf, 0xcd, 0x7f, 0x39, 0xd6, 0xe7, 0xa5, 0x63, 0xcd, 0x97, 0x0e, 0x59, 0x2c, 0x1d, 0xf2, 0x73, + 0xe9, 0x90, 0x8f, 0x2b, 0xc7, 0x5a, 0xac, 0x1c, 0xeb, 0xfb, 0xca, 0xb1, 0xde, 0x3e, 0xfe, 0x2f, + 0xaa, 0x6a, 0x9c, 0xfb, 0x29, 0x84, 0xe6, 0x12, 0xf4, 0xeb, 0x7e, 0x3a, 0xb7, 0x59, 0xf3, 0x62, + 0xe8, 0x18, 0xc3, 0x6d, 0x3d, 0xa3, 0x0f, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, 0xea, 0xe6, 0x72, + 0xce, 0x3d, 0x03, 0x00, 0x00, } func (this *VirtualStakingMaxCapInfo) Equal(that interface{}) bool { @@ -263,6 +314,53 @@ func (m *VirtualStakingMaxCapInfo) MarshalToSizedBuffer(dAtA []byte) (int, error return len(dAtA) - i, nil } +func (m *Delegation) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Delegation) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Delegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size := m.Amount.Size() + i -= size + if _, err := m.Amount.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintMeshsecurity(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.ValidatorAddress) > 0 { + i -= len(m.ValidatorAddress) + copy(dAtA[i:], m.ValidatorAddress) + i = encodeVarintMeshsecurity(dAtA, i, uint64(len(m.ValidatorAddress))) + i-- + dAtA[i] = 0x12 + } + if len(m.DelegatorAddress) > 0 { + i -= len(m.DelegatorAddress) + copy(dAtA[i:], m.DelegatorAddress) + i = encodeVarintMeshsecurity(dAtA, i, uint64(len(m.DelegatorAddress))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *Params) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -334,6 +432,25 @@ func (m *VirtualStakingMaxCapInfo) Size() (n int) { return n } +func (m *Delegation) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.DelegatorAddress) + if l > 0 { + n += 1 + l + sovMeshsecurity(uint64(l)) + } + l = len(m.ValidatorAddress) + if l > 0 { + n += 1 + l + sovMeshsecurity(uint64(l)) + } + l = m.Amount.Size() + n += 1 + l + sovMeshsecurity(uint64(l)) + return n +} + func (m *Params) Size() (n int) { if m == nil { return 0 @@ -505,6 +622,154 @@ func (m *VirtualStakingMaxCapInfo) Unmarshal(dAtA []byte) error { } return nil } +func (m *Delegation) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMeshsecurity + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Delegation: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Delegation: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DelegatorAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMeshsecurity + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthMeshsecurity + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthMeshsecurity + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DelegatorAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValidatorAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMeshsecurity + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthMeshsecurity + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthMeshsecurity + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ValidatorAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Amount", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMeshsecurity + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthMeshsecurity + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthMeshsecurity + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Amount.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipMeshsecurity(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthMeshsecurity + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *Params) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0