Skip to content

Commit

Permalink
Merge pull request #7 from osmosis-labs/trinity/native-immediate-unbo…
Browse files Browse the repository at this point in the history
…nding

chores: merge and complete logic
  • Loading branch information
trinitys7 authored Sep 5, 2024
2 parents 2fda1e9 + 94b5d7f commit cf3c8d1
Show file tree
Hide file tree
Showing 18 changed files with 151 additions and 44 deletions.
2 changes: 1 addition & 1 deletion demo/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ func NewMeshApp(
keys[meshsecprovtypes.StoreKey],
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
app.BankKeeper,
app.WasmKeeper,
&app.WasmKeeper,
app.StakingKeeper,
)

Expand Down
3 changes: 1 addition & 2 deletions tests/e2e/e2e.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"path/filepath"
"testing"

"github.com/CosmWasm/wasmd/x/wasm"
"github.com/CosmWasm/wasmd/x/wasm/ibctesting"
wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types"
Expand Down Expand Up @@ -38,7 +37,7 @@ func buildPathToWasm(fileName string) string {
// NewIBCCoordinator initializes Coordinator with N meshd TestChain instances
func NewIBCCoordinator(t *testing.T, n int, opts ...[]wasmkeeper.Option) *ibctesting.Coordinator {
return ibctesting.NewCoordinatorX(t, n,
func(t *testing.T, valSet *types.ValidatorSet, genAccs []authtypes.GenesisAccount, chainID string, opts []wasm.Option, balances ...banktypes.Balance) ibctesting.ChainApp {
func(t *testing.T, valSet *types.ValidatorSet, genAccs []authtypes.GenesisAccount, chainID string, opts []wasmkeeper.Option, balances ...banktypes.Balance) ibctesting.ChainApp {
return app.SetupWithGenesisValSet(t, valSet, genAccs, chainID, opts, balances...)
},
opts...,
Expand Down
63 changes: 63 additions & 0 deletions tests/e2e/slashing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,3 +284,66 @@ func TestSlashingScenario3(t *testing.T) {
// Check new free collateral
require.Equal(t, 0, providerCli.QueryVaultFreeBalance()) // 185 - max(32, 185) = 185 - 185 = 0
}

func TestSlasingImmediateUnbond(t *testing.T) {
x := setupExampleChains(t)
_, _, providerCli := setupMeshSecurity(t, x)

// Provider chain
// ==============
// Deposit - A user deposits the vault denom to provide some collateral to their account
execMsg := fmt.Sprintf(`{"bond":{"amount":{"denom":"%s", "amount":"200000000"}}}`, x.ProviderDenom)
providerCli.MustExecVault(execMsg)

// Stake Locally - A user triggers a local staking action to a chosen validator.
myLocalValidatorAddr := sdk.ValAddress(x.ProviderChain.Vals.Validators[0].Address).String()
execLocalStakingMsg := fmt.Sprintf(`{"stake_local":{"amount": {"denom":%q, "amount":"%d"}, "msg":%q}}`,
x.ProviderDenom, 100_000_000,
base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"validator": "%s"}`, myLocalValidatorAddr))))
providerCli.MustExecVault(execLocalStakingMsg)

assert.Equal(t, 100_000_000, providerCli.QueryVaultFreeBalance())

// Check slashable amount
require.Equal(t, 20_000_000, providerCli.QuerySlashableAmount())
// Check free collateral
require.Equal(t, 100_000_000, providerCli.QueryVaultFreeBalance())

// Validator on the provider chain is jailed
myLocalValidatorConsAddr := sdk.ConsAddress(x.ProviderChain.Vals.Validators[0].PubKey.Address())
jailValidator(t, myLocalValidatorConsAddr, x.Coordinator, x.ProviderChain, x.ProviderApp)

x.ProviderChain.NextBlock()

// Check new collateral
require.Equal(t, 200_000_000, providerCli.QueryVaultBalance())
// Check new max lien
require.Equal(t, 100_000_000, providerCli.QueryMaxLien())
// Check new slashable amount
require.Equal(t, 20_000_000, providerCli.QuerySlashableAmount())
// Check new free collateral
require.Equal(t, 100_000_000, providerCli.QueryVaultFreeBalance())

// Get native staking proxy contract
nativeStakingProxy := providerCli.QueryNativeStakingProxyByOwner(x.ProviderChain.SenderAccount.GetAddress().String())

execMsg = fmt.Sprintf(`{"unstake": {"validator":%q,"amount": {"denom":%q, "amount":"%d"}}}`,
myLocalValidatorAddr, x.ProviderDenom, 10_000_000)
_, err := providerCli.Exec(nativeStakingProxy, execMsg)
require.NoError(t, err)

x.ProviderChain.NextBlock()

_, err = providerCli.Exec(nativeStakingProxy, `{"release_unbonded": {}}`)
require.NoError(t, err)

// Check new collateral
require.Equal(t, 200_000_000, providerCli.QueryVaultBalance())
// Check new max lien
// Max lien decrease as release_unbonded
require.Equal(t, 90_000_001, providerCli.QueryMaxLien())
// Check new slashable amount
require.Equal(t, 18000001, providerCli.QuerySlashableAmount())
// Check new free collateral
require.Equal(t, 109999999, providerCli.QueryVaultFreeBalance())
}
45 changes: 45 additions & 0 deletions tests/e2e/test_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"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"
providertypes "github.com/osmosis-labs/mesh-security-sdk/x/meshsecurityprovider/types"
)

// Query is a query type used in tests only
Expand Down Expand Up @@ -129,6 +130,7 @@ func (tc *TestChain) SendMsgsWithSigner(privKey cryptotypes.PrivKey, signer *aut

type ProviderContracts struct {
Vault sdk.AccAddress
NativeStaking sdk.AccAddress
ExternalStaking sdk.AccAddress
}

Expand All @@ -153,6 +155,7 @@ func (p *TestProviderClient) BootstrapContracts(provApp *app.MeshApp, connId, po
params := provApp.MeshSecProvKeeper.GetParams(ctx)
params.VaultAddress = vaultContract.String()
provApp.MeshSecProvKeeper.SetParams(ctx, params)

// external staking
extStakingCodeID := p.chain.StoreCodeFile(buildPathToWasm("mesh_external_staking.wasm")).CodeID
initMsg = []byte(fmt.Sprintf(
Expand All @@ -165,6 +168,19 @@ func (p *TestProviderClient) BootstrapContracts(provApp *app.MeshApp, connId, po
ExternalStaking: externalStakingContract,
}
p.Contracts = r

// local staking
vaultConfig := p.QueryVault(Query{
"config": {},
})
require.Contains(p.t, vaultConfig, "local_staking")
nativeStaking, err := sdk.AccAddressFromBech32(vaultConfig["local_staking"].(string))
require.NoError(p.t, err)
r.NativeStaking = nativeStaking
p.Contracts = r

p.MustExecParamsChangeProposal(provApp, vaultContract.String(), nativeStaking.String())

return r
}

Expand Down Expand Up @@ -240,6 +256,19 @@ func (p TestProviderClient) ExecWithSigner(privKey cryptotypes.PrivKey, signer *
return rsp, err
}

// MustExecGovProposal submit and vote yes on proposal
func (p TestProviderClient) MustExecParamsChangeProposal(provApp *app.MeshApp, vault, nativeStaking string) {
msg := &providertypes.MsgUpdateParams{
Authority: provApp.MeshSecKeeper.GetAuthority(),
Params: providertypes.Params{
VaultAddress: vault,
NativeStakingAddress: nativeStaking,
},
}
proposalID := submitGovProposal(p.t, p.chain, msg)
voteAndPassGovProposal(p.t, p.chain, proposalID)
}

func (p TestProviderClient) MustFailExecVault(payload string, funds ...sdk.Coin) error {
rsp, err := p.Exec(p.Contracts.Vault, payload, funds...)
require.Error(p.t, err, "Response: %v", rsp)
Expand Down Expand Up @@ -270,6 +299,18 @@ func (p TestProviderClient) QueryExtStakingAmount(user, validator string) int {
return ParseHighLow(p.t, qRsp["stake"]).Low
}

func (p TestProviderClient) QueryNativeStakingProxyByOwner(user string) sdk.AccAddress {
qRsp := p.QueryNativeStaking(Query{
"proxy_by_owner": {
"owner": user,
},
})
require.Contains(p.t, qRsp, "proxy")
a, err := sdk.AccAddressFromBech32(qRsp["proxy"].(string))
require.NoError(p.t, err)

return a
}
func (p TestProviderClient) QueryExtStaking(q Query) QueryResponse {
return Querier(p.t, p.chain)(p.Contracts.ExternalStaking.String(), q)
}
Expand All @@ -278,6 +319,10 @@ func (p TestProviderClient) QueryVault(q Query) QueryResponse {
return Querier(p.t, p.chain)(p.Contracts.Vault.String(), q)
}

func (p TestProviderClient) QueryNativeStaking(q Query) QueryResponse {
return Querier(p.t, p.chain)(p.Contracts.NativeStaking.String(), q)
}

type HighLowType struct {
High, Low int
}
Expand Down
2 changes: 1 addition & 1 deletion tests/starship/setup/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ func (p *ProviderClient) BootstrapContracts(connId, portID, rewardDenom string)

nativeInitMsg := []byte(fmt.Sprintf(`{"denom": %q, "proxy_code_id": %d, "slash_ratio_dsign": %q, "slash_ratio_offline": %q }`, localTokenDenom, proxyCodeID, localSlashRatioDoubleSign, localSlashRatioOffline))
initMsg := []byte(fmt.Sprintf(`{"denom": %q, "local_staking": {"code_id": %d, "msg": %q}}`, localTokenDenom, nativeStakingCodeID, base64.StdEncoding.EncodeToString(nativeInitMsg)))
contracts, err := InstantiateContract(p.Chain, vaultCodeID, "provider-valut-contract", initMsg)
contracts, err := InstantiateContract(p.Chain, vaultCodeID, "provider-vault-contract", initMsg)
if err != nil {
return nil, err
}
Expand Down
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_provider.wasm.gz
Binary file not shown.
Binary file modified tests/testdata/mesh_remote_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.
7 changes: 4 additions & 3 deletions x/meshsecurityprovider/contract/in_message.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ type (
Provider *ProviderMsg `json:"provider,omitempty"`
}
ProviderMsg struct {
Bond *BondMsg `json:"bond,omitempty"`
Unbond *UnbondMsg `json:"unbond,omitempty"`
Unstake *UnstakeMsg `json:"unstake,,omitempty"`
Bond *BondMsg `json:"bond,omitempty"`
Unbond *UnbondMsg `json:"unbond,omitempty"`
Unstake *UnstakeMsg `json:"unstake,omitempty"`
}
BondMsg struct {
Amount wasmvmtypes.Coin `json:"amount"`
Expand All @@ -22,5 +22,6 @@ type (
UnstakeMsg struct {
Amount wasmvmtypes.Coin `json:"amount"`
Validator string `json:"validator"`
Delegator string `json:"delegator"`
}
)
63 changes: 33 additions & 30 deletions x/meshsecurityprovider/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,13 +153,19 @@ func (k Keeper) HandleUnbondMsg(ctx sdk.Context, actor sdk.AccAddress, unbondMsg
}

func (k Keeper) HandleUnstakeMsg(ctx sdk.Context, actor sdk.AccAddress, unstakeMsg *contract.UnstakeMsg) ([]sdk.Event, [][]byte, error) {
nativeContractAddr := k.NativeStakingAddress(ctx)
nativeContract := k.NativeStakingAddress(ctx)
nativeContractAddr, err := sdk.AccAddressFromBech32(nativeContract)
if err != nil {
return nil, nil, sdkerrors.ErrInvalidAddress.Wrapf("native staking contract not able to get")
}
var proxyRes types.ProxyByOwnerResponse

resBytes, err := k.wasmKeeper.QuerySmart(ctx,
sdk.AccAddress(nativeContractAddr),
[]byte(fmt.Sprintf(`{"proxy_by_owner": {"owner": "%s"}}`, actor.String())),
resBytes, err := k.wasmKeeper.QuerySmart(
ctx,
nativeContractAddr,
[]byte(fmt.Sprintf(`{"proxy_by_owner": {"owner": "%s"}}`, unstakeMsg.Delegator)),
)

if err != nil {
return nil, nil, sdkerrors.ErrUnauthorized.Wrapf("contract has no permission for mesh security operations")
}
Expand All @@ -170,6 +176,11 @@ func (k Keeper) HandleUnstakeMsg(ctx sdk.Context, actor sdk.AccAddress, unstakeM
return nil, nil, sdkerrors.ErrUnauthorized.Wrapf("contract has no permission for mesh security operations")
}

proxyContract, err := sdk.AccAddressFromBech32(proxyRes.Proxy)
if err != nil {
return nil, nil, sdkerrors.ErrInvalidAddress.Wrapf("native staking proxy contract not able to get")
}

coin, err := wasmkeeper.ConvertWasmCoinToSdkCoin(unstakeMsg.Amount)
if err != nil {
return nil, nil, err
Expand All @@ -180,7 +191,7 @@ func (k Keeper) HandleUnstakeMsg(ctx sdk.Context, actor sdk.AccAddress, unstakeM
return nil, nil, err
}

err = k.unstake(ctx, actor, valAddr, coin)
err = k.unstake(ctx, proxyContract, valAddr, coin)
if err != nil {
return nil, nil, err
}
Expand All @@ -193,7 +204,7 @@ func (k Keeper) HandleUnstakeMsg(ctx sdk.Context, actor sdk.AccAddress, unstakeM
)}, nil, nil
}

func (k Keeper) unstake(ctx sdk.Context, actor sdk.AccAddress, validator sdk.ValAddress, coin sdk.Coin) error {
func (k Keeper) unstake(ctx sdk.Context, proxyContract sdk.AccAddress, validator sdk.ValAddress, coin sdk.Coin) error {
if coin.Amount.IsNil() || coin.Amount.IsZero() || coin.Amount.IsNegative() {
return sdkerrors.ErrInvalidRequest.Wrap("amount")
}
Expand All @@ -204,48 +215,40 @@ func (k Keeper) unstake(ctx sdk.Context, actor sdk.AccAddress, validator sdk.Val
return sdkerrors.ErrInvalidRequest.Wrapf("invalid coin denomination: got %s, expected %s", coin.Denom, bondDenom)
}

shares, err := k.stakingKeeper.ValidateUnbondAmount(ctx, actor, validator, coin.Amount)
validatorInfo, found := k.stakingKeeper.GetValidator(ctx, validator)
if !found {
return sdkerrors.ErrNotFound.Wrapf("can not found validator with address: %s", validator.String())
}

shares, err := k.stakingKeeper.ValidateUnbondAmount(ctx, proxyContract, validator, coin.Amount)
if err == stakingtypes.ErrNoDelegation {
return nil
} else if err != nil {
return err
}

validatorInfo, found := k.stakingKeeper.GetValidator(ctx, validator)
if !found {
return sdkerrors.ErrNotFound.Wrapf("can not found validator with address: %s", validator.String())
}
if validatorInfo.IsBonded() {
_, err = k.stakingKeeper.Undelegate(ctx, actor, validator, shares)
} else {
_, err = k.InstantUndelegate(ctx, actor, validatorInfo, shares)
}
_, err = k.stakingKeeper.Undelegate(ctx, proxyContract, validator, shares)
if err != nil {
return err
}

if err != nil {
return err
return nil
}

return nil
return k.InstantUndelegate(ctx, proxyContract, validator, shares)
}

func (k Keeper) InstantUndelegate(ctx sdk.Context, delAddr sdk.AccAddress, validator stakingtypes.Validator, sharesAmount sdk.Dec) (sdk.Coin, error) {
returnAmount, err := k.stakingKeeper.Unbond(ctx, delAddr, sdk.ValAddress(validator.OperatorAddress), sharesAmount)
func (k Keeper) InstantUndelegate(ctx sdk.Context, delAddr sdk.AccAddress, validator sdk.ValAddress, sharesAmount sdk.Dec) error {
returnAmount, err := k.stakingKeeper.Unbond(ctx, delAddr, validator, sharesAmount)
if err != nil {
return sdk.Coin{}, err
return err
}

bondDenom := k.stakingKeeper.BondDenom(ctx)

amt := sdk.NewCoin(bondDenom, returnAmount)
res := sdk.NewCoins(amt)

moduleName := stakingtypes.NotBondedPoolName
if validator.IsBonded() {
moduleName = stakingtypes.BondedPoolName
}
err = k.bankKeeper.UndelegateCoinsFromModuleToAccount(ctx, moduleName, delAddr, res)
if err != nil {
return sdk.Coin{}, err
}
return amt, nil
return k.bankKeeper.UndelegateCoinsFromModuleToAccount(ctx, stakingtypes.NotBondedPoolName, delAddr, res)
}
2 changes: 1 addition & 1 deletion x/meshsecurityprovider/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ type AppModule struct {
}

func (am AppModule) RegisterServices(cfg module.Configurator) {
// types.RegisterMsgServer(cfg.MsgServer(), meshsecurityprovider.NewMsgServerImpl(&am.k))
types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.k))
// queryproto.RegisterQueryServer(cfg.QueryServer(), grpc.Querier{Q: module.NewQuerier(am.k)})
}

Expand Down
8 changes: 2 additions & 6 deletions x/meshsecurityprovider/types/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package types
import (
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/codec/types"
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/msgservice"
authzcodec "github.com/cosmos/cosmos-sdk/x/authz/codec"
)

// RegisterLegacyAminoCodec registers the necessary x/meshsecurityprovider interfaces and concrete types
Expand All @@ -30,10 +30,6 @@ var (

func init() {
RegisterLegacyAminoCodec(amino)
// Register all Amino interfaces and concrete types on the authz Amino codec so that this can later be
// used to properly serialize MsgGrant and MsgExec instances
cryptocodec.RegisterCrypto(amino)
sdk.RegisterLegacyAminoCodec(amino)
RegisterLegacyAminoCodec(authzcodec.Amino)

amino.Seal()
}

0 comments on commit cf3c8d1

Please sign in to comment.