From 3fc4106ec71303b8387dc971f6769a240b47db43 Mon Sep 17 00:00:00 2001 From: Trinity Date: Mon, 19 Aug 2024 16:59:27 +0700 Subject: [PATCH 1/2] Add e2e test for stake vesting token --- tests/e2e/e2e.go | 18 +-- tests/e2e/stake_vesting_test.go | 61 ++++++++++ tests/e2e/test_client.go | 150 ++++++++++++++++++++---- tests/e2e/valset_test.go | 8 +- x/meshsecurityprovider/keeper/keeper.go | 4 + 5 files changed, 208 insertions(+), 33 deletions(-) create mode 100644 tests/e2e/stake_vesting_test.go diff --git a/tests/e2e/e2e.go b/tests/e2e/e2e.go index cab70123..a8d24ff9 100644 --- a/tests/e2e/e2e.go +++ b/tests/e2e/e2e.go @@ -45,7 +45,7 @@ func NewIBCCoordinator(t *testing.T, n int, opts ...[]wasmkeeper.Option) *ibctes ) } -func submitGovProposal(t *testing.T, chain *ibctesting.TestChain, msgs ...sdk.Msg) uint64 { +func submitGovProposal(t *testing.T, chain *TestChain, msgs ...sdk.Msg) uint64 { chainApp := chain.App.(*app.MeshApp) govParams := chainApp.GovKeeper.GetParams(chain.GetContext()) govMsg, err := govv1.NewMsgSubmitProposal(msgs, govParams.MinDeposit, chain.SenderAccount.GetAddress().String(), "", "my title", "my summary") @@ -57,7 +57,7 @@ func submitGovProposal(t *testing.T, chain *ibctesting.TestChain, msgs ...sdk.Ms return id } -func voteAndPassGovProposal(t *testing.T, chain *ibctesting.TestChain, proposalID uint64) { +func voteAndPassGovProposal(t *testing.T, chain *TestChain, proposalID uint64) { vote := govv1.NewMsgVote(chain.SenderAccount.GetAddress(), proposalID, govv1.OptionYes, "testing") _, err := chain.SendMsgs(vote) require.NoError(t, err) @@ -67,14 +67,14 @@ func voteAndPassGovProposal(t *testing.T, chain *ibctesting.TestChain, proposalI coord := chain.Coordinator coord.IncrementTimeBy(*govParams.VotingPeriod) - coord.CommitBlock(chain) + coord.CommitBlock(chain.IBCTestChain()) rsp, err := chainApp.GovKeeper.Proposal(sdk.WrapSDKContext(chain.GetContext()), &govv1.QueryProposalRequest{ProposalId: proposalID}) require.NoError(t, err) require.Equal(t, rsp.Proposal.Status, govv1.ProposalStatus_PROPOSAL_STATUS_PASSED) } -func InstantiateContract(t *testing.T, chain *ibctesting.TestChain, codeID uint64, initMsg []byte, funds ...sdk.Coin) sdk.AccAddress { +func InstantiateContract(t *testing.T, chain *TestChain, codeID uint64, initMsg []byte, funds ...sdk.Coin) sdk.AccAddress { instantiateMsg := &wasmtypes.MsgInstantiateContract{ Sender: chain.SenderAccount.GetAddress().String(), Admin: chain.SenderAccount.GetAddress().String(), @@ -96,8 +96,8 @@ func InstantiateContract(t *testing.T, chain *ibctesting.TestChain, codeID uint6 type example struct { Coordinator *ibctesting.Coordinator - ConsumerChain *ibctesting.TestChain - ProviderChain *ibctesting.TestChain + ConsumerChain *TestChain + ProviderChain *TestChain ConsumerApp *app.MeshApp ProviderApp *app.MeshApp IbcPath *ibctesting.Path @@ -112,8 +112,8 @@ func setupExampleChains(t *testing.T) example { consChain := coord.GetChain(ibctesting2.GetChainID(2)) return example{ Coordinator: coord, - ConsumerChain: consChain, - ProviderChain: provChain, + ConsumerChain: NewTestChain(t, consChain), + ProviderChain: NewTestChain(t, provChain), ConsumerApp: consChain.App.(*app.MeshApp), ProviderApp: provChain.App.(*app.MeshApp), IbcPath: ibctesting.NewPath(consChain, provChain), @@ -138,7 +138,7 @@ func setupMeshSecurity(t *testing.T, x example) (*TestConsumerClient, ConsumerCo // setup ibc control path: consumer -> provider (direction matters) x.IbcPath.EndpointB.ChannelConfig = &ibctesting2.ChannelConfig{ - PortID: wasmkeeper.PortIDForContract(providerContracts.externalStaking), + PortID: wasmkeeper.PortIDForContract(providerContracts.ExternalStaking), Order: types2.UNORDERED, } x.IbcPath.EndpointA.ChannelConfig = &ibctesting2.ChannelConfig{ diff --git a/tests/e2e/stake_vesting_test.go b/tests/e2e/stake_vesting_test.go new file mode 100644 index 00000000..217865f2 --- /dev/null +++ b/tests/e2e/stake_vesting_test.go @@ -0,0 +1,61 @@ +package e2e + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +func TestStakeVestingTokenScenario1(t *testing.T) { + // Slashing scenario: + // Permanent lock vesting account should be able to bond in vault contract + // Unbond the amount, balance should keep being locked + x := setupExampleChains(t) + _, _, providerCli := setupMeshSecurity(t, x) + + // Generate vesting address + vestingPrivKey := secp256k1.GenPrivKey() + vestingBacc := authtypes.NewBaseAccount(vestingPrivKey.PubKey().Address().Bytes(), vestingPrivKey.PubKey(), uint64(19), 0) + + vestingBalance := sdk.NewCoin(x.ProviderDenom, sdk.NewInt(100000000)) + providerCli.MustCreatePermanentLockedAccount(vestingBacc.GetAddress().String(), vestingBalance) + + balance := x.ProviderChain.Balance(vestingBacc.GetAddress(), x.ProviderDenom) + assert.Equal(t, balance, vestingBalance) + + // Exec contract by vesting account + execMsg := fmt.Sprintf(`{"bond":{"amount":{"denom":"%s", "amount":"100000000"}}}`, x.ProviderDenom) + providerCli.MustExecVaultWithSigner(vestingPrivKey, vestingBacc, execMsg) + + balance = x.ProviderChain.Balance(providerCli.Contracts.Vault, x.ProviderDenom) + assert.Equal(t, balance, vestingBalance) + + assert.Equal(t, 100_000_000, providerCli.QuerySpecificAddressVaultBalance(vestingBacc.GetAddress().String())) + + // Try to exec msg bond again, should fail + err := providerCli.ExecVaultWithSigner(vestingPrivKey, vestingBacc, execMsg) + assert.Error(t, err) + assert.Contains(t, err.Error(), "dispatch: submessages: failed to delegate") + + // Make sure vault balance doesn't change + assert.Equal(t, 100_000_000, providerCli.QuerySpecificAddressVaultBalance(vestingBacc.GetAddress().String())) + + // Unbond vault + execMsg = fmt.Sprintf(`{"unbond":{"amount":{"denom":"%s", "amount": "30000000"}}}`, x.ProviderDenom) + providerCli.MustExecVaultWithSigner(vestingPrivKey, vestingBacc, execMsg) + + vestingBalance = sdk.NewCoin(x.ProviderDenom, sdk.NewInt(30000000)) + + balance = x.ProviderChain.Balance(vestingBacc.GetAddress(), x.ProviderDenom) + assert.Equal(t, balance, vestingBalance) + + // Vesting account is still locked + err = providerCli.BankSendWithSigner(vestingPrivKey, vestingBacc, x.ProviderChain.SenderAccount.GetAddress().String(), vestingBalance) + assert.Error(t, err) + assert.Contains(t, err.Error(), "insufficient funds") +} diff --git a/tests/e2e/test_client.go b/tests/e2e/test_client.go index a67917de..56045e91 100644 --- a/tests/e2e/test_client.go +++ b/tests/e2e/test_client.go @@ -15,7 +15,11 @@ import ( "cosmossdk.io/math" "github.com/cosmos/cosmos-sdk/baseapp" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" + 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" "github.com/osmosis-labs/mesh-security-sdk/demo/app" "github.com/osmosis-labs/mesh-security-sdk/x/meshsecurity" @@ -54,7 +58,7 @@ func (q QueryResponse) Array(key string) []QueryResponse { return result } -func Querier(t *testing.T, chain *ibctesting.TestChain) func(contract string, query Query) QueryResponse { +func Querier(t *testing.T, chain *TestChain) func(contract string, query Query) QueryResponse { return func(contract string, query Query) QueryResponse { qRsp := make(map[string]any) err := chain.SmartQuery(contract, query, &qRsp) @@ -65,20 +69,68 @@ func Querier(t *testing.T, chain *ibctesting.TestChain) func(contract string, qu type TestProviderClient struct { t *testing.T - chain *ibctesting.TestChain - contracts ProviderContracts + chain *TestChain + Contracts ProviderContracts } -func NewProviderClient(t *testing.T, chain *ibctesting.TestChain) *TestProviderClient { +type TestChain struct { + *ibctesting.TestChain + t *testing.T +} + +func NewTestChain(t *testing.T, chain *ibctesting.TestChain) *TestChain { + return &TestChain{ + t: t, + TestChain: chain, + } +} + +func (tc *TestChain) IBCTestChain() *ibctesting.TestChain { + return tc.TestChain +} + +func NewProviderClient(t *testing.T, chain *TestChain) *TestProviderClient { return &TestProviderClient{t: t, chain: chain} } +func (tc *TestChain) SendMsgsWithSigner(privKey cryptotypes.PrivKey, signer *authtypes.BaseAccount, msgs ...sdk.Msg) (*sdk.Result, error) { + // ensure the chain has the latest time + tc.Coordinator.UpdateTimeForChain(tc.TestChain) + _, r, gotErr := app.SignAndDeliverWithoutCommit( + tc.t, + tc.TxConfig, + tc.App.GetBaseApp(), + msgs, + tc.DefaultMsgFees, + tc.ChainID, + []uint64{signer.GetAccountNumber()}, + []uint64{signer.GetSequence()}, + privKey, + ) + + // NextBlock calls app.Commit() + tc.NextBlock() + + // increment sequence for successful and failed transaction execution + require.NoError(tc.t, signer.SetSequence(signer.GetSequence()+1)) + tc.Coordinator.IncrementTime() + + if gotErr != nil { + return nil, gotErr + } + + tc.CaptureIBCEvents(r.Events) + + return r, nil +} + type ProviderContracts struct { - vault sdk.AccAddress - externalStaking sdk.AccAddress + Vault sdk.AccAddress + ExternalStaking sdk.AccAddress } -func (p *TestProviderClient) BootstrapContracts(provApp *app.MeshApp, connId, portID string) ProviderContracts { var ( +func (p *TestProviderClient) BootstrapContracts(provApp *app.MeshApp, connId, portID string) ProviderContracts { + var ( unbondingPeriod = 21 * 24 * 60 * 60 // 21 days - make configurable? localSlashRatioDoubleSign = "0.20" localSlashRatioOffline = "0.10" @@ -106,19 +158,53 @@ func (p *TestProviderClient) BootstrapContracts(provApp *app.MeshApp, connId, po externalStakingContract := InstantiateContract(p.t, p.chain, extStakingCodeID, initMsg) r := ProviderContracts{ - vault: vaultContract, - externalStaking: externalStakingContract, + Vault: vaultContract, + ExternalStaking: externalStakingContract, } - p.contracts = r + p.Contracts = r return r } +func (p TestProviderClient) MustCreatePermanentLockedAccount(acc string, coins ...sdk.Coin) *sdk.Result { + rsp, err := p.chain.SendMsgs(&vestingtypes.MsgCreatePermanentLockedAccount{ + FromAddress: p.chain.SenderAccount.GetAddress().String(), + ToAddress: acc, + Amount: coins, + }) + require.NoError(p.t, err) + return rsp +} + +func (p TestProviderClient) BankSendWithSigner(privKey cryptotypes.PrivKey, signer *authtypes.BaseAccount, to string, coins ...sdk.Coin) error { + _, err := p.chain.SendMsgsWithSigner( + privKey, + signer, + &banktypes.MsgSend{ + FromAddress: signer.GetAddress().String(), + ToAddress: to, + Amount: coins, + }, + ) + return err +} + func (p TestProviderClient) MustExecVault(payload string, funds ...sdk.Coin) *sdk.Result { - return p.mustExec(p.contracts.vault, payload, funds) + return p.mustExec(p.Contracts.Vault, payload, funds) +} + +func (p TestProviderClient) MustExecVaultWithSigner(privKey cryptotypes.PrivKey, signer *authtypes.BaseAccount, payload string, funds ...sdk.Coin) *sdk.Result { + rsp, err := p.ExecWithSigner(privKey, signer, p.Contracts.Vault, payload, funds...) + require.NoError(p.t, err) + return rsp +} + +func (p TestProviderClient) ExecVaultWithSigner(privKey cryptotypes.PrivKey, signer *authtypes.BaseAccount, payload string, funds ...sdk.Coin) error { + _, err := p.ExecWithSigner(privKey, signer, p.Contracts.Vault, payload, funds...) + return err } func (p TestProviderClient) MustExecExtStaking(payload string, funds ...sdk.Coin) *sdk.Result { - return p.mustExec(p.contracts.externalStaking, payload, funds) + return p.mustExec(p.Contracts.ExternalStaking, payload, funds) } func (p TestProviderClient) mustExec(contract sdk.AccAddress, payload string, funds []sdk.Coin) *sdk.Result { @@ -137,8 +223,22 @@ func (p TestProviderClient) Exec(contract sdk.AccAddress, payload string, funds return rsp, err } +func (p TestProviderClient) ExecWithSigner(privKey cryptotypes.PrivKey, signer *authtypes.BaseAccount, contract sdk.AccAddress, payload string, funds ...sdk.Coin) (*sdk.Result, error) { + rsp, err := p.chain.SendMsgsWithSigner( + privKey, + signer, + &wasmtypes.MsgExecuteContract{ + Sender: signer.GetAddress().String(), + Contract: contract.String(), + Msg: []byte(payload), + Funds: funds, + }, + ) + return rsp, err +} + func (p TestProviderClient) MustFailExecVault(payload string, funds ...sdk.Coin) error { - rsp, err := p.Exec(p.contracts.vault, payload, funds...) + rsp, err := p.Exec(p.Contracts.Vault, payload, funds...) require.Error(p.t, err, "Response: %v", rsp) return err } @@ -149,10 +249,10 @@ func (p TestProviderClient) MustExecStakeRemote(val string, amt sdk.Coin) { func (p TestProviderClient) ExecStakeRemote(val string, amt sdk.Coin) error { payload := fmt.Sprintf(`{"stake_remote":{"contract":"%s", "amount": {"denom":%q, "amount":"%s"}, "msg":%q}}`, - p.contracts.externalStaking.String(), + p.Contracts.ExternalStaking.String(), amt.Denom, amt.Amount.String(), base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"validator": "%s"}`, val)))) - _, err := p.Exec(p.contracts.vault, payload) + _, err := p.Exec(p.Contracts.Vault, payload) return err } @@ -168,11 +268,11 @@ func (p TestProviderClient) QueryExtStakingAmount(user, validator string) int { } func (p TestProviderClient) QueryExtStaking(q Query) QueryResponse { - return Querier(p.t, p.chain)(p.contracts.externalStaking.String(), q) + return Querier(p.t, p.chain)(p.Contracts.ExternalStaking.String(), q) } func (p TestProviderClient) QueryVault(q Query) QueryResponse { - return Querier(p.t, p.chain)(p.contracts.vault.String(), q) + return Querier(p.t, p.chain)(p.Contracts.Vault.String(), q) } type HighLowType struct { @@ -210,6 +310,16 @@ func (p TestProviderClient) QueryVaultBalance() int { return b } +func (p TestProviderClient) QuerySpecificAddressVaultBalance(address string) int { + qRsp := p.QueryVault(Query{ + "account_details": {"account": address}, + }) + require.NotEmpty(p.t, qRsp["bonded"], qRsp) + b, err := strconv.Atoi(qRsp["bonded"].(string)) + require.NoError(p.t, err) + return b +} + func (p TestProviderClient) QueryMaxLien() int { qRsp := p.QueryVault(Query{ "account_details": {"account": p.chain.SenderAccount.GetAddress().String()}, @@ -228,12 +338,12 @@ func (p TestProviderClient) QuerySlashableAmount() int { type TestConsumerClient struct { t *testing.T - chain *ibctesting.TestChain + chain *TestChain contracts ConsumerContract app *app.MeshApp } -func NewConsumerClient(t *testing.T, chain *ibctesting.TestChain) *TestConsumerClient { +func NewConsumerClient(t *testing.T, chain *TestChain) *TestConsumerClient { return &TestConsumerClient{t: t, chain: chain, app: chain.App.(*app.MeshApp)} } @@ -278,7 +388,7 @@ func (p *TestConsumerClient) ExecNewEpoch() { execHeight, ok := p.app.MeshSecKeeper.GetNextScheduledTaskHeight(p.chain.GetContext(), types.SchedulerTaskHandleEpoch, p.contracts.staking) require.True(p.t, ok) if ch := uint64(p.chain.GetContext().BlockHeight()); ch < execHeight { - p.chain.Coordinator.CommitNBlocks(p.chain, execHeight-ch) + p.chain.Coordinator.CommitNBlocks(p.chain.IBCTestChain(), execHeight-ch) } rsp := p.chain.NextBlock() // ensure capture events do not contain a contract error diff --git a/tests/e2e/valset_test.go b/tests/e2e/valset_test.go index 9d5201d3..ebc3ac8f 100644 --- a/tests/e2e/valset_test.go +++ b/tests/e2e/valset_test.go @@ -165,12 +165,12 @@ func undelegate(t *testing.T, operatorKeys *secp256k1.PrivKey, amount sdkmath.In x.ConsumerChain.NextBlock() } -func jailValidator(t *testing.T, consAddr sdk.ConsAddress, coordinator *wasmibctesting.Coordinator, chain *wasmibctesting.TestChain, app *app.MeshApp) { +func jailValidator(t *testing.T, consAddr sdk.ConsAddress, coordinator *wasmibctesting.Coordinator, chain *TestChain, app *app.MeshApp) { ctx := chain.GetContext() signInfo, found := app.SlashingKeeper.GetValidatorSigningInfo(ctx, consAddr) require.True(t, found) // bump height to be > block window - coordinator.CommitNBlocks(chain, 100) + coordinator.CommitNBlocks(chain.IBCTestChain(), 100) ctx = chain.GetContext() signInfo.MissedBlocksCounter = app.SlashingKeeper.MinSignedPerWindow(ctx) app.SlashingKeeper.SetValidatorSigningInfo(ctx, consAddr, signInfo) @@ -180,7 +180,7 @@ func jailValidator(t *testing.T, consAddr sdk.ConsAddress, coordinator *wasmibct chain.NextBlock() } -func unjailValidator(t *testing.T, consAddr sdk.ConsAddress, operatorKeys *secp256k1.PrivKey, coordinator *wasmibctesting.Coordinator, chain *wasmibctesting.TestChain, app *app.MeshApp) { +func unjailValidator(t *testing.T, consAddr sdk.ConsAddress, operatorKeys *secp256k1.PrivKey, coordinator *wasmibctesting.Coordinator, chain *TestChain, app *app.MeshApp) { // move clock aa, ok := app.SlashingKeeper.GetValidatorSigningInfo(chain.GetContext(), consAddr) require.True(t, ok) @@ -193,7 +193,7 @@ func unjailValidator(t *testing.T, consAddr sdk.ConsAddress, operatorKeys *secp2 chain.NextBlock() } -func CreateNewValidator(t *testing.T, operatorKeys *secp256k1.PrivKey, chain *wasmibctesting.TestChain, power int64) mock.PV { +func CreateNewValidator(t *testing.T, operatorKeys *secp256k1.PrivKey, chain *TestChain, power int64) mock.PV { privVal := mock.NewPV() bondCoin := sdk.NewCoin(sdk.DefaultBondDenom, sdk.TokensFromConsensusPower(power, sdk.DefaultPowerReduction)) description := stakingtypes.NewDescription("my new val", "", "", "", "") diff --git a/x/meshsecurityprovider/keeper/keeper.go b/x/meshsecurityprovider/keeper/keeper.go index ed90d183..d2260f61 100644 --- a/x/meshsecurityprovider/keeper/keeper.go +++ b/x/meshsecurityprovider/keeper/keeper.go @@ -1,6 +1,8 @@ package keeper import ( + "fmt" + "github.com/cometbft/cometbft/libs/log" wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" @@ -96,6 +98,7 @@ func (k Keeper) HandleBondMsg(ctx sdk.Context, actor sdk.AccAddress, bondMsg *co if actor.String() != k.VaultAddress(ctx) { return nil, nil, sdkerrors.ErrUnauthorized.Wrapf("contract has no permission for mesh security operations") } + fmt.Println("bond msg: ", bondMsg) coin, err := wasmkeeper.ConvertWasmCoinToSdkCoin(bondMsg.Amount) if err != nil { @@ -112,6 +115,7 @@ func (k Keeper) HandleBondMsg(ctx sdk.Context, actor sdk.AccAddress, bondMsg *co return nil, nil, err } + fmt.Println("passssssssssssssssssssssssssssssssssssss") return []sdk.Event{sdk.NewEvent( types.EventTypeBond, sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), From 2922c1e3b0939915c91fbc6b49a654b90f6938ce Mon Sep 17 00:00:00 2001 From: Trinity Date: Wed, 21 Aug 2024 13:30:51 +0700 Subject: [PATCH 2/2] nit --- x/meshsecurityprovider/keeper/keeper.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/x/meshsecurityprovider/keeper/keeper.go b/x/meshsecurityprovider/keeper/keeper.go index d2260f61..ed90d183 100644 --- a/x/meshsecurityprovider/keeper/keeper.go +++ b/x/meshsecurityprovider/keeper/keeper.go @@ -1,8 +1,6 @@ package keeper import ( - "fmt" - "github.com/cometbft/cometbft/libs/log" wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" @@ -98,7 +96,6 @@ func (k Keeper) HandleBondMsg(ctx sdk.Context, actor sdk.AccAddress, bondMsg *co if actor.String() != k.VaultAddress(ctx) { return nil, nil, sdkerrors.ErrUnauthorized.Wrapf("contract has no permission for mesh security operations") } - fmt.Println("bond msg: ", bondMsg) coin, err := wasmkeeper.ConvertWasmCoinToSdkCoin(bondMsg.Amount) if err != nil { @@ -115,7 +112,6 @@ func (k Keeper) HandleBondMsg(ctx sdk.Context, actor sdk.AccAddress, bondMsg *co return nil, nil, err } - fmt.Println("passssssssssssssssssssssssssssssssssssss") return []sdk.Event{sdk.NewEvent( types.EventTypeBond, sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),