Skip to content

Commit

Permalink
feat: vesting bug fixes and simple integration testing
Browse files Browse the repository at this point in the history
  • Loading branch information
hacheigriega committed Feb 10, 2024
1 parent d00699b commit d52dbfb
Show file tree
Hide file tree
Showing 5 changed files with 846 additions and 0 deletions.
206 changes: 206 additions & 0 deletions app/integration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
package app

import (
"fmt"
"time"

cmtabcitypes "github.com/cometbft/cometbft/abci/types"
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
dbm "github.com/cosmos/cosmos-db"

"cosmossdk.io/core/appmodule"
"cosmossdk.io/log"
"cosmossdk.io/store"
"cosmossdk.io/store/metrics"
storetypes "cosmossdk.io/store/types"

"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/runtime"
"github.com/cosmos/cosmos-sdk/testutil/integration"
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
consensusparamkeeper "github.com/cosmos/cosmos-sdk/x/consensus/keeper"
consensusparamtypes "github.com/cosmos/cosmos-sdk/x/consensus/types"
)

const appName = "integration-app"

// IntegationApp is a test application that can be used to test the integration of modules.
type IntegationApp struct {
*baseapp.BaseApp

ctx sdk.Context
logger log.Logger
moduleManager module.Manager
queryHelper *baseapp.QueryServiceTestHelper
}

// NewIntegrationApp creates an application for testing purposes. This application
// is able to route messages to their respective handlers.
func NewIntegrationApp(
sdkCtx sdk.Context,
logger log.Logger,
keys map[string]*storetypes.KVStoreKey,
appCodec codec.Codec,
modules map[string]appmodule.AppModule,
) *IntegationApp {
db := dbm.NewMemDB()

interfaceRegistry := codectypes.NewInterfaceRegistry()
moduleManager := module.NewManagerFromMap(modules)
basicModuleManager := module.NewBasicManagerFromManager(moduleManager, nil)
basicModuleManager.RegisterInterfaces(interfaceRegistry)

txConfig := authtx.NewTxConfig(codec.NewProtoCodec(interfaceRegistry), authtx.DefaultSignModes)
bApp := baseapp.NewBaseApp(appName, logger, db, txConfig.TxDecoder(), baseapp.SetChainID(appName))
bApp.MountKVStores(keys)

bApp.SetInitChainer(func(ctx sdk.Context, _ *cmtabcitypes.RequestInitChain) (*cmtabcitypes.ResponseInitChain, error) {
for _, mod := range modules {
if m, ok := mod.(module.HasGenesis); ok {
m.InitGenesis(ctx, appCodec, m.DefaultGenesis(appCodec))
}
}

return &cmtabcitypes.ResponseInitChain{}, nil
})

bApp.SetBeginBlocker(func(_ sdk.Context) (sdk.BeginBlock, error) {
return moduleManager.BeginBlock(sdkCtx)
})
bApp.SetEndBlocker(func(_ sdk.Context) (sdk.EndBlock, error) {
return moduleManager.EndBlock(sdkCtx)
})

router := baseapp.NewMsgServiceRouter()
router.SetInterfaceRegistry(interfaceRegistry)
bApp.SetMsgServiceRouter(router)

if keys[consensusparamtypes.StoreKey] != nil {
// set baseApp param store
consensusParamsKeeper := consensusparamkeeper.NewKeeper(appCodec, runtime.NewKVStoreService(keys[consensusparamtypes.StoreKey]), authtypes.NewModuleAddress("gov").String(), runtime.EventService{})
bApp.SetParamStore(consensusParamsKeeper.ParamsStore)

if err := bApp.LoadLatestVersion(); err != nil {
panic(fmt.Errorf("failed to load application version from store: %w", err))
}

if _, err := bApp.InitChain(&cmtabcitypes.RequestInitChain{ChainId: appName, ConsensusParams: simtestutil.DefaultConsensusParams}); err != nil {
panic(fmt.Errorf("failed to initialize application: %w", err))
}
} else {
if err := bApp.LoadLatestVersion(); err != nil {
panic(fmt.Errorf("failed to load application version from store: %w", err))
}

// app.NewDefaultGenesisState(bApp.AppCodec())
// req := &abci.RequestInitChain{
// AppStateBytes: appState,
// ChainId: appName,
// ConsensusParams: consensusParams,
// Time: genesisTimestamp,
// }

if _, err := bApp.InitChain(&cmtabcitypes.RequestInitChain{ChainId: appName}); err != nil {
// if _, err := bApp.InitChain(req); err != nil {
panic(fmt.Errorf("failed to initialize application: %w", err))
}
}

bApp.Commit()

ctx := sdkCtx.WithBlockHeader(cmtproto.Header{ChainID: appName, Time: time.Now()}).WithIsCheckTx(true)

return &IntegationApp{
BaseApp: bApp,
logger: logger,
ctx: ctx,
moduleManager: *moduleManager,
queryHelper: baseapp.NewQueryServerTestHelper(ctx, interfaceRegistry),
}
}

// RunMsg provides the ability to run a message and return the response.
// In order to run a message, the application must have a handler for it.
// These handlers are registered on the application message service router.
// The result of the message execution is returned as an Any type.
// That any type can be unmarshaled to the expected response type.
// If the message execution fails, an error is returned.
func (app *IntegationApp) RunMsg(msg sdk.Msg, option ...integration.Option) (*codectypes.Any, error) {
// set options
cfg := &integration.Config{}
for _, opt := range option {
opt(cfg)
}

if cfg.AutomaticCommit {
defer app.Commit()
}

if cfg.AutomaticFinalizeBlock {
height := app.LastBlockHeight() + 1
if _, err := app.FinalizeBlock(&cmtabcitypes.RequestFinalizeBlock{Height: height}); err != nil {
return nil, fmt.Errorf("failed to run finalize block: %w", err)
}
}

app.logger.Info("Running msg", "msg", msg.String())

handler := app.MsgServiceRouter().Handler(msg)
if handler == nil {
return nil, fmt.Errorf("handler is nil, can't route message %s: %+v", sdk.MsgTypeURL(msg), msg)
}

msgResult, err := handler(app.ctx, msg)
if err != nil {
return nil, fmt.Errorf("failed to execute message %s: %w", sdk.MsgTypeURL(msg), err)
}

var response *codectypes.Any
if len(msgResult.MsgResponses) > 0 {
msgResponse := msgResult.MsgResponses[0]
if msgResponse == nil {
return nil, fmt.Errorf("got nil msg response %s in message result: %s", sdk.MsgTypeURL(msg), msgResult.String())
}

response = msgResponse
}

return response, nil
}

// Context returns the application context. It can be unwrapped to a sdk.Context,
// with the sdk.UnwrapSDKContext function.
func (app *IntegationApp) Context() sdk.Context {
return app.ctx
}

// AddTime adds time to the application context.
func (app *IntegationApp) AddTime(seconds int64) {
newTime := app.ctx.BlockHeader().Time.Add(time.Duration(int64(time.Second) * seconds))
app.ctx = app.ctx.WithBlockTime(newTime)
}

// QueryHelper returns the application query helper.
// It can be used when registering query services.
func (app *IntegationApp) QueryHelper() *baseapp.QueryServiceTestHelper {
return app.queryHelper
}

// CreateMultiStore is a helper for setting up multiple stores for provided modules.
func CreateMultiStore(keys map[string]*storetypes.KVStoreKey, logger log.Logger) storetypes.CommitMultiStore {
db := dbm.NewMemDB()
cms := store.NewCommitMultiStore(db, logger, metrics.NewNoOpMetrics())

for key := range keys {
cms.MountStoreWithDB(keys[key], storetypes.StoreTypeIAVL, db)
}

_ = cms.LoadLatestVersion()
return cms
}
62 changes: 62 additions & 0 deletions proto/sedachain/vesting/v1/tx.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
syntax = "proto3";
package sedachain.vesting.v1;

import "gogoproto/gogo.proto";
import "cosmos/base/v1beta1/coin.proto";
import "cosmos_proto/cosmos.proto";
import "cosmos/vesting/v1beta1/vesting.proto";
import "cosmos/msg/v1/msg.proto";
import "amino/amino.proto";

option go_package = "github.com/sedaprotocol/seda-chain/x/vesting/types";

// Msg defines the bank Msg service.
service Msg {
option (cosmos.msg.v1.service) = true;

// CreateVestingAccount creates a new vesting account.
rpc CreateVestingAccount(MsgCreateVestingAccount) returns (MsgCreateVestingAccountResponse);

// Clawback returns the vesting funds back to the funder.
rpc Clawback(MsgClawback) returns (MsgClawbackResponse);
}

// MsgCreateVestingAccount defines a message that creates a vesting account.
message MsgCreateVestingAccount {
option (cosmos.msg.v1.signer) = "from_address";
option (amino.name) = "cosmos-sdk/MsgCreateVestingAccount";

option (gogoproto.equal) = true;

string from_address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
string to_address = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"];
repeated cosmos.base.v1beta1.Coin amount = 3 [
(gogoproto.nullable) = false,
(amino.dont_omitempty) = true,
(amino.encoding) = "legacy_coins",
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];

// end of vesting as unix time (in seconds).
int64 end_time = 4;
}

// MsgCreateVestingAccountResponse defines the CreateVestingAccount response type.
message MsgCreateVestingAccountResponse {}

// MsgClawback defines a message that returns the vesting funds to the funder.
message MsgClawback {
option (cosmos.msg.v1.signer) = "funder_address";

// funder_address is the address which funded the account.
string funder_address = 1;
// account_address is the address of the vesting to claw back from.
string account_address = 2;
}

// MsgClawbackResponse defines the MsgClawback response type.
message MsgClawbackResponse {
// coins is the funds that have been clawed back to the funder.
repeated cosmos.base.v1beta1.Coin coins = 1
[(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins", (gogoproto.nullable) = false];
}
19 changes: 19 additions & 0 deletions proto/sedachain/vesting/v1/vesting.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
syntax = "proto3";
package sedachain.vesting.v1;

import "cosmos/vesting/v1beta1/vesting.proto";
import "gogoproto/gogo.proto";
import "google/protobuf/timestamp.proto";

option go_package = "github.com/sedaprotocol/seda-chain/x/vesting/types";

// ClawbackContinuousVestingAccount implements the VestingAccount interface.
// It wraps a ContinuousVestingAccount provided by Cosmos SDK to provide
// additional support for clawback.
message ClawbackContinuousVestingAccount {
option (gogoproto.goproto_getters) = false;
option (gogoproto.goproto_stringer) = false;

cosmos.vesting.v1beta1.ContinuousVestingAccount vesting_account = 1 [(gogoproto.embed) = true];
string funder_address = 2;
}
Loading

0 comments on commit d52dbfb

Please sign in to comment.