diff --git a/Makefile b/Makefile index c43b83294c..8a82afa15e 100644 --- a/Makefile +++ b/Makefile @@ -10,26 +10,26 @@ export GO111MODULE = on LEDGER_ENABLED ?= true build_tags = netgo ifeq ($(LEDGER_ENABLED),true) - ifeq ($(OS),Windows_NT) - GCCEXE = $(shell where gcc.exe 2> NUL) - ifeq ($(GCCEXE),) - $(error gcc.exe not installed for ledger support, please install or set LEDGER_ENABLED=false) - else - build_tags += ledger - endif - else - UNAME_S = $(shell uname -s) - ifeq ($(UNAME_S),OpenBSD) - $(warning OpenBSD detected, disabling ledger support (https://github.com/cosmos/cosmos-sdk/issues/1988)) - else - GCC = $(shell command -v gcc 2> /dev/null) - ifeq ($(GCC),) - $(error gcc not installed for ledger support, please install or set LEDGER_ENABLED=false) - else - build_tags += ledger - endif - endif - endif + ifeq ($(OS),Windows_NT) + GCCEXE = $(shell where gcc.exe 2> NUL) + ifeq ($(GCCEXE),) + $(error gcc.exe not installed for ledger support, please install or set LEDGER_ENABLED=false) + else + build_tags += ledger + endif + else + UNAME_S = $(shell uname -s) + ifeq ($(UNAME_S),OpenBSD) + $(warning OpenBSD detected, disabling ledger support (https://github.com/cosmos/cosmos-sdk/issues/1988)) + else + GCC = $(shell command -v gcc 2> /dev/null) + ifeq ($(GCC),) + $(error gcc not installed for ledger support, please install or set LEDGER_ENABLED=false) + else + build_tags += ledger + endif + endif + endif endif build_tags += $(BUILD_TAGS) @@ -43,13 +43,13 @@ build_tags_comma_sep := $(subst $(whitespace),$(comma),$(build_tags)) # process linker flags ldflags = -X github.com/cosmos/cosmos-sdk/version.Name=sei \ - -X github.com/cosmos/cosmos-sdk/version.ServerName=seid \ - -X github.com/cosmos/cosmos-sdk/version.Version=$(VERSION) \ - -X github.com/cosmos/cosmos-sdk/version.Commit=$(COMMIT) \ - -X "github.com/cosmos/cosmos-sdk/version.BuildTags=$(build_tags_comma_sep)" + -X github.com/cosmos/cosmos-sdk/version.ServerName=seid \ + -X github.com/cosmos/cosmos-sdk/version.Version=$(VERSION) \ + -X github.com/cosmos/cosmos-sdk/version.Commit=$(COMMIT) \ + -X "github.com/cosmos/cosmos-sdk/version.BuildTags=$(build_tags_comma_sep)" ifeq ($(LINK_STATICALLY),true) - ldflags += -linkmode=external -extldflags "-Wl,-z,muldefs -static" + ldflags += -linkmode=external -extldflags "-Wl,-z,muldefs -static" endif ldflags += $(LDFLAGS) ldflags := $(strip $(ldflags)) @@ -64,6 +64,10 @@ all: lint install install: go.sum go install $(BUILD_FLAGS) ./cmd/seid +loadtest: go.sum + go build $(BUILD_FLAGS) -o ./build/loadtest ./loadtest/ + + go.sum: go.mod @echo "--> Ensure dependencies have not been modified" @go mod verify diff --git a/aclmapping/bank/mappings.go b/aclmapping/bank/mappings.go new file mode 100644 index 0000000000..47989a1823 --- /dev/null +++ b/aclmapping/bank/mappings.go @@ -0,0 +1,91 @@ +package aclbankmapping + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + utils "github.com/sei-protocol/sei-chain/aclmapping/utils" +) + +var ErrorInvalidMsgType = fmt.Errorf("invalid message received for bank module") + +func GetBankDepedencyGenerator() aclkeeper.DependencyGeneratorMap { + dependencyGeneratorMap := make(aclkeeper.DependencyGeneratorMap) + + // dex place orders + placeOrdersKey := acltypes.GenerateMessageKey(&banktypes.MsgSend{}) + dependencyGeneratorMap[placeOrdersKey] = MsgSendDependencyGenerator + + return dependencyGeneratorMap +} + +func MsgSendDependencyGenerator(keeper aclkeeper.Keeper, ctx sdk.Context, msg sdk.Msg) ([]sdkacltypes.AccessOperation, error) { + msgSend, ok := msg.(*banktypes.MsgSend) + if !ok { + return []sdkacltypes.AccessOperation{}, ErrorInvalidMsgType + } + fromAddrIdentifier := string(banktypes.CreateAccountBalancesPrefixFromBech32(msgSend.FromAddress)) + toAddrIdentifier := string(banktypes.CreateAccountBalancesPrefixFromBech32(msgSend.ToAddress)) + + accessOperations := []sdkacltypes.AccessOperation{ + // MsgSend also checks if the coin denom is enabled, but the information is from the params. + // Changing the param would require a gov proposal, which is synchrounos by default + + // Checks balance of sender + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: fromAddrIdentifier, + }, + // Reduce the amount from the sender's balance + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: fromAddrIdentifier, + }, + + // Checks balance for receiver + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: toAddrIdentifier, + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: toAddrIdentifier, + }, + + // Tries to create the reciever's account if it doesn't exist + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.CreateAddressStoreKeyFromBech32(msgSend.ToAddress)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.CreateAddressStoreKeyFromBech32(msgSend.ToAddress)), + }, + + // Gets Account Info for the sender + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.CreateAddressStoreKeyFromBech32(msgSend.FromAddress)), + }, + + { + ResourceType: sdkacltypes.ResourceType_ANY, + AccessType: sdkacltypes.AccessType_COMMIT, + IdentifierTemplate: utils.DefaultIDTemplate, + }, + } + + return accessOperations, nil +} diff --git a/aclmapping/bank/mappings_test.go b/aclmapping/bank/mappings_test.go new file mode 100644 index 0000000000..9800592f0b --- /dev/null +++ b/aclmapping/bank/mappings_test.go @@ -0,0 +1,158 @@ +package aclbankmapping + +import ( + "testing" + + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + "github.com/cosmos/cosmos-sdk/simapp" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/bank/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + aclutils "github.com/sei-protocol/sei-chain/aclmapping/utils" + oracletypes "github.com/sei-protocol/sei-chain/x/oracle/types" + "github.com/stretchr/testify/require" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" +) + + +func cacheTxContext(ctx sdk.Context) (sdk.Context, sdk.CacheMultiStore) { + ms := ctx.MultiStore() + msCache := ms.CacheMultiStore() + return ctx.WithMultiStore(msCache), msCache +} + +func TestMsgBankSendAclOps(t *testing.T) { + priv1 := secp256k1.GenPrivKey() + addr1 := sdk.AccAddress(priv1.PubKey().Address()) + priv2 := secp256k1.GenPrivKey() + addr2 := sdk.AccAddress(priv2.PubKey().Address()) + coins := sdk.Coins{sdk.NewInt64Coin("foocoin", 10)} + + tests := []struct { + name string + expectedError error + msg *types.MsgSend + dynamicDep bool + }{ + { + name: "default send", + msg: types.NewMsgSend(addr1, addr2, coins), + expectedError: nil, + dynamicDep: true, + }, + { + name: "dont check synchronous", + msg: types.NewMsgSend(addr1, addr2, coins), + expectedError: nil, + dynamicDep: false, + }, + } + + acc1 := &authtypes.BaseAccount{ + Address: addr1.String(), + } + acc2 := &authtypes.BaseAccount{ + Address: addr2.String(), + } + accs := authtypes.GenesisAccounts{acc1, acc2} + balances := []types.Balance{ + { + Address: addr1.String(), + Coins: coins, + }, + { + Address: addr2.String(), + Coins: coins, + }, + } + + app := simapp.SetupWithGenesisAccounts(accs, balances...) + ctx := app.BaseApp.NewContext(false, tmproto.Header{}) + + handler := bank.NewHandler(app.BankKeeper) + msgValidator := sdkacltypes.NewMsgValidator(aclutils.StoreKeyToResourceTypePrefixMap) + ctx = ctx.WithMsgValidator(msgValidator) + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + handlerCtx, cms := cacheTxContext(ctx) + + _, err := handler(handlerCtx, tc.msg) + + depdenencies , _ := MsgSendDependencyGenerator(app.AccessControlKeeper, handlerCtx, tc.msg) + + if !tc.dynamicDep { + depdenencies = sdkacltypes.SynchronousAccessOps() + } + + if tc.expectedError != nil { + require.EqualError(t, err, tc.expectedError.Error()) + } else { + require.NoError(t, err) + } + missing := handlerCtx.MsgValidator().ValidateAccessOperations(depdenencies, cms.GetEvents()) + require.Empty(t, missing) + }) + } +} + +func TestGeneratorInvalidMessageTypes(t *testing.T) { + accs := authtypes.GenesisAccounts{} + balances := []types.Balance{} + + app := simapp.SetupWithGenesisAccounts(accs, balances...) + ctx := app.BaseApp.NewContext(false, tmproto.Header{}) + + oracleVote := oracletypes.MsgAggregateExchangeRateVote{ + ExchangeRates: "1usei", + Feeder: "test", + Validator: "validator", + } + + _, err := MsgSendDependencyGenerator(app.AccessControlKeeper, ctx, &oracleVote) + require.Error(t, err) +} + +func TestMsgBeginBankSendGenerator(t *testing.T) { + priv1 := secp256k1.GenPrivKey() + addr1 := sdk.AccAddress(priv1.PubKey().Address()) + priv2 := secp256k1.GenPrivKey() + addr2 := sdk.AccAddress(priv2.PubKey().Address()) + coins := sdk.Coins{sdk.NewInt64Coin("foocoin", 10)} + + acc1 := &authtypes.BaseAccount{ + Address: addr1.String(), + } + acc2 := &authtypes.BaseAccount{ + Address: addr2.String(), + } + accs := authtypes.GenesisAccounts{acc1, acc2} + balances := []types.Balance{ + { + Address: addr1.String(), + Coins: coins, + }, + { + Address: addr2.String(), + Coins: coins, + }, + } + + app := simapp.SetupWithGenesisAccounts(accs, balances...) + ctx := app.BaseApp.NewContext(false, tmproto.Header{}) + + sendMsg := banktypes.MsgSend{ + FromAddress: addr1.String(), + ToAddress: addr2.String(), + Amount: coins, + } + + accessOps, err := MsgSendDependencyGenerator(app.AccessControlKeeper, ctx, &sendMsg) + require.NoError(t, err) + err = acltypes.ValidateAccessOps(accessOps) + require.NoError(t, err) +} diff --git a/aclmapping/dependency_generator.go b/aclmapping/dependency_generator.go new file mode 100644 index 0000000000..3b276cdaf4 --- /dev/null +++ b/aclmapping/dependency_generator.go @@ -0,0 +1,27 @@ +package aclmapping + +import ( + aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper" + aclbankmapping "github.com/sei-protocol/sei-chain/aclmapping/bank" + acldexmapping "github.com/sei-protocol/sei-chain/aclmapping/dex" + acltokenfactorymapping "github.com/sei-protocol/sei-chain/aclmapping/tokenfactory" + aclwasmmapping "github.com/sei-protocol/sei-chain/aclmapping/wasm" +) + +type CustomDependencyGenerator struct{} + +func NewCustomDependencyGenerator() CustomDependencyGenerator { + return CustomDependencyGenerator{} +} + +func (customDepGen CustomDependencyGenerator) GetCustomDependencyGenerators() aclkeeper.DependencyGeneratorMap { + dependencyGeneratorMap := make(aclkeeper.DependencyGeneratorMap) + wasmDependencyGenerators := aclwasmmapping.NewWasmDependencyGenerator() + + dependencyGeneratorMap = dependencyGeneratorMap.Merge(acldexmapping.GetDexDependencyGenerators()) + dependencyGeneratorMap = dependencyGeneratorMap.Merge(aclbankmapping.GetBankDepedencyGenerator()) + dependencyGeneratorMap = dependencyGeneratorMap.Merge(acltokenfactorymapping.GetTokenFactoryDependencyGenerators()) + dependencyGeneratorMap = dependencyGeneratorMap.Merge(wasmDependencyGenerators.GetWasmDependencyGenerators()) + + return dependencyGeneratorMap +} diff --git a/aclmapping/dex/mappings.go b/aclmapping/dex/mappings.go new file mode 100644 index 0000000000..775f785f5a --- /dev/null +++ b/aclmapping/dex/mappings.go @@ -0,0 +1,113 @@ +package acldexmapping + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + dextypes "github.com/sei-protocol/sei-chain/x/dex/types" +) + +var ErrPlaceOrdersGenerator = fmt.Errorf("invalid message received for dex module") + +func GetDexDependencyGenerators() aclkeeper.DependencyGeneratorMap { + dependencyGeneratorMap := make(aclkeeper.DependencyGeneratorMap) + + // dex place orders + placeOrdersKey := acltypes.GenerateMessageKey(&dextypes.MsgPlaceOrders{}) + cancelOrdersKey := acltypes.GenerateMessageKey(&dextypes.MsgCancelOrders{}) + dependencyGeneratorMap[placeOrdersKey] = DexPlaceOrdersDependencyGenerator + dependencyGeneratorMap[cancelOrdersKey] = DexCancelOrdersDependencyGenerator + + return dependencyGeneratorMap +} + +func GetDexMemReadWrite(contract string) []sdkacltypes.AccessOperation { + if contract == "" { + return []sdkacltypes.AccessOperation{} + } + + return []sdkacltypes.AccessOperation{ + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_DexMem, + IdentifierTemplate: contract, + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_DexMem, + IdentifierTemplate: contract, + }, + } +} + +func GetLongShortOrderBookOps(contractAddr string, priceDenom string, assetDenom string) []sdkacltypes.AccessOperation { + return []sdkacltypes.AccessOperation{ + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DEX_CONTRACT_LONGBOOK, + IdentifierTemplate: string(dextypes.OrderBookPrefix(true, contractAddr, priceDenom, assetDenom)), + }, + + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DEX_CONTRACT_SHORTBOOK, + IdentifierTemplate: string(dextypes.OrderBookPrefix(false, contractAddr, priceDenom, assetDenom)), + }, + } +} + +func DexPlaceOrdersDependencyGenerator(keeper aclkeeper.Keeper, ctx sdk.Context, msg sdk.Msg) ([]sdkacltypes.AccessOperation, error) { + placeOrdersMsg, ok := msg.(*dextypes.MsgPlaceOrders) + if !ok { + return []sdkacltypes.AccessOperation{}, ErrPlaceOrdersGenerator + } + + contractAddr := placeOrdersMsg.ContractAddr + + aclOps := GetDexMemReadWrite(contractAddr) + + aclOps = append(aclOps, []sdkacltypes.AccessOperation{ + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DEX_NEXT_ORDER_ID, + IdentifierTemplate: string(dextypes.NextOrderIDPrefix(contractAddr)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DEX_NEXT_ORDER_ID, + IdentifierTemplate: string(dextypes.NextOrderIDPrefix(contractAddr)), + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DEX_TICK_SIZE, + IdentifierTemplate: string(dextypes.TickSizeKeyPrefix(contractAddr)), + }, + }...) + + // Last Operation should always be a commit + aclOps = append(aclOps, *acltypes.CommitAccessOp()) + return aclOps, nil +} + +func DexCancelOrdersDependencyGenerator(keeper aclkeeper.Keeper, ctx sdk.Context, msg sdk.Msg) ([]sdkacltypes.AccessOperation, error) { + cancelOrdersMsg, ok := msg.(*dextypes.MsgCancelOrders) + if !ok { + return []sdkacltypes.AccessOperation{}, ErrPlaceOrdersGenerator + } + contractAddr := cancelOrdersMsg.ContractAddr + + aclOps := GetDexMemReadWrite(contractAddr) + + for _, order := range cancelOrdersMsg.GetCancellations() { + priceDenom := order.GetPriceDenom() + assetDenom := order.GetAssetDenom() + aclOps = append(aclOps, GetLongShortOrderBookOps(contractAddr, priceDenom, assetDenom)...) + } + + // Last Operation should always be a commit + aclOps = append(aclOps, *acltypes.CommitAccessOp()) + return aclOps, nil +} diff --git a/aclmapping/dex/mappings_test.go b/aclmapping/dex/mappings_test.go new file mode 100644 index 0000000000..906fd01b2e --- /dev/null +++ b/aclmapping/dex/mappings_test.go @@ -0,0 +1,294 @@ +package acldexmapping_test + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + "github.com/k0kubun/pp/v3" + dexacl "github.com/sei-protocol/sei-chain/aclmapping/dex" + aclutils "github.com/sei-protocol/sei-chain/aclmapping/utils" + "github.com/sei-protocol/sei-chain/app" + "github.com/sei-protocol/sei-chain/app/apptesting" + keepertest "github.com/sei-protocol/sei-chain/testutil/keeper" + dexcache "github.com/sei-protocol/sei-chain/x/dex/cache" + dexmsgserver "github.com/sei-protocol/sei-chain/x/dex/keeper/msgserver" + "github.com/sei-protocol/sei-chain/x/dex/types" + dextypes "github.com/sei-protocol/sei-chain/x/dex/types" + dexutils "github.com/sei-protocol/sei-chain/x/dex/utils" + oracletypes "github.com/sei-protocol/sei-chain/x/oracle/types" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type KeeperTestSuite struct { + apptesting.KeeperTestHelper + + queryClient dextypes.QueryClient + msgServer dextypes.MsgServer + defaultDenom string + defaultExchangeRate string + initialBalance sdk.Coins + creator string + contract string + + msgPlaceOrders *dextypes.MsgPlaceOrders + msgCancelOrders *dextypes.MsgCancelOrders +} + +func TestKeeperTestSuite(t *testing.T) { + suite.Run(t, new(KeeperTestSuite)) +} + +// Runs before each test case +func (suite *KeeperTestSuite) SetupTest() { + suite.Setup() +} + +// Explicitly only run once during setup +func (suite *KeeperTestSuite) PrepareTest() { + suite.defaultDenom = "usei" + suite.defaultExchangeRate = fmt.Sprintf("%dusei", sdk.NewDec(1700)) + + suite.initialBalance = sdk.Coins{sdk.NewInt64Coin(suite.defaultDenom, 100000000000)} + suite.initialBalance = sdk.Coins{sdk.NewInt64Coin("stake", 100000000000)} + suite.FundAcc(suite.TestAccs[0], suite.initialBalance) + + suite.queryClient = dextypes.NewQueryClient(suite.QueryHelper) + suite.msgServer = dexmsgserver.NewMsgServerImpl(suite.App.DexKeeper) + + msgValidator := sdkacltypes.NewMsgValidator(aclutils.StoreKeyToResourceTypePrefixMap) + suite.Ctx = suite.Ctx.WithMsgValidator(msgValidator) + + suite.Ctx = suite.Ctx.WithBlockHeight(10) + suite.Ctx = suite.Ctx.WithBlockTime(time.Unix(333, 0)) + + suite.creator = suite.TestAccs[0].String() + suite.contract = "sei14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sh9m79m" + + suite.App.DexKeeper.AddRegisteredPair(suite.Ctx, suite.contract, keepertest.TestPair) + suite.App.DexKeeper.SetTickSizeForPair(suite.Ctx, suite.contract, keepertest.TestPair, *keepertest.TestPair.Ticksize) + + suite.msgPlaceOrders = &types.MsgPlaceOrders{ + Creator: suite.creator, + ContractAddr: suite.contract, + Orders: []*types.Order{ + { + Price: sdk.MustNewDecFromStr("10"), + Quantity: sdk.MustNewDecFromStr("10"), + Data: "", + PositionDirection: types.PositionDirection_LONG, + OrderType: types.OrderType_LIMIT, + PriceDenom: keepertest.TestPriceDenom, + AssetDenom: keepertest.TestAssetDenom, + }, + { + Price: sdk.MustNewDecFromStr("20"), + Quantity: sdk.MustNewDecFromStr("5"), + Data: "", + PositionDirection: types.PositionDirection_SHORT, + OrderType: types.OrderType_MARKET, + PriceDenom: keepertest.TestPriceDenom, + AssetDenom: keepertest.TestAssetDenom, + }, + }, + } + + suite.msgCancelOrders = &types.MsgCancelOrders{ + Creator: suite.creator, + ContractAddr: suite.contract, + Cancellations: []*types.Cancellation{ + { + Id: 1, + Price: sdk.MustNewDecFromStr("10"), + Creator: suite.creator, + PositionDirection: types.PositionDirection_LONG, + PriceDenom: keepertest.TestPriceDenom, + AssetDenom: keepertest.TestAssetDenom, + }, + { + Id: 2, + Creator: suite.creator, + Price: sdk.MustNewDecFromStr("20"), + PositionDirection: types.PositionDirection_SHORT, + PriceDenom: keepertest.TestPriceDenom, + AssetDenom: keepertest.TestAssetDenom, + }, + }, + } +} + + +func (suite *KeeperTestSuite) TestMsgPlaceOrder() { + suite.PrepareTest() + tests := []struct { + name string + expectedError error + msg *dextypes.MsgPlaceOrders + dynamicDep bool + }{ + { + name: "default place order", + msg: suite.msgPlaceOrders, + expectedError: nil, + dynamicDep: true, + }, + { + name: "dont check synchronous", + msg: suite.msgPlaceOrders, + expectedError: nil, + dynamicDep: false, + }, + } + for _, tc := range tests { + suite.Run(fmt.Sprintf("Test Case: %s", tc.name), func() { + goCtx := context.WithValue(suite.Ctx.Context(), dexutils.DexMemStateContextKey, dexcache.NewMemState()) + suite.Ctx = suite.Ctx.WithContext(goCtx) + + handlerCtx, cms := aclutils.CacheTxContext(suite.Ctx) + _, err := suite.msgServer.PlaceOrders( + sdk.WrapSDKContext(handlerCtx), + tc.msg, + ) + + depdenencies , _ := dexacl.DexPlaceOrdersDependencyGenerator( + suite.App.AccessControlKeeper, + handlerCtx, + tc.msg, + ) + + if !tc.dynamicDep { + depdenencies = sdkacltypes.SynchronousAccessOps() + } + + if tc.expectedError != nil { + suite.Require().EqualError(err, tc.expectedError.Error()) + } else { + suite.Require().NoError(err) + } + + missing := handlerCtx.MsgValidator().ValidateAccessOperations(depdenencies, cms.GetEvents()) + pp.Default.SetColoringEnabled(false) + + suite.Require().Empty(missing) + }) + } +} + +func (suite *KeeperTestSuite) TestMsgCancelOrder() { + suite.PrepareTest() + tests := []struct { + name string + expectedError error + msg *dextypes.MsgCancelOrders + dynamicDep bool + }{ + { + name: "default cancel order", + msg: suite.msgCancelOrders, + expectedError: nil, + dynamicDep: true, + }, + { + name: "dont check synchronous", + msg: suite.msgCancelOrders, + expectedError: nil, + dynamicDep: false, + }, + } + for _, tc := range tests { + suite.Run(fmt.Sprintf("Test Case: %s", tc.name), func() { + goCtx := context.WithValue(suite.Ctx.Context(), dexutils.DexMemStateContextKey, dexcache.NewMemState()) + suite.Ctx = suite.Ctx.WithContext(goCtx) + + _, err := suite.msgServer.PlaceOrders( + sdk.WrapSDKContext(suite.Ctx), + suite.msgPlaceOrders, + ) + + handlerCtx, cms := aclutils.CacheTxContext(suite.Ctx) + _, err = suite.msgServer.CancelOrders( + sdk.WrapSDKContext(handlerCtx), + tc.msg, + ) + + depdenencies , _ := dexacl.DexCancelOrdersDependencyGenerator( + suite.App.AccessControlKeeper, + handlerCtx, + tc.msg, + ) + + if !tc.dynamicDep { + depdenencies = sdkacltypes.SynchronousAccessOps() + } + + if tc.expectedError != nil { + suite.Require().EqualError(err, tc.expectedError.Error()) + } else { + suite.Require().NoError(err) + } + + missing := handlerCtx.MsgValidator().ValidateAccessOperations(depdenencies, cms.GetEvents()) + suite.Require().Empty(missing) + }) + } +} + + + +func TestGeneratorInvalidMessageTypes(t *testing.T) { + tm := time.Now().UTC() + valPub := secp256k1.GenPrivKey().PubKey() + testWrapper := app.NewTestWrapper(t, tm, valPub) + + oracleVote := oracletypes.MsgAggregateExchangeRateVote{ + ExchangeRates: "1usei", + Feeder: "test", + Validator: "validator", + } + + _, err := dexacl.DexPlaceOrdersDependencyGenerator( + testWrapper.App.AccessControlKeeper, + testWrapper.Ctx, + &oracleVote, + ) + require.Error(t, err) + + _, err = dexacl.DexCancelOrdersDependencyGenerator( + testWrapper.App.AccessControlKeeper, + testWrapper.Ctx, + &oracleVote, + ) + require.Error(t, err) +} + +func (suite *KeeperTestSuite) TestMsgPlaceOrderGenerator() { + suite.PrepareTest() + + accessOps, err := dexacl.DexPlaceOrdersDependencyGenerator( + suite.App.AccessControlKeeper, + suite.Ctx, + suite.msgPlaceOrders, + ) + require.NoError(suite.T(), err) + err = acltypes.ValidateAccessOps(accessOps) + require.NoError(suite.T(), err) +} + +func (suite *KeeperTestSuite) TestMsgCancelOrderGenerator() { + suite.PrepareTest() + accessOps, err := dexacl.DexCancelOrdersDependencyGenerator( + suite.App.AccessControlKeeper, + suite.Ctx, + suite.msgCancelOrders, + ) + require.NoError(suite.T(), err) + err = acltypes.ValidateAccessOps(accessOps) + require.NoError(suite.T(), err) +} + diff --git a/aclmapping/oracle/mappings.go b/aclmapping/oracle/mappings.go new file mode 100644 index 0000000000..778bc5bf6b --- /dev/null +++ b/aclmapping/oracle/mappings.go @@ -0,0 +1,69 @@ +package acloraclemapping + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + utils "github.com/sei-protocol/sei-chain/aclmapping/utils" + oracletypes "github.com/sei-protocol/sei-chain/x/oracle/types" +) + +var ErrorInvalidMsgType = fmt.Errorf("invalid message received for oracle module") + +func GetOracleDependencyGenerator() aclkeeper.DependencyGeneratorMap { + dependencyGeneratorMap := make(aclkeeper.DependencyGeneratorMap) + + // vote + voteKey := acltypes.GenerateMessageKey(&oracletypes.MsgAggregateExchangeRateVote{}) + dependencyGeneratorMap[voteKey] = MsgVoteDependencyGenerator + + return dependencyGeneratorMap +} + +func MsgVoteDependencyGenerator(keeper aclkeeper.Keeper, ctx sdk.Context, msg sdk.Msg) ([]sdkacltypes.AccessOperation, error) { + msgVote, ok := msg.(*oracletypes.MsgAggregateExchangeRateVote) + if !ok { + return []sdkacltypes.AccessOperation{}, ErrorInvalidMsgType + } + valAddr, _ := sdk.ValAddressFromBech32(msgVote.Validator) + + accessOperations := []sdkacltypes.AccessOperation{ + // validate feeder + // read feeder delegation for val addr - READ + { + ResourceType: sdkacltypes.ResourceType_KV_ORACLE_FEEDERS, + AccessType: sdkacltypes.AccessType_READ, + IdentifierTemplate: string(oracletypes.GetFeederDelegationKey(valAddr)), + }, + // read validator from staking - READ + // validator is bonded check - READ + // (both covered by below) + { + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATOR, + AccessType: sdkacltypes.AccessType_READ, + IdentifierTemplate: string(stakingtypes.GetValidatorKey(valAddr)), + }, + + // get vote target (for all exchange rate tuples) -> blanket read on that prefix - READ + { + ResourceType: sdkacltypes.ResourceType_KV_ORACLE_VOTE_TARGETS, + AccessType: sdkacltypes.AccessType_READ, + IdentifierTemplate: utils.DefaultIDTemplate, + }, + + // set exchange rate vote - WRITE + { + ResourceType: sdkacltypes.ResourceType_KV_ORACLE_AGGREGATE_VOTES, + AccessType: sdkacltypes.AccessType_WRITE, + IdentifierTemplate: string(oracletypes.GetAggregateExchangeRateVoteKey(valAddr)), + }, + + // Last Operation should always be a commit + *acltypes.CommitAccessOp(), + } + return accessOperations, nil +} diff --git a/aclmapping/oracle/mappings_test.go b/aclmapping/oracle/mappings_test.go new file mode 100644 index 0000000000..0e3246475c --- /dev/null +++ b/aclmapping/oracle/mappings_test.go @@ -0,0 +1,143 @@ +package acloraclemapping_test + +import ( + "fmt" + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + oracleacl "github.com/sei-protocol/sei-chain/aclmapping/oracle" + aclutils "github.com/sei-protocol/sei-chain/aclmapping/utils" + utils "github.com/sei-protocol/sei-chain/aclmapping/utils" + "github.com/sei-protocol/sei-chain/app/apptesting" + oraclekeeper "github.com/sei-protocol/sei-chain/x/oracle/keeper" + oracletypes "github.com/sei-protocol/sei-chain/x/oracle/types" + + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/sei-protocol/sei-chain/app" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type KeeperTestSuite struct { + apptesting.KeeperTestHelper + + queryClient oracletypes.QueryClient + msgServer oracletypes.MsgServer + // defaultDenom is on the suite, as it depends on the creator test address. + defaultDenom string + defaultExchangeRate string + initalBalance sdk.Coins + + validator sdk.ValAddress +} + +func TestKeeperTestSuite(t *testing.T) { + suite.Run(t, new(KeeperTestSuite)) +} + +// Runs before each test case +func (suite *KeeperTestSuite) SetupTest() { + suite.Setup() +} + +// Explicitly only run once during setup +func (suite *KeeperTestSuite) PrepareTest() { + suite.defaultDenom = "usei" + suite.defaultExchangeRate = fmt.Sprintf("%dusei", sdk.NewDec(1700)) + + suite.initalBalance = sdk.Coins{sdk.NewInt64Coin(suite.defaultDenom, 100000000000)} + suite.FundAcc(suite.TestAccs[0], suite.initalBalance) + + suite.queryClient = oracletypes.NewQueryClient(suite.QueryHelper) + suite.msgServer = oraclekeeper.NewMsgServerImpl(suite.App.OracleKeeper) + + // testInput := oraclekeeper.CreateTestInput(suite.T()) + // suite.Ctx = testInput.Ctx + + msgValidator := sdkacltypes.NewMsgValidator(aclutils.StoreKeyToResourceTypePrefixMap) + suite.Ctx = suite.Ctx.WithMsgValidator(msgValidator) + suite.Ctx = suite.Ctx.WithBlockHeight(1) + suite.validator = suite.SetupValidator(stakingtypes.Bonded) + + suite.App.OracleKeeper.SetFeederDelegation(suite.Ctx, suite.validator, suite.TestAccs[0]) + suite.App.OracleKeeper.SetVoteTarget(suite.Ctx, suite.defaultDenom) +} + +func (suite *KeeperTestSuite) TestMsgBurnDependencies() { + suite.PrepareTest() + tests := []struct { + name string + expectedError error + msg *oracletypes.MsgAggregateExchangeRateVote + dynamicDep bool + }{ + { + name: "default vote", + msg: &oracletypes.MsgAggregateExchangeRateVote{ + ExchangeRates: suite.defaultExchangeRate, + Feeder: suite.TestAccs[0].String(), + Validator: suite.validator.String(), + }, + expectedError: nil, + dynamicDep: true, + }, + { + name: "dont check synchronous", + msg: &oracletypes.MsgAggregateExchangeRateVote{ + ExchangeRates: suite.defaultExchangeRate, + Feeder: suite.TestAccs[0].String(), + Validator: suite.validator.String(), + }, + expectedError: nil, + dynamicDep: false, + }, + } + for _, tc := range tests { + suite.Run(fmt.Sprintf("Test Case: %s", tc.name), func() { + handlerCtx, cms := utils.CacheTxContext(suite.Ctx) + _, err := suite.msgServer.AggregateExchangeRateVote( + sdk.WrapSDKContext(handlerCtx), + tc.msg, + ) + depdenencies , _ := oracleacl.MsgVoteDependencyGenerator( + suite.App.AccessControlKeeper, + handlerCtx, + tc.msg, + ) + + if !tc.dynamicDep { + depdenencies = sdkacltypes.SynchronousAccessOps() + } + + if tc.expectedError != nil { + suite.Require().EqualError(err, tc.expectedError.Error()) + } else { + suite.Require().NoError(err) + } + + missing := handlerCtx.MsgValidator().ValidateAccessOperations(depdenencies, cms.GetEvents()) + suite.Require().Empty(missing) + }) + } +} +func TestMsgVoteDependencyGenerator(t *testing.T) { + tm := time.Now().UTC() + valPub := secp256k1.GenPrivKey().PubKey() + + testWrapper := app.NewTestWrapper(t, tm, valPub) + + oracleVote := oracletypes.MsgAggregateExchangeRateVote{ + ExchangeRates: "1usei", + Feeder: "test", + Validator: "validator", + } + + accessOps, err := oracleacl.MsgVoteDependencyGenerator(testWrapper.App.AccessControlKeeper, testWrapper.Ctx, &oracleVote) + require.NoError(t, err) + err = acltypes.ValidateAccessOps(accessOps) + require.NoError(t, err) +} diff --git a/aclmapping/staking/mappings.go b/aclmapping/staking/mappings.go new file mode 100644 index 0000000000..70a280f1c8 --- /dev/null +++ b/aclmapping/staking/mappings.go @@ -0,0 +1,653 @@ +package aclstakingmapping + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +var ErrorInvalidMsgType = fmt.Errorf("invalid message received for staking module") + +func GetStakingDependencyGenerator() aclkeeper.DependencyGeneratorMap { + dependencyGeneratorMap := make(aclkeeper.DependencyGeneratorMap) + + delegateKey := acltypes.GenerateMessageKey(&stakingtypes.MsgDelegate{}) + undelegateKey := acltypes.GenerateMessageKey(&stakingtypes.MsgUndelegate{}) + beginRedelegateKey := acltypes.GenerateMessageKey(&stakingtypes.MsgBeginRedelegate{}) + dependencyGeneratorMap[delegateKey] = MsgDelegateDependencyGenerator + dependencyGeneratorMap[undelegateKey] = MsgUndelegateDependencyGenerator + dependencyGeneratorMap[beginRedelegateKey] = MsgBeginRedelegateDependencyGenerator + + return dependencyGeneratorMap +} + +func MsgDelegateDependencyGenerator(keeper aclkeeper.Keeper, ctx sdk.Context, msg sdk.Msg) ([]sdkacltypes.AccessOperation, error) { + msgDelegate, ok := msg.(*stakingtypes.MsgDelegate) + if !ok { + return []sdkacltypes.AccessOperation{}, ErrorInvalidMsgType + } + + bondedModuleAdr := keeper.AccountKeeper.GetModuleAddress(stakingtypes.BondedPoolName) + notBondedModuleAdr := keeper.AccountKeeper.GetModuleAddress(stakingtypes.NotBondedPoolName) + + delegateAddr, _ := sdk.AccAddressFromBech32(msgDelegate.DelegatorAddress) + validatorAddr, _ := sdk.ValAddressFromBech32(msgDelegate.ValidatorAddress) + + validator, _ := keeper.StakingKeeper.GetValidator(ctx, validatorAddr) + // validatorCons, _ := validator.GetConsAddr() + // validatorAddrCons := string(stakingtypes.GetValidatorByConsAddrKey(validatorCons)) + // validatorOperatorAddr := validator.GetOperator().String() + + delegationKey := string(stakingtypes.GetDelegationKey(delegateAddr, validatorAddr)) + validatorKey := string(stakingtypes.GetValidatorKey(validatorAddr)) + delegatorBalanceKey := string(banktypes.CreateAccountBalancesPrefixFromBech32(msgDelegate.DelegatorAddress)) + validatorBalanceKey := string(banktypes.CreateAccountBalancesPrefixFromBech32(msgDelegate.ValidatorAddress)) + // validatorOperatorBalanceKey := string(banktypes.CreateAccountBalancesPrefixFromBech32(validatorOperatorAddr)) + + accessOperations := []sdkacltypes.AccessOperation{ + // Treat Delegations and Undelegations to have the same ACL since they are highly coupled, no point in finer granularization + + // Get delegation/redelegations and error checking + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_DELEGATION, + IdentifierTemplate: delegationKey, + }, + // Update/delete delegation and update redelegation + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_DELEGATION, + IdentifierTemplate: delegationKey, + }, + + // Check Unbonding + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATORS_BY_POWER, + IdentifierTemplate: string(stakingtypes.GetValidatorsByPowerIndexKey(validator, keeper.StakingKeeper.PowerReduction(ctx))), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATORS_BY_POWER, + IdentifierTemplate: string(stakingtypes.GetValidatorsByPowerIndexKey(validator, keeper.StakingKeeper.PowerReduction(ctx))), + }, + + // Before Unbond Distribution Hook + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_DELEGATOR_STARTING_INFO, + IdentifierTemplate: string(distributiontypes.GetDelegatorStartingInfoKey(validatorAddr, delegateAddr)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_DELEGATOR_STARTING_INFO, + IdentifierTemplate: string(distributiontypes.GetDelegatorStartingInfoKey(validatorAddr, delegateAddr)), + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_VAL_CURRENT_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorCurrentRewardsKey(validatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_VAL_CURRENT_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorCurrentRewardsKey(validatorAddr)), + }, + + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_OUTSTANDING_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorOutstandingRewardsKey(validatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_OUTSTANDING_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorOutstandingRewardsKey(validatorAddr)), + }, + + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_FEE_POOL, + IdentifierTemplate: string(distributiontypes.FeePoolKey), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_FEE_POOL, + IdentifierTemplate: string(distributiontypes.FeePoolKey), + }, + + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_VAL_HISTORICAL_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorHistoricalRewardsPrefix(validatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_VAL_HISTORICAL_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorHistoricalRewardsPrefix(validatorAddr)), + }, + + // Gets Module Account information + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.AddressStoreKey(bondedModuleAdr)), + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.AddressStoreKey(notBondedModuleAdr)), + }, + + // Get Delegator Acc Info + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.AddressStoreKey(delegateAddr)), + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.AddressStoreKey(delegateAddr)), + }, + + // Update the delegator and validator account balances + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: delegatorBalanceKey, + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: delegatorBalanceKey, + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: validatorBalanceKey, + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: validatorBalanceKey, + }, + + // Checks if the validators exchange rate is valid + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATOR, + IdentifierTemplate: validatorKey, + }, + // Update validator shares and power index + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATOR, + IdentifierTemplate: validatorKey, + }, + + // Last Operation should always be a commit + *acltypes.CommitAccessOp(), + } + return accessOperations, nil +} + +func MsgUndelegateDependencyGenerator(keeper aclkeeper.Keeper, ctx sdk.Context, msg sdk.Msg) ([]sdkacltypes.AccessOperation, error) { + msgUndelegate, ok := msg.(*stakingtypes.MsgUndelegate) + if !ok { + return []sdkacltypes.AccessOperation{}, ErrorInvalidMsgType + } + bondedModuleAdr := keeper.AccountKeeper.GetModuleAddress(stakingtypes.BondedPoolName) + notBondedModuleAdr := keeper.AccountKeeper.GetModuleAddress(stakingtypes.NotBondedPoolName) + + delegateAddr, _ := sdk.AccAddressFromBech32(msgUndelegate.DelegatorAddress) + validatorAddr, _ := sdk.ValAddressFromBech32(msgUndelegate.ValidatorAddress) + + validator, _ := keeper.StakingKeeper.GetValidator(ctx, validatorAddr) + validatorCons, _ := validator.GetConsAddr() + validatorAddrCons := string(stakingtypes.GetValidatorByConsAddrKey(validatorCons)) + + delegationKey := string(stakingtypes.GetDelegationKey(delegateAddr, validatorAddr)) + validatorKey := string(stakingtypes.GetValidatorKey(validatorAddr)) + delegatorBalanceKey := string(banktypes.CreateAccountBalancesPrefixFromBech32(msgUndelegate.DelegatorAddress)) + validatorBalanceKey := string(banktypes.CreateAccountBalancesPrefixFromBech32(msgUndelegate.ValidatorAddress)) + + accessOperations := []sdkacltypes.AccessOperation{ + // Treat Delegations and Undelegations to have the same ACL since they are highly coupled, no point in finer granularization + + // Get delegation/redelegations and error checking + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_DELEGATION, + IdentifierTemplate: delegationKey, + }, + // Update/delete delegation and update redelegation + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_DELEGATION, + IdentifierTemplate: delegationKey, + }, + + // Check Unbonding + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_UNBONDING_DELEGATION, + IdentifierTemplate: string(stakingtypes.GetUBDKey(delegateAddr, validatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_UNBONDING_DELEGATION, + IdentifierTemplate: string(stakingtypes.GetUBDKey(delegateAddr, validatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_UNBONDING_DELEGATION_VAL, + IdentifierTemplate: string(stakingtypes.GetUBDsByValIndexKey(validatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_UNBONDING_DELEGATION_VAL, + IdentifierTemplate: string(stakingtypes.GetUBDsByValIndexKey(validatorAddr)), + }, + + // Testing + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_UNBONDING_DELEGATION, + IdentifierTemplate: delegationKey, + }, + + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_UNBONDING, + IdentifierTemplate: string(stakingtypes.UnbondingQueueKey), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_UNBONDING, + IdentifierTemplate: string(stakingtypes.UnbondingQueueKey), + }, + + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATORS_CON_ADDR, + IdentifierTemplate: validatorAddrCons, + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATORS_CON_ADDR, + IdentifierTemplate: string(stakingtypes.GetUBDsByValIndexKey(validatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATORS_BY_POWER, + IdentifierTemplate: string(stakingtypes.GetValidatorsByPowerIndexKey(validator, keeper.StakingKeeper.PowerReduction(ctx))), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATORS_BY_POWER, + IdentifierTemplate: string(stakingtypes.GetValidatorsByPowerIndexKey(validator, keeper.StakingKeeper.PowerReduction(ctx))), + }, + + // Before Unbond Distribution Hook + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_DELEGATOR_STARTING_INFO, + IdentifierTemplate: string(distributiontypes.GetDelegatorStartingInfoKey(validatorAddr, delegateAddr)), + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_VAL_CURRENT_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorCurrentRewardsKey(validatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_VAL_CURRENT_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorCurrentRewardsKey(validatorAddr)), + }, + + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_OUTSTANDING_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorOutstandingRewardsKey(validatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_OUTSTANDING_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorOutstandingRewardsKey(validatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_FEE_POOL, + IdentifierTemplate: string(distributiontypes.FeePoolKey), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_FEE_POOL, + IdentifierTemplate: string(distributiontypes.FeePoolKey), + }, + + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_VAL_HISTORICAL_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorHistoricalRewardsPrefix(validatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_VAL_HISTORICAL_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorHistoricalRewardsPrefix(validatorAddr)), + }, + + // Gets Module Account information + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.AddressStoreKey(bondedModuleAdr)), + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.AddressStoreKey(notBondedModuleAdr)), + }, + + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_DELEGATOR_STARTING_INFO, + IdentifierTemplate: string(distributiontypes.GetDelegatorStartingInfoKey(validatorAddr, delegateAddr)), + }, + + // Update the delegator and validator account balances + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: delegatorBalanceKey, + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: delegatorBalanceKey, + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: validatorBalanceKey, + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: validatorBalanceKey, + }, + + // Checks if the validators exchange rate is valid + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATOR, + IdentifierTemplate: validatorKey, + }, + // Update validator shares and power index + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATOR, + IdentifierTemplate: validatorKey, + }, + + // Last Operation should always be a commit + *acltypes.CommitAccessOp(), + } + + return accessOperations, nil +} + +func MsgBeginRedelegateDependencyGenerator(keeper aclkeeper.Keeper, ctx sdk.Context, msg sdk.Msg) ([]sdkacltypes.AccessOperation, error) { + msgBeingRedelegate, ok := msg.(*stakingtypes.MsgBeginRedelegate) + if !ok { + return []sdkacltypes.AccessOperation{}, ErrorInvalidMsgType + } + bondedModuleAdr := keeper.AccountKeeper.GetModuleAddress(stakingtypes.BondedPoolName) + notBondedModuleAdr := keeper.AccountKeeper.GetModuleAddress(stakingtypes.NotBondedPoolName) + + delegateAddr, _ := sdk.AccAddressFromBech32(msgBeingRedelegate.DelegatorAddress) + srcValidatorAddr, _ := sdk.ValAddressFromBech32(msgBeingRedelegate.ValidatorSrcAddress) + dstValidatorAddr, _ := sdk.ValAddressFromBech32(msgBeingRedelegate.ValidatorDstAddress) + + srcValidator, _ := keeper.StakingKeeper.GetValidator(ctx, srcValidatorAddr) + dstValidator, _ := keeper.StakingKeeper.GetValidator(ctx, dstValidatorAddr) + + srcDelegationKey := string(stakingtypes.GetDelegationKey(delegateAddr, srcValidatorAddr)) + dstDelegationKey := string(stakingtypes.GetDelegationKey(delegateAddr, dstValidatorAddr)) + + srcValidatorKey := string(stakingtypes.GetValidatorKey(srcValidatorAddr)) + dstValidatorKey := string(stakingtypes.GetValidatorKey(dstValidatorAddr)) + + dstValidatorBalanceKey := string(banktypes.CreateAccountBalancesPrefixFromBech32(msgBeingRedelegate.ValidatorDstAddress)) + + accessOperations := []sdkacltypes.AccessOperation{ + // Treat Delegations and Undelegations to have the same ACL since they are highly coupled, no point in finer granularization + + // Get delegation/redelegations and error checking + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_DELEGATION, + IdentifierTemplate: srcDelegationKey, + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_DELEGATION, + IdentifierTemplate: dstDelegationKey, + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_REDELEGATION, + IdentifierTemplate: string(stakingtypes.GetREDsKey(delegateAddr)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_REDELEGATION, + IdentifierTemplate: string(stakingtypes.GetREDsKey(delegateAddr)), + }, + + // Update/delete delegation and update redelegation + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_DELEGATION, + IdentifierTemplate: dstDelegationKey, + }, + + // Check Unbonding + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATORS_BY_POWER, + IdentifierTemplate: string(stakingtypes.GetValidatorsByPowerIndexKey(srcValidator, keeper.StakingKeeper.PowerReduction(ctx))), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATORS_BY_POWER, + IdentifierTemplate: string(stakingtypes.GetValidatorsByPowerIndexKey(srcValidator, keeper.StakingKeeper.PowerReduction(ctx))), + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATORS_BY_POWER, + IdentifierTemplate: string(stakingtypes.GetValidatorsByPowerIndexKey(dstValidator, keeper.StakingKeeper.PowerReduction(ctx))), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATORS_BY_POWER, + IdentifierTemplate: string(stakingtypes.GetValidatorsByPowerIndexKey(dstValidator, keeper.StakingKeeper.PowerReduction(ctx))), + }, + + // Before Unbond Distribution Hook + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_DELEGATOR_STARTING_INFO, + IdentifierTemplate: string(distributiontypes.GetDelegatorStartingInfoKey(srcValidatorAddr, delegateAddr)), + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_DELEGATOR_STARTING_INFO, + IdentifierTemplate: string(distributiontypes.GetDelegatorStartingInfoKey(dstValidatorAddr, delegateAddr)), + }, + + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_VAL_CURRENT_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorCurrentRewardsKey(srcValidatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_VAL_CURRENT_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorCurrentRewardsKey(srcValidatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_VAL_CURRENT_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorCurrentRewardsKey(dstValidatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_VAL_CURRENT_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorCurrentRewardsKey(dstValidatorAddr)), + }, + + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_OUTSTANDING_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorOutstandingRewardsKey(srcValidatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_OUTSTANDING_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorOutstandingRewardsKey(srcValidatorAddr)), + }, + + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_FEE_POOL, + IdentifierTemplate: string(distributiontypes.FeePoolKey), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_FEE_POOL, + IdentifierTemplate: string(distributiontypes.FeePoolKey), + }, + + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_VAL_HISTORICAL_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorHistoricalRewardsPrefix(srcValidatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_VAL_HISTORICAL_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorHistoricalRewardsPrefix(srcValidatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_VAL_HISTORICAL_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorHistoricalRewardsPrefix(dstValidatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_VAL_HISTORICAL_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorHistoricalRewardsPrefix(dstValidatorAddr)), + }, + + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_REDELEGATION_QUEUE, + IdentifierTemplate: string(stakingtypes.RedelegationQueueKey), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_REDELEGATION_QUEUE, + IdentifierTemplate: string(stakingtypes.RedelegationQueueKey), + }, + + // Gets Module Account information + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.AddressStoreKey(bondedModuleAdr)), + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.AddressStoreKey(notBondedModuleAdr)), + }, + + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_DELEGATOR_STARTING_INFO, + IdentifierTemplate: string(distributiontypes.GetDelegatorStartingInfoKey(srcValidatorAddr, delegateAddr)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_DELEGATOR_STARTING_INFO, + IdentifierTemplate: string(distributiontypes.GetDelegatorStartingInfoKey(dstValidatorAddr, delegateAddr)), + }, + + // Update the delegator and validator account balances + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: dstValidatorBalanceKey, + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: dstValidatorBalanceKey, + }, + + // Checks if the validators exchange rate is valid + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_REDELEGATION_VAL_SRC, + IdentifierTemplate: string(stakingtypes.GetREDByValSrcIndexKey( + delegateAddr, + srcValidatorAddr, + dstValidatorAddr, + )), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_REDELEGATION_VAL_DST, + IdentifierTemplate: string(stakingtypes.GetREDByValDstIndexKey( + delegateAddr, + srcValidatorAddr, + dstValidatorAddr, + )), + }, + + // Checks if the validators exchange rate is valid + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATOR, + IdentifierTemplate: srcValidatorKey, + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATOR, + IdentifierTemplate: dstValidatorKey, + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATOR, + IdentifierTemplate: srcValidatorKey, + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATOR, + IdentifierTemplate: dstValidatorKey, + }, + + // Last Operation should always be a commit + *acltypes.CommitAccessOp(), + } + return accessOperations, nil +} diff --git a/aclmapping/staking/mappings_test.go b/aclmapping/staking/mappings_test.go new file mode 100644 index 0000000000..e1f9f64e74 --- /dev/null +++ b/aclmapping/staking/mappings_test.go @@ -0,0 +1,342 @@ +package aclstakingmapping_test + +import ( + "fmt" + "testing" + "time" + + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + "github.com/cosmos/cosmos-sdk/x/staking/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + stakingacl "github.com/sei-protocol/sei-chain/aclmapping/staking" + aclutils "github.com/sei-protocol/sei-chain/aclmapping/utils" + "github.com/sei-protocol/sei-chain/app/apptesting" + oracletypes "github.com/sei-protocol/sei-chain/x/oracle/types" + + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + "github.com/sei-protocol/sei-chain/app" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type KeeperTestSuite struct { + apptesting.KeeperTestHelper + + queryClient stakingtypes.QueryClient + msgServer stakingtypes.MsgServer + // defaultDenom is on the suite, as it depends on the creator test address. + defaultDenom string + defaultExchangeRate string + initalBalance sdk.Coins + + validator sdk.ValAddress + newValidator sdk.ValAddress + delegateMsg *types.MsgDelegate + undelegateMsg *types.MsgUndelegate + redelegateMsg *types.MsgBeginRedelegate +} + +func TestKeeperTestSuite(t *testing.T) { + suite.Run(t, new(KeeperTestSuite)) +} + +// Runs before each test case +func (suite *KeeperTestSuite) SetupTest() { + suite.Setup() +} + +// Explicitly only run once during setup +func (suite *KeeperTestSuite) PrepareTest() { + suite.defaultDenom = "usei" + suite.defaultExchangeRate = fmt.Sprintf("%dusei", sdk.NewDec(1700)) + + suite.initalBalance = sdk.Coins{sdk.NewInt64Coin(suite.defaultDenom, 100000000000)} + suite.initalBalance = sdk.Coins{sdk.NewInt64Coin("stake", 100000000000)} + suite.FundAcc(suite.TestAccs[0], suite.initalBalance) + + suite.queryClient = stakingtypes.NewQueryClient(suite.QueryHelper) + suite.msgServer = stakingkeeper.NewMsgServerImpl(suite.App.StakingKeeper) + + msgValidator := sdkacltypes.NewMsgValidator(aclutils.StoreKeyToResourceTypePrefixMap) + suite.Ctx = suite.Ctx.WithMsgValidator(msgValidator) + + suite.Ctx = suite.Ctx.WithBlockHeight(10) + suite.Ctx = suite.Ctx.WithBlockTime(time.Unix(333, 0)) + suite.validator = suite.SetupValidator(stakingtypes.Bonded) + suite.newValidator = suite.SetupValidator(stakingtypes.Unbonded) + + notBondedPool := suite.App.StakingKeeper.GetNotBondedPool(suite.Ctx) + suite.App.AccountKeeper.SetModuleAccount(suite.Ctx, notBondedPool) + + validator, _ := suite.App.StakingKeeper.GetValidator(suite.Ctx, suite.validator) + suite.App.StakingKeeper.SetValidatorByConsAddr(suite.Ctx, validator) + + newValidator, _ := suite.App.StakingKeeper.GetValidator(suite.Ctx, suite.newValidator) + suite.App.StakingKeeper.SetValidatorByConsAddr(suite.Ctx, newValidator) + + valTokens := suite.App.StakingKeeper.TokensFromConsensusPower(suite.Ctx, 10) + validator, issuedShares := validator.AddTokensFromDel(valTokens) + validator = keeper.TestingUpdateValidator(suite.App.StakingKeeper, suite.Ctx, validator, true) + + newValTokens := suite.App.StakingKeeper.TokensFromConsensusPower(suite.Ctx, 10) + newValidator, newIssuedShares := newValidator.AddTokensFromDel(newValTokens) + newValidator = keeper.TestingUpdateValidator(suite.App.StakingKeeper, suite.Ctx, validator, true) + + val0AccAddr := sdk.AccAddress(suite.validator) + val1AccAddr := sdk.AccAddress(suite.newValidator) + + val0selfDelegation := types.NewDelegation(val0AccAddr, suite.validator, issuedShares) + val1SelfDelegation := types.NewDelegation(val1AccAddr, suite.validator, newIssuedShares) + + suite.App.StakingKeeper.SetDelegation(suite.Ctx, val0selfDelegation) + suite.App.StakingKeeper.SetDelegation(suite.Ctx, val1SelfDelegation) + + bondedPool := suite.App.StakingKeeper.GetBondedPool(suite.Ctx) + suite.App.AccountKeeper.SetModuleAccount(suite.Ctx, bondedPool) + + suite.delegateMsg = &stakingtypes.MsgDelegate{ + Amount: sdk.NewInt64Coin("stake", 10), + ValidatorAddress: suite.validator.String(), + DelegatorAddress: suite.TestAccs[0].String(), + } + suite.undelegateMsg = &stakingtypes.MsgUndelegate{ + Amount: sdk.NewInt64Coin("stake", 10), + ValidatorAddress: suite.validator.String(), + DelegatorAddress: suite.TestAccs[0].String(), + } + suite.redelegateMsg = &stakingtypes.MsgBeginRedelegate{ + Amount: sdk.NewInt64Coin("stake", 10), + ValidatorSrcAddress: suite.validator.String(), + ValidatorDstAddress: suite.newValidator.String(), + DelegatorAddress: suite.TestAccs[0].String(), + } + + _, err := suite.msgServer.Delegate( + sdk.WrapSDKContext(suite.Ctx), + suite.delegateMsg, + ) + if err != nil { + panic(err) + } +} + + +func (suite *KeeperTestSuite) TestMsgUndelegateDependencies() { + suite.PrepareTest() + tests := []struct { + name string + expectedError error + msg *stakingtypes.MsgUndelegate + dynamicDep bool + }{ + { + name: "default vote", + msg: suite.undelegateMsg, + expectedError: nil, + dynamicDep: true, + }, + { + name: "dont check synchronous", + msg: suite.undelegateMsg, + expectedError: nil, + dynamicDep: false, + }, + } + for _, tc := range tests { + suite.Run(fmt.Sprintf("Test Case: %s", tc.name), func() { + + handlerCtx, cms := aclutils.CacheTxContext(suite.Ctx) + _, err := suite.msgServer.Undelegate( + sdk.WrapSDKContext(handlerCtx), + tc.msg, + ) + depdenencies , _ := stakingacl.MsgUndelegateDependencyGenerator( + suite.App.AccessControlKeeper, + handlerCtx, + tc.msg, + ) + + if !tc.dynamicDep { + depdenencies = sdkacltypes.SynchronousAccessOps() + } + + if tc.expectedError != nil { + suite.Require().EqualError(err, tc.expectedError.Error()) + } else { + suite.Require().NoError(err) + } + + missing := handlerCtx.MsgValidator().ValidateAccessOperations(depdenencies, cms.GetEvents()) + suite.Require().Empty(missing) + }) + } +} + +func (suite *KeeperTestSuite) TestMsgRedelegateDependencies() { + suite.PrepareTest() + tests := []struct { + name string + expectedError error + msg *stakingtypes.MsgBeginRedelegate + dynamicDep bool + }{ + { + name: "default vote", + msg: suite.redelegateMsg, + expectedError: nil, + dynamicDep: true, + }, + // { + // name: "dont check synchronous", + // msg: suite.redelegateMsg, + // expectedError: nil, + // dynamicDep: false, + // }, + } + for _, tc := range tests { + suite.Run(fmt.Sprintf("Test Case: %s", tc.name), func() { + + handlerCtx, cms := aclutils.CacheTxContext(suite.Ctx) + _, err := suite.msgServer.BeginRedelegate( + sdk.WrapSDKContext(handlerCtx), + tc.msg, + ) + depdenencies , _ := stakingacl.MsgBeginRedelegateDependencyGenerator( + suite.App.AccessControlKeeper, + handlerCtx, + tc.msg, + ) + + if !tc.dynamicDep { + depdenencies = sdkacltypes.SynchronousAccessOps() + } + + if tc.expectedError != nil { + suite.Require().EqualError(err, tc.expectedError.Error()) + } else { + suite.Require().NoError(err) + } + + missing := handlerCtx.MsgValidator().ValidateAccessOperations(depdenencies, cms.GetEvents()) + suite.Require().Empty(missing) + }) + } +} + + +func (suite *KeeperTestSuite) TestMsgDelegateDependencies() { + suite.PrepareTest() + tests := []struct { + name string + expectedError error + msg *stakingtypes.MsgDelegate + dynamicDep bool + }{ + { + name: "default vote", + msg: suite.delegateMsg, + expectedError: nil, + dynamicDep: true, + }, + { + name: "dont check synchronous", + msg: suite.delegateMsg, + expectedError: nil, + dynamicDep: false, + }, + } + for _, tc := range tests { + suite.Run(fmt.Sprintf("Test Case: %s", tc.name), func() { + handlerCtx, cms := aclutils.CacheTxContext(suite.Ctx) + _, err := suite.msgServer.Delegate( + sdk.WrapSDKContext(handlerCtx), + tc.msg, + ) + depdenencies , _ := stakingacl.MsgDelegateDependencyGenerator( + suite.App.AccessControlKeeper, + handlerCtx, + tc.msg, + ) + + if !tc.dynamicDep { + depdenencies = sdkacltypes.SynchronousAccessOps() + } + + if tc.expectedError != nil { + suite.Require().EqualError(err, tc.expectedError.Error()) + } else { + suite.Require().NoError(err) + } + + missing := handlerCtx.MsgValidator().ValidateAccessOperations(depdenencies, cms.GetEvents()) + suite.Require().Empty(missing) + }) + } +} + +func TestGeneratorInvalidMessageTypes(t *testing.T) { + tm := time.Now().UTC() + valPub := secp256k1.GenPrivKey().PubKey() + testWrapper := app.NewTestWrapper(t, tm, valPub) + + stakingDelegate := stakingtypes.MsgDelegate{ + DelegatorAddress: "delegator", + ValidatorAddress: "validator", + Amount: sdk.Coin{Denom: "usei", Amount: sdk.NewInt(5)}, + } + oracleVote := oracletypes.MsgAggregateExchangeRateVote{ + ExchangeRates: "1usei", + Feeder: "test", + Validator: "validator", + } + + _, err := stakingacl.MsgUndelegateDependencyGenerator(testWrapper.App.AccessControlKeeper, testWrapper.Ctx, &oracleVote) + require.Error(t, err) + _, err = stakingacl.MsgUndelegateDependencyGenerator(testWrapper.App.AccessControlKeeper, testWrapper.Ctx, &stakingDelegate) + require.Error(t, err) + _, err = stakingacl.MsgUndelegateDependencyGenerator(testWrapper.App.AccessControlKeeper, testWrapper.Ctx, &stakingDelegate) + require.Error(t, err) + +} + +func (suite *KeeperTestSuite) TestMsgDelegateGenerator() { + suite.PrepareTest() + stakingDelegate := suite.delegateMsg + + accessOps, err := stakingacl.MsgDelegateDependencyGenerator( + suite.App.AccessControlKeeper, + suite.Ctx, + stakingDelegate, + ) + require.NoError(suite.T(), err) + err = acltypes.ValidateAccessOps(accessOps) + require.NoError(suite.T(), err) +} + +func (suite *KeeperTestSuite) TestMsgUndelegateGenerator() { + suite.PrepareTest() + accessOps, err := stakingacl.MsgUndelegateDependencyGenerator( + suite.App.AccessControlKeeper, + suite.Ctx, + suite.undelegateMsg, + ) + require.NoError(suite.T(), err) + err = acltypes.ValidateAccessOps(accessOps) + require.NoError(suite.T(), err) +} + +func (suite *KeeperTestSuite) TestMsgBeginRedelegateGenerator() { + suite.PrepareTest() + accessOps, err := stakingacl.MsgBeginRedelegateDependencyGenerator( + suite.App.AccessControlKeeper, + suite.Ctx, + suite.redelegateMsg, + ) + require.NoError(suite.T(), err) + err = acltypes.ValidateAccessOps(accessOps) + require.NoError(suite.T(), err) +} diff --git a/aclmapping/tokenfactory/mappings.go b/aclmapping/tokenfactory/mappings.go new file mode 100644 index 0000000000..1a48909742 --- /dev/null +++ b/aclmapping/tokenfactory/mappings.go @@ -0,0 +1,200 @@ +package aclTokenFactorymapping + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + tfktypes "github.com/sei-protocol/sei-chain/x/tokenfactory/types" +) + +var ErrInvalidMessageType = fmt.Errorf("invalid message received for TokenFactory Module") + +func GetTokenFactoryDependencyGenerators() aclkeeper.DependencyGeneratorMap { + dependencyGeneratorMap := make(aclkeeper.DependencyGeneratorMap) + MintMsgKey := acltypes.GenerateMessageKey(&tfktypes.MsgMint{}) + dependencyGeneratorMap[MintMsgKey] = TokenFactoryMintDependencyGenerator + + BurnMsgKey := acltypes.GenerateMessageKey(&tfktypes.MsgBurn{}) + dependencyGeneratorMap[BurnMsgKey] = TokenFactoryBurnDependencyGenerator + + return dependencyGeneratorMap +} + +func TokenFactoryMintDependencyGenerator(keeper aclkeeper.Keeper, ctx sdk.Context, msg sdk.Msg) ([]sdkacltypes.AccessOperation, error) { + mintMsg, ok := msg.(*tfktypes.MsgMint) + if !ok { + return []sdkacltypes.AccessOperation{}, ErrInvalidMessageType + } + moduleAdr := keeper.AccountKeeper.GetModuleAddress(tfktypes.ModuleName) + denom := mintMsg.GetAmount().Denom + + denomMetaDataKey := append([]byte(tfktypes.DenomAuthorityMetadataKey), []byte(denom)...) + tokenfactoryDenomKey := tfktypes.GetDenomPrefixStore(denom) + bankDenomMetaDataKey := banktypes.DenomMetadataKey(denom) + supplyKey := string(append(banktypes.SupplyKey, []byte(denom)...)) + + return []sdkacltypes.AccessOperation{ + // Reads denom data From BankKeeper + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_BANK_DENOM, + IdentifierTemplate: string(bankDenomMetaDataKey), + }, + + // Gets Authoritity data related to the denom + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_TOKENFACTORY_METADATA, + IdentifierTemplate: string(denomMetaDataKey), + }, + + // Gets Authoritity data related to the denom + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_TOKENFACTORY_DENOM, + IdentifierTemplate: string(tokenfactoryDenomKey), + }, + + // Gets Module Account information + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.AddressStoreKey(moduleAdr)), + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: string(banktypes.CreateAccountBalancesPrefix(moduleAdr)), + }, + + // Deposit into Sender's Bank Balance + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: string(banktypes.CreateAccountBalancesPrefixFromBech32(mintMsg.GetSender())), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: string(banktypes.CreateAccountBalancesPrefixFromBech32(mintMsg.GetSender())), + }, + + // Read and update supply after burn + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_BANK_SUPPLY, + IdentifierTemplate: supplyKey, + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_BANK_SUPPLY, + IdentifierTemplate: supplyKey, + }, + + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.CreateAddressStoreKeyFromBech32(mintMsg.GetSender())), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.CreateAddressStoreKeyFromBech32(mintMsg.GetSender())), + }, + + // Coins removed from Module account (Deferred) + + // Last Operation should always be a commit + *acltypes.CommitAccessOp(), + }, nil +} + +func TokenFactoryBurnDependencyGenerator(keeper aclkeeper.Keeper, ctx sdk.Context, msg sdk.Msg) ([]sdkacltypes.AccessOperation, error) { + burnMsg, ok := msg.(*tfktypes.MsgBurn) + if !ok { + return []sdkacltypes.AccessOperation{}, ErrInvalidMessageType + } + + moduleAdr := keeper.AccountKeeper.GetModuleAddress(tfktypes.ModuleName) + denom := burnMsg.GetAmount().Denom + + denomMetaDataKey := append([]byte(tfktypes.DenomAuthorityMetadataKey), []byte(denom)...) + tokenfactoryDenomKey := tfktypes.GetDenomPrefixStore(denom) + bankDenomMetaDataKey := banktypes.DenomMetadataKey(denom) + supplyKey := string(append(banktypes.SupplyKey, []byte(denom)...)) + return []sdkacltypes.AccessOperation{ + // Reads denom data From BankKeeper + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_BANK_DENOM, + IdentifierTemplate: string(bankDenomMetaDataKey), + }, + + // Gets Authoritity data related to the denom + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_TOKENFACTORY_METADATA, + IdentifierTemplate: string(denomMetaDataKey), + }, + + // Gets Authoritity data related to the denom + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_TOKENFACTORY_DENOM, + IdentifierTemplate: string(tokenfactoryDenomKey), + }, + + // Gets Module Account Balance + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.AddressStoreKey(moduleAdr)), + }, + + // Sends from Sender to Module account (deferred deposit) + // Checks balance for receiver + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: string(banktypes.CreateAccountBalancesPrefixFromBech32(burnMsg.GetSender())), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: string(banktypes.CreateAccountBalancesPrefixFromBech32(burnMsg.GetSender())), + }, + + // Read and update supply after burn + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_BANK_SUPPLY, + IdentifierTemplate: supplyKey, + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_BANK_SUPPLY, + IdentifierTemplate: supplyKey, + }, + + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.CreateAddressStoreKeyFromBech32(burnMsg.GetSender())), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.CreateAddressStoreKeyFromBech32(burnMsg.GetSender())), + }, + + // Coins removed from Module account (Deferred) + + // Last Operation should always be a commit + *acltypes.CommitAccessOp(), + }, nil +} diff --git a/aclmapping/tokenfactory/mappings_test.go b/aclmapping/tokenfactory/mappings_test.go new file mode 100644 index 0000000000..70792a373d --- /dev/null +++ b/aclmapping/tokenfactory/mappings_test.go @@ -0,0 +1,234 @@ +package aclTokenFactorymapping_test + +import ( + "fmt" + "testing" + + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + "github.com/cosmos/cosmos-sdk/simapp" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + tkfactory "github.com/sei-protocol/sei-chain/aclmapping/tokenfactory" + aclutils "github.com/sei-protocol/sei-chain/aclmapping/utils" + "github.com/sei-protocol/sei-chain/app/apptesting" + oracletypes "github.com/sei-protocol/sei-chain/x/oracle/types" + tokenfactorykeeper "github.com/sei-protocol/sei-chain/x/tokenfactory/keeper" + "github.com/sei-protocol/sei-chain/x/tokenfactory/types" + tokenfactorytypes "github.com/sei-protocol/sei-chain/x/tokenfactory/types" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" +) +type KeeperTestSuite struct { + apptesting.KeeperTestHelper + + queryClient tokenfactorytypes.QueryClient + msgServer tokenfactorytypes.MsgServer + // defaultDenom is on the suite, as it depends on the creator test address. + defaultDenom string + testDenom string + initalBalance sdk.Coins +} + +func TestKeeperTestSuite(t *testing.T) { + suite.Run(t, new(KeeperTestSuite)) +} + +// Runs before each test case +func (suite *KeeperTestSuite) SetupTest() { + suite.Setup() +} + +// Explicitly only run once during setup +func (suite *KeeperTestSuite) PrepareTest() { + suite.defaultDenom = "usei" + suite.testDenom = "foocoins" + + suite.initalBalance = sdk.Coins{sdk.NewInt64Coin(suite.defaultDenom, 100000000000)} + suite.FundAcc(suite.TestAccs[0], suite.initalBalance) + + suite.SetupTokenFactory() + suite.queryClient = tokenfactorytypes.NewQueryClient(suite.QueryHelper) + suite.msgServer = tokenfactorykeeper.NewMsgServerImpl(suite.App.TokenFactoryKeeper) + + res, err := suite.msgServer.CreateDenom( + sdk.WrapSDKContext(suite.Ctx), + types.NewMsgCreateDenom(suite.TestAccs[0].String(), suite.testDenom), + ) + suite.App.BankKeeper.WriteDeferredOperations(suite.Ctx) + + if err != nil { + panic(err) + } + suite.testDenom = res.GetNewTokenDenom() + + _, err = suite.msgServer.Mint( + sdk.WrapSDKContext(suite.Ctx), + types.NewMsgMint(suite.TestAccs[0].String(), sdk.NewInt64Coin(suite.testDenom, 1000000)), + ) + if err != nil { + panic(err) + } + suite.App.BankKeeper.WriteDeferredOperations(suite.Ctx) + + msgValidator := sdkacltypes.NewMsgValidator(aclutils.StoreKeyToResourceTypePrefixMap) + suite.Ctx = suite.Ctx.WithMsgValidator(msgValidator) +} + +func cacheTxContext(ctx sdk.Context) (sdk.Context, sdk.CacheMultiStore) { + ms := ctx.MultiStore() + msCache := ms.CacheMultiStore() + return ctx.WithMultiStore(msCache), msCache +} + +func (suite *KeeperTestSuite) TestMsgBurnDependencies() { + suite.PrepareTest() + + burnAmount := sdk.NewInt64Coin(suite.testDenom, 10) + addr1 := suite.TestAccs[0].String() + tests := []struct { + name string + expectedError error + msg *tokenfactorytypes.MsgBurn + dynamicDep bool + }{ + { + name: "default burn", + msg: tokenfactorytypes.NewMsgBurn(addr1, burnAmount), + expectedError: nil, + dynamicDep: true, + }, + { + name: "dont check synchronous", + msg: tokenfactorytypes.NewMsgBurn(addr1, burnAmount), + expectedError: nil, + dynamicDep: false, + }, + } + for _, tc := range tests { + suite.Run(fmt.Sprintf("Test Case: %s", tc.name), func() { + handlerCtx, cms := cacheTxContext(suite.Ctx) + _, err := suite.msgServer.Burn( + sdk.WrapSDKContext(handlerCtx), + tc.msg, + ) + suite.App.BankKeeper.WriteDeferredOperations(suite.Ctx) + + depdenencies , _ := tkfactory.TokenFactoryBurnDependencyGenerator( + suite.App.AccessControlKeeper, + handlerCtx, + tc.msg, + ) + + if !tc.dynamicDep { + depdenencies = sdkacltypes.SynchronousAccessOps() + } + + if tc.expectedError != nil { + suite.Require().EqualError(err, tc.expectedError.Error()) + } else { + suite.Require().NoError(err) + } + + missing := handlerCtx.MsgValidator().ValidateAccessOperations(depdenencies, cms.GetEvents()) + suite.Require().Empty(missing) + }) + } +} + +func (suite *KeeperTestSuite) TestMsgMintDependencies() { + suite.PrepareTest() + + burnAmount := sdk.NewInt64Coin(suite.testDenom, 10) + addr1 := suite.TestAccs[0].String() + tests := []struct { + name string + expectedError error + msg *tokenfactorytypes.MsgMint + dynamicDep bool + }{ + { + name: "default mint", + msg: tokenfactorytypes.NewMsgMint(addr1, burnAmount), + expectedError: nil, + dynamicDep: true, + }, + { + name: "dont check synchronous", + msg: tokenfactorytypes.NewMsgMint(addr1, burnAmount), + expectedError: nil, + dynamicDep: false, + }, + } + for _, tc := range tests { + suite.Run(fmt.Sprintf("Test Case: %s", tc.name), func() { + handlerCtx, cms := cacheTxContext(suite.Ctx) + _, err := suite.msgServer.Mint( + sdk.WrapSDKContext(handlerCtx), + tc.msg, + ) + suite.App.BankKeeper.WriteDeferredOperations(handlerCtx) + + depdenencies , _ := tkfactory.TokenFactoryMintDependencyGenerator( + suite.App.AccessControlKeeper, + handlerCtx, + tc.msg, + ) + + if !tc.dynamicDep { + depdenencies = sdkacltypes.SynchronousAccessOps() + } + + if tc.expectedError != nil { + suite.Require().EqualError(err, tc.expectedError.Error()) + } else { + suite.Require().NoError(err) + } + + missing := handlerCtx.MsgValidator().ValidateAccessOperations(depdenencies, cms.GetEvents()) + suite.Require().Empty(missing) + }) + } +} + +func TestGeneratorInvalidMessageTypes(t *testing.T) { + accs := authtypes.GenesisAccounts{} + balances := []banktypes.Balance{} + + app := simapp.SetupWithGenesisAccounts(accs, balances...) + ctx := app.BaseApp.NewContext(false, tmproto.Header{}) + + oracleVote := oracletypes.MsgAggregateExchangeRateVote{ + ExchangeRates: "1usei", + Feeder: "test", + Validator: "validator", + } + + _, err := tkfactory.TokenFactoryBurnDependencyGenerator(app.AccessControlKeeper, ctx, &oracleVote) + require.Error(t, err) +} + +func TestMsgBeginBurnDepedencyGenerator(t *testing.T) { + priv1 := secp256k1.GenPrivKey() + addr1 := sdk.AccAddress(priv1.PubKey().Address()) + + accs := authtypes.GenesisAccounts{} + balances := []banktypes.Balance{} + + app := simapp.SetupWithGenesisAccounts(accs, balances...) + ctx := app.BaseApp.NewContext(false, tmproto.Header{}) + + sendMsg := tokenfactorytypes.MsgBurn{ + Sender: addr1.String(), + Amount: sdk.NewInt64Coin("usei", 10), + } + + accessOps, err := tkfactory.TokenFactoryBurnDependencyGenerator(app.AccessControlKeeper, ctx, &sendMsg) + require.NoError(t, err) + err = acltypes.ValidateAccessOps(accessOps) + require.NoError(t, err) +} diff --git a/aclmapping/utils/identifier_templates.go b/aclmapping/utils/identifier_templates.go new file mode 100644 index 0000000000..effbc00445 --- /dev/null +++ b/aclmapping/utils/identifier_templates.go @@ -0,0 +1,41 @@ +package utils + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" +) + +const ( + ACCOUNT = "acc" + BANK = "bank" + AUTH = "auth" + STAKING = "staking" + TOKENFACTORY = "tokenfactory" + DEX = "dex" + DefaultIDTemplate = "*" +) + +func GetIdentifierTemplatePerModule(module string, identifier string) string { + return fmt.Sprintf("%s/%s", module, identifier) +} + +func GetPrefixedIdentifierTemplatePerModule(module string, identifier string, prefix string) string { + return fmt.Sprintf("%s/%s/%s", module, prefix, identifier) +} + +func GetOracleReadAccessOpsForValAndFeeder(feederAddr sdk.Address, valAddr sdk.Address) []sdkacltypes.AccessOperation { + return []sdkacltypes.AccessOperation{ + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_ORACLE, + IdentifierTemplate: feederAddr.String(), + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_ORACLE, + IdentifierTemplate: valAddr.String(), + }, + } +} diff --git a/aclmapping/utils/resource_type.go b/aclmapping/utils/resource_type.go new file mode 100644 index 0000000000..7e90975e59 --- /dev/null +++ b/aclmapping/utils/resource_type.go @@ -0,0 +1,106 @@ +package utils + +import ( + aclsdktypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + dextypes "github.com/sei-protocol/sei-chain/x/dex/types" + epochtypes "github.com/sei-protocol/sei-chain/x/epoch/types" + oracletypes "github.com/sei-protocol/sei-chain/x/oracle/types" + tokenfactorytypes "github.com/sei-protocol/sei-chain/x/tokenfactory/types" +) + +var StoreKeyToResourceTypePrefixMap = aclsdktypes.StoreKeyToResourceTypePrefixMap{ + aclsdktypes.ParentNodeKey: { + aclsdktypes.ResourceType_ANY: aclsdktypes.EmptyPrefix, + aclsdktypes.ResourceType_KV: aclsdktypes.EmptyPrefix, + aclsdktypes.ResourceType_Mem: aclsdktypes.EmptyPrefix, + aclsdktypes.ResourceType_KV_WASM: aclsdktypes.EmptyPrefix, + }, + dextypes.StoreKey: { + aclsdktypes.ResourceType_KV_DEX: aclsdktypes.EmptyPrefix, + aclsdktypes.ResourceType_DexMem: aclsdktypes.EmptyPrefix, + aclsdktypes.ResourceType_KV_DEX_CONTRACT_LONGBOOK: dextypes.KeyPrefix(dextypes.LongBookKey), + aclsdktypes.ResourceType_KV_DEX_CONTRACT_SHORTBOOK: dextypes.KeyPrefix(dextypes.ShortBookKey), + // pricedenom and assetdenoms are the prefixes + aclsdktypes.ResourceType_KV_DEX_PAIR_PREFIX: aclsdktypes.EmptyPrefix, + aclsdktypes.ResourceType_KV_DEX_TWAP: dextypes.KeyPrefix(dextypes.TwapKey), + aclsdktypes.ResourceType_KV_DEX_PRICE: dextypes.KeyPrefix(dextypes.PriceKey), + aclsdktypes.ResourceType_KV_DEX_SETTLEMENT_ENTRY: dextypes.KeyPrefix(dextypes.SettlementEntryKey), + aclsdktypes.ResourceType_KV_DEX_REGISTERED_PAIR: dextypes.KeyPrefix(dextypes.RegisteredPairKey), + aclsdktypes.ResourceType_KV_DEX_TICK_SIZE: dextypes.KeyPrefix(dextypes.TickSizeKey), + aclsdktypes.ResourceType_KV_DEX_ORDER: dextypes.KeyPrefix(dextypes.OrderKey), + aclsdktypes.ResourceType_KV_DEX_CANCEL: dextypes.KeyPrefix(dextypes.CancelKey), + aclsdktypes.ResourceType_KV_DEX_ACCOUNT_ACTIVE_ORDERS: dextypes.KeyPrefix(dextypes.AccountActiveOrdersKey), + aclsdktypes.ResourceType_KV_DEX_REGISTERED_PAIR_COUNT: dextypes.KeyPrefix(dextypes.RegisteredPairCount), + aclsdktypes.ResourceType_KV_DEX_ASSET_LIST: dextypes.KeyPrefix(dextypes.AssetListKey), + aclsdktypes.ResourceType_KV_DEX_NEXT_ORDER_ID: dextypes.KeyPrefix(dextypes.NextOrderIDKey), + aclsdktypes.ResourceType_KV_DEX_NEXT_SETTLEMENT_ID: dextypes.KeyPrefix(dextypes.NextSettlementIDKey), + aclsdktypes.ResourceType_KV_DEX_MATCH_RESULT: dextypes.KeyPrefix(dextypes.MatchResultKey), + aclsdktypes.ResourceType_KV_DEX_ORDER_BOOK: dextypes.KeyPrefix(dextypes.NextOrderIDKey), + // SETTLEMENT keys are prefixed with account and order id + aclsdktypes.ResourceType_KV_DEX_SETTLEMENT_ORDER_ID: aclsdktypes.EmptyPrefix, + aclsdktypes.ResourceType_KV_DEX_SETTLEMENT: aclsdktypes.EmptyPrefix, + }, + banktypes.StoreKey: { + aclsdktypes.ResourceType_KV_BANK: aclsdktypes.EmptyPrefix, + aclsdktypes.ResourceType_KV_BANK_BALANCES: banktypes.BalancesPrefix, + aclsdktypes.ResourceType_KV_BANK_SUPPLY: banktypes.SupplyKey, + aclsdktypes.ResourceType_KV_BANK_DENOM: banktypes.DenomMetadataPrefix, + }, + authtypes.StoreKey: { + aclsdktypes.ResourceType_KV_AUTH: aclsdktypes.EmptyPrefix, + aclsdktypes.ResourceType_KV_AUTH_ADDRESS_STORE: authtypes.AddressStoreKeyPrefix, + }, + distributiontypes.StoreKey: { + aclsdktypes.ResourceType_KV_DISTRIBUTION: aclsdktypes.EmptyPrefix, + aclsdktypes.ResourceType_KV_DISTRIBUTION_FEE_POOL: distributiontypes.FeePoolKey, + aclsdktypes.ResourceType_KV_DISTRIBUTION_PROPOSER_KEY: distributiontypes.ProposerKey, + aclsdktypes.ResourceType_KV_DISTRIBUTION_OUTSTANDING_REWARDS: distributiontypes.ValidatorOutstandingRewardsPrefix, + aclsdktypes.ResourceType_KV_DISTRIBUTION_DELEGATOR_WITHDRAW_ADDR: distributiontypes.DelegatorWithdrawAddrPrefix, + aclsdktypes.ResourceType_KV_DISTRIBUTION_DELEGATOR_STARTING_INFO: distributiontypes.DelegatorStartingInfoPrefix, + aclsdktypes.ResourceType_KV_DISTRIBUTION_VAL_HISTORICAL_REWARDS: distributiontypes.ValidatorHistoricalRewardsPrefix, + aclsdktypes.ResourceType_KV_DISTRIBUTION_VAL_CURRENT_REWARDS: distributiontypes.ValidatorCurrentRewardsPrefix, + aclsdktypes.ResourceType_KV_DISTRIBUTION_VAL_ACCUM_COMMISSION: distributiontypes.ValidatorAccumulatedCommissionPrefix, + aclsdktypes.ResourceType_KV_DISTRIBUTION_SLASH_EVENT: distributiontypes.ValidatorSlashEventPrefix, + }, + oracletypes.StoreKey: { + aclsdktypes.ResourceType_KV_ORACLE: aclsdktypes.EmptyPrefix, + aclsdktypes.ResourceType_KV_ORACLE_VOTE_TARGETS: oracletypes.VoteTargetKey, + aclsdktypes.ResourceType_KV_ORACLE_AGGREGATE_VOTES: oracletypes.AggregateExchangeRateVoteKey, + aclsdktypes.ResourceType_KV_ORACLE_FEEDERS: oracletypes.FeederDelegationKey, + aclsdktypes.ResourceType_KV_ORACLE_PRICE_SNAPSHOT: oracletypes.PriceSnapshotKey, + aclsdktypes.ResourceType_KV_ORACLE_EXCHANGE_RATE: oracletypes.ExchangeRateKey, + aclsdktypes.ResourceType_KV_ORACLE_VOTE_PENALTY_COUNTER: oracletypes.VotePenaltyCounterKey, + }, + stakingtypes.StoreKey: { + aclsdktypes.ResourceType_KV_STAKING: aclsdktypes.EmptyPrefix, + aclsdktypes.ResourceType_KV_STAKING_VALIDATION_POWER: stakingtypes.LastValidatorPowerKey, + aclsdktypes.ResourceType_KV_STAKING_TOTAL_POWER: stakingtypes.LastTotalPowerKey, + aclsdktypes.ResourceType_KV_STAKING_VALIDATOR: stakingtypes.ValidatorsKey, + aclsdktypes.ResourceType_KV_STAKING_VALIDATORS_CON_ADDR: stakingtypes.ValidatorsByConsAddrKey, + aclsdktypes.ResourceType_KV_STAKING_VALIDATORS_BY_POWER: stakingtypes.ValidatorsByPowerIndexKey, + aclsdktypes.ResourceType_KV_STAKING_DELEGATION: stakingtypes.DelegationKey, + aclsdktypes.ResourceType_KV_STAKING_UNBONDING_DELEGATION: stakingtypes.UnbondingDelegationKey, + aclsdktypes.ResourceType_KV_STAKING_UNBONDING_DELEGATION_VAL: stakingtypes.UnbondingDelegationByValIndexKey, + aclsdktypes.ResourceType_KV_STAKING_REDELEGATION: stakingtypes.RedelegationKey, + aclsdktypes.ResourceType_KV_STAKING_REDELEGATION_VAL_SRC: stakingtypes.RedelegationByValSrcIndexKey, + aclsdktypes.ResourceType_KV_STAKING_REDELEGATION_VAL_DST: stakingtypes.RedelegationByValDstIndexKey, + aclsdktypes.ResourceType_KV_STAKING_UNBONDING: stakingtypes.UnbondingQueueKey, + aclsdktypes.ResourceType_KV_STAKING_REDELEGATION_QUEUE: stakingtypes.RedelegationQueueKey, + aclsdktypes.ResourceType_KV_STAKING_VALIDATOR_QUEUE: stakingtypes.ValidatorQueueKey, + aclsdktypes.ResourceType_KV_STAKING_HISTORICAL_INFO: stakingtypes.HistoricalInfoKey, + }, + tokenfactorytypes.StoreKey: { + aclsdktypes.ResourceType_KV_TOKENFACTORY: aclsdktypes.EmptyPrefix, + aclsdktypes.ResourceType_KV_TOKENFACTORY_DENOM: []byte(tokenfactorytypes.DenomsPrefixKey), + aclsdktypes.ResourceType_KV_TOKENFACTORY_METADATA: []byte(tokenfactorytypes.DenomAuthorityMetadataKey), + aclsdktypes.ResourceType_KV_TOKENFACTORY_ADMIN: []byte(tokenfactorytypes.AdminPrefixKey), + aclsdktypes.ResourceType_KV_TOKENFACTORY_CREATOR: []byte(tokenfactorytypes.AdminPrefixKey), + }, + epochtypes.StoreKey: { + aclsdktypes.ResourceType_KV_EPOCH: aclsdktypes.EmptyPrefix, + }, +} diff --git a/aclmapping/utils/resource_type_test.go b/aclmapping/utils/resource_type_test.go new file mode 100644 index 0000000000..fffe91a44e --- /dev/null +++ b/aclmapping/utils/resource_type_test.go @@ -0,0 +1,29 @@ +package utils_test + +import ( + "fmt" + "testing" + + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + aclutils "github.com/sei-protocol/sei-chain/aclmapping/utils" +) + +func TestAllResourcesInTree(t *testing.T) { + storeKeyToResourceMap := aclutils.StoreKeyToResourceTypePrefixMap + resourceTree := sdkacltypes.ResourceTree + + storeKeyAllResourceTypes := make(map[sdkacltypes.ResourceType]bool) + for _, resourceTypeToPrefix := range storeKeyToResourceMap { + for resourceType := range resourceTypeToPrefix { + storeKeyAllResourceTypes[resourceType] = true + } + } + + for resourceType := range resourceTree { + if _, ok := storeKeyAllResourceTypes[resourceType]; !ok { + panic(fmt.Sprintf("Missing resourceType=%s in the storekey to resource type prefix mapping", resourceType)) + } + } + + +} diff --git a/aclmapping/utils/test_utils.go b/aclmapping/utils/test_utils.go new file mode 100644 index 0000000000..5b39275708 --- /dev/null +++ b/aclmapping/utils/test_utils.go @@ -0,0 +1,9 @@ +package utils + +import sdk "github.com/cosmos/cosmos-sdk/types" + +func CacheTxContext(ctx sdk.Context) (sdk.Context, sdk.CacheMultiStore) { + ms := ctx.MultiStore() + msCache := ms.CacheMultiStore() + return ctx.WithMultiStore(msCache), msCache +} diff --git a/aclmapping/wasm/mappings.go b/aclmapping/wasm/mappings.go new file mode 100644 index 0000000000..1559a41cc6 --- /dev/null +++ b/aclmapping/wasm/mappings.go @@ -0,0 +1,53 @@ +package aclwasmmapping + +import ( + "fmt" + + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + "github.com/sei-protocol/sei-chain/utils" +) + +var ( + ErrInvalidWasmExecuteMessage = fmt.Errorf("invalid message received for type WasmExecuteContract") + ErrInvalidWasmFunction = fmt.Errorf("unable to identify wasm function") + ErrWasmFunctionDependenciesDisabled = fmt.Errorf("wasm function dependency mapping disabled") +) + +type WasmDependencyGenerator struct{} + +func NewWasmDependencyGenerator() WasmDependencyGenerator { + return WasmDependencyGenerator{} +} + +func (wasmDepGen WasmDependencyGenerator) GetWasmDependencyGenerators() aclkeeper.DependencyGeneratorMap { + dependencyGeneratorMap := make(aclkeeper.DependencyGeneratorMap) + + // wasm execute + executeContractKey := acltypes.GenerateMessageKey(&wasmtypes.MsgExecuteContract{}) + dependencyGeneratorMap[executeContractKey] = wasmDepGen.WasmExecuteContractGenerator + + return dependencyGeneratorMap +} + +func (wasmDepGen WasmDependencyGenerator) WasmExecuteContractGenerator(keeper aclkeeper.Keeper, ctx sdk.Context, msg sdk.Msg) ([]sdkacltypes.AccessOperation, error) { + executeContractMsg, ok := msg.(*wasmtypes.MsgExecuteContract) + if !ok { + return []sdkacltypes.AccessOperation{}, ErrInvalidWasmExecuteMessage + } + contractAddr, err := sdk.AccAddressFromBech32(executeContractMsg.Contract) + if err != nil { + return []sdkacltypes.AccessOperation{}, err + } + wasmDependencyMapping, err := keeper.GetWasmDependencyMapping(ctx, contractAddr, executeContractMsg.Msg, true) + if err != nil { + return []sdkacltypes.AccessOperation{}, err + } + if !wasmDependencyMapping.Enabled { + return []sdkacltypes.AccessOperation{}, ErrWasmFunctionDependenciesDisabled + } + return utils.Map(wasmDependencyMapping.AccessOps, func(op sdkacltypes.AccessOperationWithSelector) sdkacltypes.AccessOperation { return *op.Operation }), nil +} diff --git a/app/abci.go b/app/abci.go index 35b2a84ae5..bcaf0de3d6 100644 --- a/app/abci.go +++ b/app/abci.go @@ -2,8 +2,10 @@ package app import ( "context" + "time" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/sei-protocol/sei-chain/utils/metrics" abci "github.com/tendermint/tendermint/abci/types" "go.opentelemetry.io/otel/attribute" ) @@ -31,11 +33,12 @@ func (app *App) CheckTx(ctx context.Context, req *abci.RequestCheckTx) (*abci.Re } func (app *App) DeliverTx(ctx sdk.Context, req abci.RequestDeliverTx) abci.ResponseDeliverTx { - // tracectx, span := (*app.tracingInfo.Tracer).Start(app.tracingInfo.TracerContext, "DeliverTx") - // oldCtx := app.tracingInfo.TracerContext - // app.tracingInfo.TracerContext = tracectx - // defer span.End() - // defer func() { app.tracingInfo.TracerContext = oldCtx }() + defer metrics.MeasureDeliverTxDuration(time.Now()) + tracectx, span := (*app.tracingInfo.Tracer).Start(app.tracingInfo.TracerContext, "DeliverTx") + oldCtx := app.tracingInfo.TracerContext + app.tracingInfo.TracerContext = tracectx + defer span.End() + defer func() { app.tracingInfo.TracerContext = oldCtx }() return app.BaseApp.DeliverTx(ctx, req) } diff --git a/app/ante.go b/app/ante.go index 74e28d85cc..194c9c85d6 100644 --- a/app/ante.go +++ b/app/ante.go @@ -5,10 +5,12 @@ import ( wasmTypes "github.com/CosmWasm/wasmd/x/wasm/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper" "github.com/cosmos/cosmos-sdk/x/auth/ante" ibcante "github.com/cosmos/ibc-go/v3/modules/core/ante" ibckeeper "github.com/cosmos/ibc-go/v3/modules/core/keeper" "github.com/sei-protocol/sei-chain/app/antedecorators" + "github.com/sei-protocol/sei-chain/app/antedecorators/depdecorators" "github.com/sei-protocol/sei-chain/utils/tracing" "github.com/sei-protocol/sei-chain/x/dex" dexkeeper "github.com/sei-protocol/sei-chain/x/dex/keeper" @@ -22,40 +24,41 @@ import ( type HandlerOptions struct { ante.HandlerOptions - IBCKeeper *ibckeeper.Keeper - WasmConfig *wasmTypes.WasmConfig - OracleKeeper *oraclekeeper.Keeper - DexKeeper *dexkeeper.Keeper - NitroKeeper *nitrokeeper.Keeper - TXCounterStoreKey sdk.StoreKey + IBCKeeper *ibckeeper.Keeper + WasmConfig *wasmTypes.WasmConfig + OracleKeeper *oraclekeeper.Keeper + DexKeeper *dexkeeper.Keeper + NitroKeeper *nitrokeeper.Keeper + AccessControlKeeper *aclkeeper.Keeper + TXCounterStoreKey sdk.StoreKey TracingInfo *tracing.Info } -func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { +func NewAnteHandlerAndDepGenerator(options HandlerOptions) (sdk.AnteHandler, sdk.AnteDepGenerator, error) { if options.AccountKeeper == nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "account keeper is required for AnteHandler") + return nil, nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "account keeper is required for AnteHandler") } if options.BankKeeper == nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "bank keeper is required for AnteHandler") + return nil, nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "bank keeper is required for AnteHandler") } if options.SignModeHandler == nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "sign mode handler is required for ante builder") + return nil, nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "sign mode handler is required for ante builder") } if options.WasmConfig == nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "wasm config is required for ante builder") - } - if options.TXCounterStoreKey == nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "tx counter key is required for ante builder") + return nil, nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "wasm config is required for ante builder") } if options.OracleKeeper == nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "oracle keeper is required for ante builder") + return nil, nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "oracle keeper is required for ante builder") } if options.NitroKeeper == nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "nitro keeper is required for ante builder") + return nil, nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "nitro keeper is required for ante builder") + } + if options.AccessControlKeeper == nil { + return nil, nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "accesscontrol keeper is required for ante builder") } if options.TracingInfo == nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "tracing info is required for ante builder") + return nil, nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "tracing info is required for ante builder") } sigGasConsumer := options.SigGasConsumer @@ -71,29 +74,31 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { // sigVerifyDecorator = ante.NewBatchSigVerificationDecorator(options.BatchVerifier, sequentialVerifyDecorator) // } - anteDecorators := []sdk.AnteDecorator{ - ante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first + anteDecorators := []sdk.AnteFullDecorator{ + sdk.DefaultWrappedAnteDecorator(ante.NewSetUpContextDecorator(antedecorators.GetGasMeterSetter(*options.AccessControlKeeper))), // outermost AnteDecorator. SetUpContext must be called first // TODO: have dex antehandler separate, and then call the individual antehandlers FROM the gasless antehandler decorator wrapper - wasmkeeper.NewLimitSimulationGasDecorator(options.WasmConfig.SimulationGasLimit), // after setup context to enforce limits early - wasmkeeper.NewCountTXDecorator(options.TXCounterStoreKey), - ante.NewRejectExtensionOptionsDecorator(), - oracle.NewSpammingPreventionDecorator(*options.OracleKeeper), - ante.NewValidateBasicDecorator(), - ante.NewTxTimeoutHeightDecorator(), - ante.NewValidateMemoDecorator(options.AccountKeeper), + sdk.DefaultWrappedAnteDecorator(antedecorators.NewGaslessDecorator([]sdk.AnteDecorator{}, *options.OracleKeeper, *options.NitroKeeper)), + sdk.DefaultWrappedAnteDecorator(wasmkeeper.NewLimitSimulationGasDecorator(options.WasmConfig.SimulationGasLimit)), // after setup context to enforce limits early + sdk.DefaultWrappedAnteDecorator(ante.NewRejectExtensionOptionsDecorator()), + sdk.DefaultWrappedAnteDecorator(oracle.NewSpammingPreventionDecorator(*options.OracleKeeper)), + sdk.DefaultWrappedAnteDecorator(ante.NewValidateBasicDecorator()), + sdk.DefaultWrappedAnteDecorator(ante.NewTxTimeoutHeightDecorator()), + sdk.DefaultWrappedAnteDecorator(ante.NewValidateMemoDecorator(options.AccountKeeper)), ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper), ante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper, options.TxFeeChecker), // PriorityDecorator must be called after DeductFeeDecorator which sets tx priority based on tx fees - antedecorators.NewPriorityDecorator(), + sdk.DefaultWrappedAnteDecorator(antedecorators.NewPriorityDecorator()), // SetPubKeyDecorator must be called before all signature verification decorators - ante.NewSetPubKeyDecorator(options.AccountKeeper), - ante.NewValidateSigCountDecorator(options.AccountKeeper), - ante.NewSigGasConsumeDecorator(options.AccountKeeper, sigGasConsumer), - sequentialVerifyDecorator, - ante.NewIncrementSequenceDecorator(options.AccountKeeper), - ibcante.NewAnteDecorator(options.IBCKeeper), - dex.NewTickSizeMultipleDecorator(*options.DexKeeper), + sdk.CustomDepWrappedAnteDecorator(ante.NewSetPubKeyDecorator(options.AccountKeeper), depdecorators.SignerDepDecorator{ReadOnly: false}), + sdk.DefaultWrappedAnteDecorator(ante.NewValidateSigCountDecorator(options.AccountKeeper)), + sdk.CustomDepWrappedAnteDecorator(ante.NewSigGasConsumeDecorator(options.AccountKeeper, sigGasConsumer), depdecorators.SignerDepDecorator{ReadOnly: true}), + sdk.CustomDepWrappedAnteDecorator(sequentialVerifyDecorator, depdecorators.SignerDepDecorator{ReadOnly: true}), + sdk.CustomDepWrappedAnteDecorator(ante.NewIncrementSequenceDecorator(options.AccountKeeper), depdecorators.SignerDepDecorator{ReadOnly: false}), + sdk.DefaultWrappedAnteDecorator(ibcante.NewAnteDecorator(options.IBCKeeper)), + sdk.DefaultWrappedAnteDecorator(dex.NewTickSizeMultipleDecorator(*options.DexKeeper)), } - return sdk.ChainAnteDecorators(anteDecorators...), nil + anteHandler, anteDepGenerator := sdk.ChainAnteDecorators(anteDecorators...) + + return anteHandler, anteDepGenerator, nil } diff --git a/app/ante_test.go b/app/ante_test.go new file mode 100644 index 0000000000..ef2b364e4c --- /dev/null +++ b/app/ante_test.go @@ -0,0 +1,209 @@ +package app_test + +import ( + "context" + "testing" + + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/tx" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/simapp" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + + "github.com/cosmos/cosmos-sdk/x/auth/ante" + xauthsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + aclutils "github.com/sei-protocol/sei-chain/aclmapping/utils" + app "github.com/sei-protocol/sei-chain/app" + "github.com/sei-protocol/sei-chain/app/apptesting" + "github.com/sei-protocol/sei-chain/utils/tracing" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "go.opentelemetry.io/otel" +) + +// AnteTestSuite is a test suite to be used with ante handler tests. +type AnteTestSuite struct { + apptesting.KeeperTestHelper + + anteHandler sdk.AnteHandler + anteDepGenerator sdk.AnteDepGenerator + clientCtx client.Context + txBuilder client.TxBuilder + testAcc sdk.AccAddress + testAccPriv cryptotypes.PrivKey +} + +func TestKeeperTestSuite(t *testing.T) { + suite.Run(t, new(AnteTestSuite)) +} + +// SetupTest setups a new test, with new app, context, and anteHandler. +func (suite *AnteTestSuite) SetupTest(isCheckTx bool) { + suite.Setup() + + // keys and addresses + suite.testAccPriv, _, suite.testAcc = testdata.KeyTestPubAddr() + initalBalance := sdk.Coins{sdk.NewInt64Coin("atom", 100000000000)} + suite.FundAcc(suite.testAcc, initalBalance) + + suite.Ctx = suite.Ctx.WithBlockHeight(1) + + msgValidator := sdkacltypes.NewMsgValidator(aclutils.StoreKeyToResourceTypePrefixMap) + suite.Ctx = suite.Ctx.WithMsgValidator(msgValidator) + + // Set up TxConfig. + encodingConfig := simapp.MakeTestEncodingConfig() + // We're using TestMsg encoding in some tests, so register it here. + encodingConfig.Amino.RegisterConcrete(&testdata.TestMsg{}, "testdata.TestMsg", nil) + testdata.RegisterInterfaces(encodingConfig.InterfaceRegistry) + + suite.clientCtx = client.Context{}. + WithTxConfig(encodingConfig.TxConfig) + + wasmConfig := wasmtypes.DefaultWasmConfig() + defaultTracer, _ := tracing.DefaultTracerProvider() + otel.SetTracerProvider(defaultTracer) + tr := defaultTracer.Tracer("component-main") + + antehandler, anteDepGenerator, err := app.NewAnteHandlerAndDepGenerator( + app.HandlerOptions{ + HandlerOptions: ante.HandlerOptions{ + AccountKeeper: suite.App.AccountKeeper, + BankKeeper: suite.App.BankKeeper, + FeegrantKeeper: suite.App.FeeGrantKeeper, + SignModeHandler: suite.clientCtx.TxConfig.SignModeHandler(), + SigGasConsumer: ante.DefaultSigVerificationGasConsumer, + // BatchVerifier: app.batchVerifier, + }, + IBCKeeper: suite.App.IBCKeeper, + WasmConfig: &wasmConfig, + OracleKeeper: &suite.App.OracleKeeper, + DexKeeper: &suite.App.DexKeeper, + NitroKeeper: &suite.App.NitroKeeper, + AccessControlKeeper: &suite.App.AccessControlKeeper, + TracingInfo: &tracing.Info{ + Tracer: &tr, + TracerContext: context.Background(), + }, + }, + ) + + suite.Require().NoError(err) + suite.anteHandler = antehandler + suite.anteDepGenerator = anteDepGenerator +} + +func (suite *AnteTestSuite) AnteHandlerValidateAccessOp(acessOps []sdkacltypes.AccessOperation) error { + for _, accessOp := range acessOps { + err := acltypes.ValidateAccessOp(accessOp) + if err != nil { + return err + } + } + return nil +} + +// CreateTestTx is a helper function to create a tx given multiple inputs. +func (suite *AnteTestSuite) CreateTestTx(privs []cryptotypes.PrivKey, accNums []uint64, accSeqs []uint64, chainID string) (xauthsigning.Tx, error) { + // First round: we gather all the signer infos. We use the "set empty + // signature" hack to do that. + var sigsV2 []signing.SignatureV2 + for i, priv := range privs { + sigV2 := signing.SignatureV2{ + PubKey: priv.PubKey(), + Data: &signing.SingleSignatureData{ + SignMode: suite.clientCtx.TxConfig.SignModeHandler().DefaultMode(), + Signature: nil, + }, + Sequence: accSeqs[i], + } + + sigsV2 = append(sigsV2, sigV2) + } + err := suite.txBuilder.SetSignatures(sigsV2...) + if err != nil { + return nil, err + } + + // Second round: all signer infos are set, so each signer can sign. + sigsV2 = []signing.SignatureV2{} + for i, priv := range privs { + signerData := xauthsigning.SignerData{ + ChainID: chainID, + AccountNumber: accNums[i], + Sequence: accSeqs[i], + } + sigV2, err := tx.SignWithPrivKey( + suite.clientCtx.TxConfig.SignModeHandler().DefaultMode(), signerData, + suite.txBuilder, priv, suite.clientCtx.TxConfig, accSeqs[i]) + if err != nil { + return nil, err + } + + sigsV2 = append(sigsV2, sigV2) + } + err = suite.txBuilder.SetSignatures(sigsV2...) + if err != nil { + return nil, err + } + + return suite.txBuilder.GetTx(), nil +} + +func (suite *AnteTestSuite) TestValidateDepedencies() { + suite.SetupTest(true) // setup + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + + // msg and signatures + msg := testdata.NewTestMsg(suite.testAcc) + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + suite.Require().NoError(suite.txBuilder.SetMsgs(msg)) + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{}, []uint64{}, []uint64{} + invalidTx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.Ctx.ChainID()) + suite.Require().NoError(err) + + _, err = suite.anteHandler(suite.Ctx, invalidTx, false) + + suite.Require().NotNil(err, "Did not error on invalid tx") + + privs, accNums, accSeqs = []cryptotypes.PrivKey{suite.testAccPriv}, []uint64{8}, []uint64{0} + + handlerCtx, cms := aclutils.CacheTxContext(suite.Ctx) + validTx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.Ctx.ChainID()) + + suite.Require().NoError(err) + depdenencies, _ := suite.anteDepGenerator([]sdkacltypes.AccessOperation{}, validTx) + _, err = suite.anteHandler(handlerCtx, validTx, false) + suite.Require().Nil(err, "ValidateBasicDecorator returned error on valid tx. err: %v", err) + err = suite.AnteHandlerValidateAccessOp(depdenencies) + + require.NoError(suite.T(), err) + + missing := handlerCtx.MsgValidator().ValidateAccessOperations(depdenencies, cms.GetEvents()) + suite.Require().Empty(missing) + + // test decorator skips on recheck + suite.Ctx = suite.Ctx.WithIsReCheckTx(true) + + // decorator should skip processing invalidTx on recheck and thus return nil-error + handlerCtx, cms = aclutils.CacheTxContext(suite.Ctx) + depdenencies, _ = suite.anteDepGenerator([]sdkacltypes.AccessOperation{}, invalidTx) + _, err = suite.anteHandler(handlerCtx, invalidTx, false) + missing = handlerCtx.MsgValidator().ValidateAccessOperations(depdenencies, cms.GetEvents()) + + err = suite.AnteHandlerValidateAccessOp(depdenencies) + require.NoError(suite.T(), err) + + suite.Require().Empty(missing) + + suite.Require().Nil(err, "ValidateBasicDecorator ran on ReCheck") +} diff --git a/app/antedecorators/depdecorators/signers.go b/app/antedecorators/depdecorators/signers.go new file mode 100644 index 0000000000..9d8043c3b8 --- /dev/null +++ b/app/antedecorators/depdecorators/signers.go @@ -0,0 +1,34 @@ +package depdecorators + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +type SignerDepDecorator struct { + ReadOnly bool +} + +func (d SignerDepDecorator) AnteDeps(txDeps []sdkacltypes.AccessOperation, tx sdk.Tx, next sdk.AnteDepGenerator) (newTxDeps []sdkacltypes.AccessOperation, err error) { + sigTx, ok := tx.(authsigning.SigVerifiableTx) + if !ok { + return txDeps, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid tx type") + } + var accessType sdkacltypes.AccessType + if d.ReadOnly { + accessType = sdkacltypes.AccessType_READ + } else { + accessType = sdkacltypes.AccessType_WRITE + } + for _, signer := range sigTx.GetSigners() { + txDeps = append(txDeps, sdkacltypes.AccessOperation{ + AccessType: accessType, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.AddressStoreKey(signer)), + }) + } + return next(txDeps, tx) +} diff --git a/app/antedecorators/gas.go b/app/antedecorators/gas.go new file mode 100644 index 0000000000..d5be80fd12 --- /dev/null +++ b/app/antedecorators/gas.go @@ -0,0 +1,53 @@ +package antedecorators + +import ( + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper" +) + +const ( + GasMultiplierNumerator uint64 = 1 + DefaultGasMultiplierDenominator uint64 = 1 + WasmCorrectDependencyDiscountDenominator uint64 = 2 +) + +func GetGasMeterSetter(aclkeeper aclkeeper.Keeper) func(bool, sdk.Context, uint64, sdk.Tx) sdk.Context { + return func(simulate bool, ctx sdk.Context, gasLimit uint64, tx sdk.Tx) sdk.Context { + if simulate || ctx.BlockHeight() == 0 { + return ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) + } + + denominator := uint64(1) + for _, msg := range tx.GetMsgs() { + candidateDenominator := getMessageMultiplierDenominator(ctx, msg, aclkeeper) + if candidateDenominator > denominator { + denominator = candidateDenominator + } + } + return ctx.WithGasMeter(types.NewMultiplierGasMeter(gasLimit, DefaultGasMultiplierDenominator, denominator)) + } +} + +func getMessageMultiplierDenominator(ctx sdk.Context, msg sdk.Msg, aclkeeper aclkeeper.Keeper) uint64 { + if wasmExecuteMsg, ok := msg.(*wasmtypes.MsgExecuteContract); ok { + addr, err := sdk.AccAddressFromBech32(wasmExecuteMsg.Contract) + if err != nil { + return DefaultGasMultiplierDenominator + } + mapping, err := aclkeeper.GetWasmDependencyMapping(ctx, addr, []byte{}, false) + if err != nil { + return DefaultGasMultiplierDenominator + } + // only give gas discount if none of the dependency (except COMMIT) has id "*" + for _, op := range mapping.AccessOps { + if op.Operation.AccessType != sdkacltypes.AccessType_COMMIT && op.Operation.IdentifierTemplate == "*" { + return DefaultGasMultiplierDenominator + } + } + return WasmCorrectDependencyDiscountDenominator + } + return DefaultGasMultiplierDenominator +} diff --git a/app/antedecorators/gas_test.go b/app/antedecorators/gas_test.go new file mode 100644 index 0000000000..27baf0b972 --- /dev/null +++ b/app/antedecorators/gas_test.go @@ -0,0 +1,77 @@ +package antedecorators_test + +import ( + "testing" + + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/accesscontrol" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + "github.com/sei-protocol/sei-chain/app" + "github.com/sei-protocol/sei-chain/app/antedecorators" + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/proto/tendermint/types" +) + +type TestTx struct { + msgs []sdk.Msg +} + +func (t TestTx) GetMsgs() []sdk.Msg { + return t.msgs +} + +func (t TestTx) ValidateBasic() error { + return nil +} + +func TestMultiplierGasSetter(t *testing.T) { + app := app.Setup(false) + contractAddr, err := sdk.AccAddressFromBech32("sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw") + require.NoError(t, err) + ctx := app.NewContext(false, types.Header{}).WithBlockHeight(2) + testMsg := wasmtypes.MsgExecuteContract{ + Contract: "sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw", + Msg: []byte("{}"), + } + testTx := TestTx{msgs: []sdk.Msg{&testMsg}} + // discounted mapping + app.AccessControlKeeper.SetWasmDependencyMapping(ctx, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV, + IdentifierTemplate: "something", + }, + }, + { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + gasMeterSetter := antedecorators.GetGasMeterSetter(app.AccessControlKeeper) + ctxWithGasMeter := gasMeterSetter(false, ctx, 1000, testTx) + ctxWithGasMeter.GasMeter().ConsumeGas(2, "") + require.Equal(t, uint64(1), ctxWithGasMeter.GasMeter().GasConsumed()) + // not discounted mapping + app.AccessControlKeeper.SetWasmDependencyMapping(ctx, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV, + IdentifierTemplate: "*", + }, + }, + { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + ctxWithGasMeter = gasMeterSetter(false, ctx, 1000, testTx) + ctxWithGasMeter.GasMeter().ConsumeGas(2, "") + require.Equal(t, uint64(2), ctxWithGasMeter.GasMeter().GasConsumed()) +} diff --git a/app/antedecorators/gasless.go b/app/antedecorators/gasless.go index ea9d39999e..17591d0069 100644 --- a/app/antedecorators/gasless.go +++ b/app/antedecorators/gasless.go @@ -4,6 +4,8 @@ import ( "bytes" sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + aclutils "github.com/sei-protocol/sei-chain/aclmapping/utils" dextypes "github.com/sei-protocol/sei-chain/x/dex/types" nitrokeeper "github.com/sei-protocol/sei-chain/x/nitro/keeper" nitrotypes "github.com/sei-protocol/sei-chain/x/nitro/types" @@ -44,6 +46,25 @@ func (gd GaslessDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, return next(ctx, tx, simulate) } +func (gd GaslessDecorator) AnteDeps(txDeps []sdkacltypes.AccessOperation, tx sdk.Tx, next sdk.AnteDepGenerator) (newTxDeps []sdkacltypes.AccessOperation, err error) { + deps := []sdkacltypes.AccessOperation{} + for _, msg := range tx.GetMsgs() { + // Error checking will be handled in AnteHandler + switch m := msg.(type) { + case *oracletypes.MsgAggregateExchangeRateVote: + feederAddr, _ := sdk.AccAddressFromBech32(m.Feeder) + valAddr, _ := sdk.ValAddressFromBech32(m.Validator) + deps = append(deps, aclutils.GetOracleReadAccessOpsForValAndFeeder(feederAddr, valAddr)...) + // TODO: add tx gasless deps for nitro for nitrokeeper read + // TODO: we also need to add READs for Validator + bonded check + default: + continue + } + } + + return next(append(txDeps, deps...), tx) +} + func isTxGasless(tx sdk.Tx, ctx sdk.Context, oracleKeeper oraclekeeper.Keeper, nitroKeeper nitrokeeper.Keeper) bool { if len(tx.GetMsgs()) == 0 { // empty TX shouldn't be gasless diff --git a/app/antedecorators/gasless_test.go b/app/antedecorators/gasless_test.go index 439588bb5b..7fb68025bc 100644 --- a/app/antedecorators/gasless_test.go +++ b/app/antedecorators/gasless_test.go @@ -46,12 +46,12 @@ func (tx FakeTx) ValidateBasic() error { func TestGaslessDecorator(t *testing.T) { output = "" - anteDecorators := []sdk.AnteDecorator{ - FakeAnteDecoratorOne{}, - antedecorators.NewGaslessDecorator([]sdk.AnteDecorator{FakeAnteDecoratorTwo{}}, oraclekeeper.Keeper{}, nitrokeeper.Keeper{}), - FakeAnteDecoratorThree{}, + anteDecorators := []sdk.AnteFullDecorator{ + sdk.DefaultWrappedAnteDecorator(FakeAnteDecoratorOne{}), + sdk.DefaultWrappedAnteDecorator(antedecorators.NewGaslessDecorator([]sdk.AnteDecorator{FakeAnteDecoratorTwo{}}, oraclekeeper.Keeper{}, nitrokeeper.Keeper{})), + sdk.DefaultWrappedAnteDecorator(FakeAnteDecoratorThree{}), } - chainedHandler := sdk.ChainAnteDecorators(anteDecorators...) + chainedHandler, _ := sdk.ChainAnteDecorators(anteDecorators...) chainedHandler(sdk.Context{}, FakeTx{}, false) require.Equal(t, "onetwothree", output) } diff --git a/app/antedecorators/traced_test.go b/app/antedecorators/traced_test.go index 4207e5e7d8..efbca5cc44 100644 --- a/app/antedecorators/traced_test.go +++ b/app/antedecorators/traced_test.go @@ -11,15 +11,15 @@ import ( func TestTracedDecorator(t *testing.T) { output = "" - anteDecorators := []sdk.AnteDecorator{ - FakeAnteDecoratorOne{}, - FakeAnteDecoratorTwo{}, - FakeAnteDecoratorThree{}, + anteDecorators := []sdk.AnteFullDecorator{ + sdk.DefaultWrappedAnteDecorator(FakeAnteDecoratorOne{}), + sdk.DefaultWrappedAnteDecorator(FakeAnteDecoratorTwo{}), + sdk.DefaultWrappedAnteDecorator(FakeAnteDecoratorThree{}), } - tracedDecorators := utils.Map(anteDecorators, func(d sdk.AnteDecorator) sdk.AnteDecorator { - return antedecorators.NewTracedAnteDecorator(d, nil) + tracedDecorators := utils.Map(anteDecorators, func(d sdk.AnteFullDecorator) sdk.AnteFullDecorator { + return sdk.DefaultWrappedAnteDecorator(antedecorators.NewTracedAnteDecorator(d, nil)) }) - chainedHandler := sdk.ChainAnteDecorators(tracedDecorators...) + chainedHandler, _ := sdk.ChainAnteDecorators(tracedDecorators...) chainedHandler(sdk.Context{}, FakeTx{}, false) require.Equal(t, "onetwothree", output) } diff --git a/app/app.go b/app/app.go index 3c9f462ce7..4e29bb4ec5 100644 --- a/app/app.go +++ b/app/app.go @@ -9,10 +9,13 @@ import ( "os" "path/filepath" "strings" + "sync" "time" storetypes "github.com/cosmos/cosmos-sdk/store/types" + "github.com/sei-protocol/sei-chain/aclmapping" + aclutils "github.com/sei-protocol/sei-chain/aclmapping/utils" appparams "github.com/sei-protocol/sei-chain/app/params" "github.com/sei-protocol/sei-chain/utils" "github.com/sei-protocol/sei-chain/wasmbinding" @@ -29,10 +32,18 @@ import ( servertypes "github.com/cosmos/cosmos-sdk/server/types" "github.com/cosmos/cosmos-sdk/simapp" sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" "github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/version" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/auth/ante" + + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + aclmodule "github.com/cosmos/cosmos-sdk/x/accesscontrol" + aclclient "github.com/cosmos/cosmos-sdk/x/accesscontrol/client" + aclconstants "github.com/cosmos/cosmos-sdk/x/accesscontrol/constants" + aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" authrest "github.com/cosmos/cosmos-sdk/x/auth/client/rest" authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" authsims "github.com/cosmos/cosmos-sdk/x/auth/simulation" @@ -100,6 +111,7 @@ import ( tmproto "github.com/tendermint/tendermint/proto/tendermint/types" dbm "github.com/tendermint/tm-db" + "github.com/sei-protocol/sei-chain/utils/metrics" "github.com/sei-protocol/sei-chain/utils/tracing" dexmodule "github.com/sei-protocol/sei-chain/x/dex" @@ -131,8 +143,6 @@ import ( wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" "go.opentelemetry.io/otel" - - "github.com/sei-protocol/sei-chain/utils/metrics" ) // this line is used by starport scaffolding # stargate/wasm/app/enabledProposals @@ -144,6 +154,8 @@ func getGovProposalHandlers() []govclient.ProposalHandler { upgradeclient.CancelProposalHandler, ibcclientclient.UpdateClientProposalHandler, ibcclientclient.UpgradeProposalHandler, + aclclient.ResourceDependencyProposalHandler, + aclclient.WasmDependencyProposalHandler, // this line is used by starport scaffolding # stargate/app/govProposalHandler ) @@ -158,6 +170,7 @@ var ( // non-dependant module elements, such as codec registration // and genesis verification. ModuleBasics = module.NewBasicManager( + aclmodule.AppModuleBasic{}, auth.AppModuleBasic{}, genutil.AppModuleBasic{}, bank.AppModuleBasic{}, @@ -186,6 +199,7 @@ var ( // module account permissions maccPerms = map[string][]string{ + acltypes.ModuleName: nil, authtypes.FeeCollectorName: nil, distrtypes.ModuleName: nil, minttypes.ModuleName: {authtypes.Minter}, @@ -222,6 +236,8 @@ var ( // Boolean to only emit seid version and git commit metric once per chain initialization EmittedSeidVersionMetric = false + // EmptyAclmOpts defines a type alias for a list of wasm options. + EmptyACLOpts []aclkeeper.Option ) var ( @@ -278,23 +294,24 @@ type App struct { memKeys map[string]*sdk.MemoryStoreKey // keepers - AccountKeeper authkeeper.AccountKeeper - BankKeeper bankkeeper.Keeper - CapabilityKeeper *capabilitykeeper.Keeper - StakingKeeper stakingkeeper.Keeper - SlashingKeeper slashingkeeper.Keeper - MintKeeper mintkeeper.Keeper - DistrKeeper distrkeeper.Keeper - GovKeeper govkeeper.Keeper - CrisisKeeper crisiskeeper.Keeper - UpgradeKeeper upgradekeeper.Keeper - ParamsKeeper paramskeeper.Keeper - IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly - EvidenceKeeper evidencekeeper.Keeper - TransferKeeper ibctransferkeeper.Keeper - FeeGrantKeeper feegrantkeeper.Keeper - WasmKeeper wasm.Keeper - OracleKeeper oraclekeeper.Keeper + AccessControlKeeper aclkeeper.Keeper + AccountKeeper authkeeper.AccountKeeper + BankKeeper bankkeeper.Keeper + CapabilityKeeper *capabilitykeeper.Keeper + StakingKeeper stakingkeeper.Keeper + SlashingKeeper slashingkeeper.Keeper + MintKeeper mintkeeper.Keeper + DistrKeeper distrkeeper.Keeper + GovKeeper govkeeper.Keeper + CrisisKeeper crisiskeeper.Keeper + UpgradeKeeper upgradekeeper.Keeper + ParamsKeeper paramskeeper.Keeper + IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly + EvidenceKeeper evidencekeeper.Keeper + TransferKeeper ibctransferkeeper.Keeper + FeeGrantKeeper feegrantkeeper.Keeper + WasmKeeper wasm.Keeper + OracleKeeper oraclekeeper.Keeper // make scoped keepers public for test purposes ScopedIBCKeeper capabilitykeeper.ScopedKeeper @@ -321,7 +338,7 @@ type App struct { optimisticProcessingInfo *OptimisticProcessingInfo // batchVerifier *ante.SR25519BatchVerifier - // txDecoder sdk.TxDecoder + txDecoder sdk.TxDecoder } // New returns a reference to an initialized blockchain app @@ -337,6 +354,7 @@ func New( enabledProposals []wasm.ProposalType, appOpts servertypes.AppOptions, wasmOpts []wasm.Option, + aclOpts []aclkeeper.Option, baseAppOptions ...func(*baseapp.BaseApp), ) *App { appCodec := encodingConfig.Marshaler @@ -349,7 +367,7 @@ func New( bApp.SetInterfaceRegistry(interfaceRegistry) keys := sdk.NewKVStoreKeys( - authtypes.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey, + acltypes.StoreKey, authtypes.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey, minttypes.StoreKey, distrtypes.StoreKey, slashingtypes.StoreKey, govtypes.StoreKey, paramstypes.StoreKey, ibchost.StoreKey, upgradetypes.StoreKey, feegrant.StoreKey, evidencetypes.StoreKey, ibctransfertypes.StoreKey, capabilitytypes.StoreKey, oracletypes.StoreKey, wasm.StoreKey, @@ -381,7 +399,7 @@ func New( Tracer: &tr, TracerContext: context.Background(), }, - // txDecoder: encodingConfig.TxConfig.TxDecoder(), + txDecoder: encodingConfig.TxConfig.TxDecoder(), } app.ParamsKeeper = initParamsKeeper(appCodec, cdc, keys[paramstypes.StoreKey], tkeys[paramstypes.TStoreKey]) @@ -501,10 +519,37 @@ func New( app.keys[nitrotypes.StoreKey], app.GetSubspace(nitrotypes.ModuleName), ) + + customDependencyGenerators := aclmapping.NewCustomDependencyGenerator() + aclOpts = append(aclOpts, aclkeeper.WithDependencyGeneratorMappings(customDependencyGenerators.GetCustomDependencyGenerators())) + app.AccessControlKeeper = aclkeeper.NewKeeper( + appCodec, + app.keys[acltypes.StoreKey], + app.GetSubspace(acltypes.ModuleName), + app.AccountKeeper, + app.StakingKeeper, + aclOpts..., + ) // The last arguments can contain custom message handlers, and custom query handlers, // if we want to allow any custom callbacks supportedFeatures := "iterator,staking,stargate,sei" - wasmOpts = append(wasmbinding.RegisterCustomPlugins(&app.OracleKeeper, &app.DexKeeper, &app.EpochKeeper, &app.TokenFactoryKeeper, &app.AccountKeeper, app.MsgServiceRouter()), wasmOpts...) + wasmOpts = append( + wasmbinding.RegisterCustomPlugins( + &app.OracleKeeper, + &app.DexKeeper, + &app.EpochKeeper, + &app.TokenFactoryKeeper, + &app.AccountKeeper, + app.MsgServiceRouter(), + app.IBCKeeper.ChannelKeeper, + scopedWasmKeeper, + app.BankKeeper, + appCodec, + app.TransferKeeper, + app.AccessControlKeeper, + ), + wasmOpts..., + ) app.WasmKeeper = wasm.NewKeeper( appCodec, keys[wasm.StoreKey], @@ -536,7 +581,8 @@ func New( AddRoute(upgradetypes.RouterKey, upgrade.NewSoftwareUpgradeProposalHandler(app.UpgradeKeeper)). AddRoute(ibcclienttypes.RouterKey, ibcclient.NewClientProposalHandler(app.IBCKeeper.ClientKeeper)). AddRoute(dexmoduletypes.RouterKey, dexmodule.NewProposalHandler(app.DexKeeper)). - AddRoute(tokenfactorytypes.RouterKey, tokenfactorymodule.NewProposalHandler(app.TokenFactoryKeeper)) + AddRoute(tokenfactorytypes.RouterKey, tokenfactorymodule.NewProposalHandler(app.TokenFactoryKeeper)). + AddRoute(acltypes.ModuleName, aclmodule.NewProposalHandler(app.AccessControlKeeper)) if len(enabledProposals) != 0 { govRouter.AddRoute(wasm.RouterKey, wasm.NewWasmProposalHandler(app.WasmKeeper, enabledProposals)) } @@ -569,6 +615,7 @@ func New( app.AccountKeeper, app.StakingKeeper, app.BaseApp.DeliverTx, encodingConfig.TxConfig, ), + aclmodule.NewAppModule(appCodec, app.AccessControlKeeper), auth.NewAppModule(appCodec, app.AccountKeeper, nil), vesting.NewAppModule(app.AccountKeeper, app.BankKeeper), bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper), @@ -622,6 +669,7 @@ func New( wasm.ModuleName, tokenfactorytypes.ModuleName, nitrotypes.ModuleName, + acltypes.ModuleName, ) app.mm.SetOrderEndBlockers( @@ -648,6 +696,7 @@ func New( wasm.ModuleName, tokenfactorytypes.ModuleName, nitrotypes.ModuleName, + acltypes.ModuleName, ) // NOTE: The genutils module must occur after staking so that pools are @@ -679,6 +728,7 @@ func New( dexmoduletypes.ModuleName, nitrotypes.ModuleName, wasm.ModuleName, + acltypes.ModuleName, // this line is used by starport scaffolding # stargate/app/initGenesis ) @@ -727,7 +777,7 @@ func New( signModeHandler := encodingConfig.TxConfig.SignModeHandler() // app.batchVerifier = ante.NewSR25519BatchVerifier(app.AccountKeeper, signModeHandler) - anteHandler, err := NewAnteHandler( + anteHandler, anteDepGenerator, err := NewAnteHandlerAndDepGenerator( HandlerOptions{ HandlerOptions: ante.HandlerOptions{ AccountKeeper: app.AccountKeeper, @@ -737,13 +787,14 @@ func New( SigGasConsumer: ante.DefaultSigVerificationGasConsumer, // BatchVerifier: app.batchVerifier, }, - IBCKeeper: app.IBCKeeper, - TXCounterStoreKey: keys[wasm.StoreKey], - WasmConfig: &wasmConfig, - OracleKeeper: &app.OracleKeeper, - DexKeeper: &app.DexKeeper, - NitroKeeper: &app.NitroKeeper, - TracingInfo: app.tracingInfo, + IBCKeeper: app.IBCKeeper, + TXCounterStoreKey: keys[wasm.StoreKey], + WasmConfig: &wasmConfig, + OracleKeeper: &app.OracleKeeper, + DexKeeper: &app.DexKeeper, + NitroKeeper: &app.NitroKeeper, + TracingInfo: app.tracingInfo, + AccessControlKeeper: &app.AccessControlKeeper, }, ) if err != nil { @@ -751,6 +802,7 @@ func New( } app.SetAnteHandler(anteHandler) + app.SetAnteDepGenerator(anteDepGenerator) app.SetEndBlocker(app.EndBlocker) app.SetPrepareProposalHandler(app.PrepareProposalHandler) app.SetProcessProposalHandler(app.ProcessProposalHandler) @@ -818,6 +870,15 @@ func (app *App) SetStoreUpgradeHandlers() { // configure store loader that checks if version == upgradeHeight and applies store upgrades app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades)) } + + if upgradeInfo.Name == "2.0.0beta" && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { + storeUpgrades := storetypes.StoreUpgrades{ + Added: []string{acltypes.StoreKey}, + } + + // configure store loader that checks if version == upgradeHeight and applies store upgrades + app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades)) + } } // AppName returns the name of the App @@ -916,9 +977,153 @@ func (app *App) FinalizeBlocker(ctx sdk.Context, req *abci.RequestFinalizeBlock) return &resp, nil } +func (app *App) DeliverTxWithResult(ctx sdk.Context, tx []byte) *abci.ExecTxResult { + deliverTxResp := app.DeliverTx(ctx, abci.RequestDeliverTx{ + Tx: tx, + }) + return &abci.ExecTxResult{ + Code: deliverTxResp.Code, + Data: deliverTxResp.Data, + Log: deliverTxResp.Log, + Info: deliverTxResp.Info, + GasWanted: deliverTxResp.GasWanted, + GasUsed: deliverTxResp.GasUsed, + Events: deliverTxResp.Events, + Codespace: deliverTxResp.Codespace, + } +} + +func (app *App) ProcessBlockSynchronous(ctx sdk.Context, txs [][]byte) []*abci.ExecTxResult { + defer metrics.BlockProcessLatency(time.Now(), metrics.SYNCHRONOUS) + + txResults := []*abci.ExecTxResult{} + for _, tx := range txs { + txResults = append(txResults, app.DeliverTxWithResult(ctx, tx)) + metrics.IncrTxProcessTypeCounter(metrics.SYNCHRONOUS) + } + return txResults +} + +// Returns a mapping of the accessOperation to the channels +func GetChannelsFromSignalMapping(signalMapping acltypes.MessageCompletionSignalMapping) sdkacltypes.MessageAccessOpsChannelMapping { + channelsMapping := make(sdkacltypes.MessageAccessOpsChannelMapping) + for messageIndex, accessOperationsToSignal := range signalMapping { + channelsMapping[messageIndex] = make(sdkacltypes.AccessOpsChannelMapping) + for accessOperation, completionSignals := range accessOperationsToSignal { + var channels []chan interface{} + for _, completionSignal := range completionSignals { + channels = append(channels, completionSignal.Channel) + } + channelsMapping[messageIndex][accessOperation] = channels + } + } + return channelsMapping +} + +type ChannelResult struct { + txIndex int + result *abci.ExecTxResult +} + +// cacheContext returns a new context based off of the provided context with +// a branched multi-store. +func (app *App) CacheContext(ctx sdk.Context) (sdk.Context, sdk.CacheMultiStore) { + ms := ctx.MultiStore() + msCache := ms.CacheMultiStore() + return ctx.WithMultiStore(msCache), msCache +} + +func (app *App) ProcessTxConcurrent( + ctx sdk.Context, + txIndex int, + txBytes []byte, + wg *sync.WaitGroup, + resultChan chan<- ChannelResult, + txCompletionSignalingMap acltypes.MessageCompletionSignalMapping, + txBlockingSignalsMap acltypes.MessageCompletionSignalMapping, + txMsgAccessOpMapping acltypes.MsgIndexToAccessOpMapping, +) { + defer wg.Done() + // Store the Channels in the Context Object for each transaction + ctx = ctx.WithTxCompletionChannels(GetChannelsFromSignalMapping(txCompletionSignalingMap)) + ctx = ctx.WithTxBlockingChannels(GetChannelsFromSignalMapping(txBlockingSignalsMap)) + ctx = ctx.WithTxMsgAccessOps(txMsgAccessOpMapping) + ctx = ctx.WithMsgValidator( + sdkacltypes.NewMsgValidator(aclutils.StoreKeyToResourceTypePrefixMap), + ) + + // Deliver the transaction and store the result in the channel + resultChan <- ChannelResult{txIndex, app.DeliverTxWithResult(ctx, txBytes)} + metrics.IncrTxProcessTypeCounter(metrics.CONCURRENT) +} + +func (app *App) ProcessBlockConcurrent( + ctx sdk.Context, + txs [][]byte, + completionSignalingMap map[int]acltypes.MessageCompletionSignalMapping, + blockingSignalsMap map[int]acltypes.MessageCompletionSignalMapping, + txMsgAccessOpMapping map[int]acltypes.MsgIndexToAccessOpMapping, +) ([]*abci.ExecTxResult, bool) { + defer metrics.BlockProcessLatency(time.Now(), metrics.CONCURRENT) + + txResults := []*abci.ExecTxResult{} + // If there's no transactions then return empty results + if len(txs) == 0 { + return txResults, true + } + + var waitGroup sync.WaitGroup + resultChan := make(chan ChannelResult) + // For each transaction, start goroutine and deliver TX + for txIndex, txBytes := range txs { + waitGroup.Add(1) + go app.ProcessTxConcurrent( + ctx, + txIndex, + txBytes, + &waitGroup, + resultChan, + completionSignalingMap[txIndex], + blockingSignalsMap[txIndex], + txMsgAccessOpMapping[txIndex], + ) + } + + // Do not call waitGroup.Wait() synchronously as it blocks on channel reads + // until all the messages are read. This closes the channel once + // results are all read and prevent any further writes. + go func() { + waitGroup.Wait() + close(resultChan) + }() + + // Gather Results and store it based on txIndex and read results from channel + // Concurrent results may be in different order than the original txIndex + txResultsMap := map[int]*abci.ExecTxResult{} + for result := range resultChan { + txResultsMap[result.txIndex] = result.result + } + + // Gather Results and store in array based on txIndex to preserve ordering + for txIndex := range txs { + txResults = append(txResults, txResultsMap[txIndex]) + } + + ok := true + for i, result := range txResults { + if result.GetCode() == sdkerrors.ErrInvalidConcurrencyExecution.ABCICode() { + ctx.Logger().Error(fmt.Sprintf("Invalid concurrent execution of deliverTx index=%d", i)) + metrics.IncrFailedConcurrentDeliverTxCounter() + ok = false + } + } + + return txResults, ok +} + func (app *App) ProcessBlock(ctx sdk.Context, txs [][]byte, req BlockProcessRequest, lastCommit abci.CommitInfo) ([]abci.Event, []*abci.ExecTxResult, abci.ResponseEndBlock, error) { goCtx := app.decorateContextWithDexMemState(ctx.Context()) - ctx = ctx.WithContext(goCtx) + ctx = ctx.WithContext(goCtx).WithContextMemCache(sdk.NewContextMemCache()) events := []abci.Event{} beginBlockReq := abci.RequestBeginBlock{ @@ -957,31 +1162,82 @@ func (app *App) ProcessBlock(ctx sdk.Context, txs [][]byte, req BlockProcessRequ // } // app.batchVerifier.VerifyTxs(ctx, typedTxs) - txResults := []*abci.ExecTxResult{} - for _, tx := range txs { - // ctx = ctx.WithContext(context.WithValue(ctx.Context(), ante.ContextKeyTxIndexKey, i)) - deliverTxResp := app.DeliverTx(ctx, abci.RequestDeliverTx{ - Tx: tx, - }) - txResults = append(txResults, &abci.ExecTxResult{ - Code: deliverTxResp.Code, - Data: deliverTxResp.Data, - Log: deliverTxResp.Log, - Info: deliverTxResp.Info, - GasWanted: deliverTxResp.GasWanted, - GasUsed: deliverTxResp.GasUsed, - Events: deliverTxResp.Events, - Codespace: deliverTxResp.Codespace, - }) + dependencyDag, err := app.AccessControlKeeper.BuildDependencyDag(ctx, app.txDecoder, app.GetAnteDepGenerator(), txs) + + var txResults []*abci.ExecTxResult + + switch err { + case nil: + // Only run concurrently if no error + // Branch off the current context and pass a cached context to the concurrent delivered TXs that are shared. + // runTx will write to this ephermeral CacheMultiStore, after the process block is done, Write() is called on this + // CacheMultiStore where it writes the data to the parent store (DeliverState) in sorted Key order to maintain + // deterministic ordering between validators in the case of concurrent deliverTXs + processBlockCtx, processBlockCache := app.CacheContext(ctx) + concurrentResults, ok := app.ProcessBlockConcurrent( + processBlockCtx, + txs, + dependencyDag.CompletionSignalingMap, + dependencyDag.BlockingSignalsMap, + dependencyDag.TxMsgAccessOpMapping, + ) + if ok { + txResults = concurrentResults + // Write the results back to the concurrent contexts - if concurrent execution fails, + // this should not be called and the state is rolled back and retried with synchronous execution + processBlockCache.Write() + } else { + ctx.Logger().Error("Concurrent Execution failed, retrying with Synchronous") + txResults = app.ProcessBlockSynchronous(ctx, txs) + } + + // Write the results back to the concurrent contexts + processBlockCache.Write() + case acltypes.ErrGovMsgInBlock: + ctx.Logger().Info(fmt.Sprintf("Gov msg found while building DAG, processing synchronously: %s", err)) + txResults = app.ProcessBlockSynchronous(ctx, txs) + metrics.IncrDagBuildErrorCounter(metrics.GovMsgInBlock) + default: + ctx.Logger().Error(fmt.Sprintf("Error while building DAG, processing synchronously: %s", err)) + txResults = app.ProcessBlockSynchronous(ctx, txs) + metrics.IncrDagBuildErrorCounter(metrics.FailedToBuild) } + + // Finalize all Bank Module Transfers here so that events are included + lazyWriteEvents := app.BankKeeper.WriteDeferredDepositsToModuleAccounts(ctx) + events = append(events, lazyWriteEvents...) + + ctx = app.enrichContextWithTxResults(ctx, txResults) endBlockResp := app.EndBlock(ctx, abci.RequestEndBlock{ Height: req.GetHeight(), }) + events = append(events, endBlockResp.Events...) return events, txResults, endBlockResp, nil } +func (app *App) enrichContextWithTxResults(ctx sdk.Context, txResults []*abci.ExecTxResult) sdk.Context { + wasmContractsWithIncorrectDependencies := []sdk.AccAddress{} + for _, txResult := range txResults { + if txResult.Codespace == acltypes.ModuleName && txResult.Code == 2 { + for _, event := range txResult.Events { + if event.Type == wasmbinding.EventTypeWasmContractWithIncorrectDependency { + for _, attr := range event.Attributes { + if attr.Key == wasmbinding.AttributeKeyWasmContractAddress { + addr, err := sdk.AccAddressFromBech32(attr.Value) + if err == nil { + wasmContractsWithIncorrectDependencies = append(wasmContractsWithIncorrectDependencies, addr) + } + } + } + } + } + } + } + return ctx.WithContext(context.WithValue(ctx.Context(), aclconstants.BadWasmDependencyAddressesKey, wasmContractsWithIncorrectDependencies)) +} + func (app *App) getFinalizeBlockResponse(appHash []byte, events []abci.Event, txResults []*abci.ExecTxResult, endBlockResp abci.ResponseEndBlock) abci.ResponseFinalizeBlock { return abci.ResponseFinalizeBlock{ Events: events, @@ -1118,6 +1374,7 @@ func GetMaccPerms() map[string][]string { func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino, key, tkey sdk.StoreKey) paramskeeper.Keeper { paramsKeeper := paramskeeper.NewKeeper(appCodec, legacyAmino, key, tkey) + paramsKeeper.Subspace(acltypes.ModuleName) paramsKeeper.Subspace(authtypes.ModuleName) paramsKeeper.Subspace(banktypes.ModuleName) paramsKeeper.Subspace(stakingtypes.ModuleName) diff --git a/app/app_test.go b/app/app_test.go index 5b8eb2c5d1..706ba6868a 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -6,6 +6,9 @@ import ( "time" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + "github.com/k0kubun/pp/v3" "github.com/sei-protocol/sei-chain/app" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" @@ -29,3 +32,47 @@ func TestEmptyBlockIdempotency(t *testing.T) { require.Equal(t, len(referenceData), len(data)) } } + +func TestGetChannelsFromSignalMapping(t *testing.T) { + dag := acltypes.NewDag() + commit := *acltypes.CommitAccessOp() + writeA := sdkacltypes.AccessOperation{ + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV, + IdentifierTemplate: "ResourceA", + } + readA := sdkacltypes.AccessOperation{ + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV, + IdentifierTemplate: "ResourceA", + } + readAll := sdkacltypes.AccessOperation{ + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_ANY, + IdentifierTemplate: "*", + } + + dag.AddNodeBuildDependency(0, 0, writeA) + dag.AddNodeBuildDependency(0, 0, commit) + dag.AddNodeBuildDependency(1, 0, readAll) + dag.AddNodeBuildDependency(1, 0, commit) + dag.AddNodeBuildDependency(2, 0, writeA) + dag.AddNodeBuildDependency(2, 0, commit) + dag.AddNodeBuildDependency(3, 0, writeA) + dag.AddNodeBuildDependency(3, 0, commit) + + dag.AddNodeBuildDependency(0, 1, writeA) + dag.AddNodeBuildDependency(0, 1, commit) + dag.AddNodeBuildDependency(1, 1, readA) + dag.AddNodeBuildDependency(1, 1, commit) + + completionSignalsMap, blockingSignalsMap := dag.CompletionSignalingMap, dag.BlockingSignalsMap + + pp.Default.SetColoringEnabled(false) + + resultCompletionSignalsMap := app.GetChannelsFromSignalMapping(completionSignalsMap[0]) + resultBlockingSignalsMap := app.GetChannelsFromSignalMapping(blockingSignalsMap[1]) + + require.True(t, len(resultCompletionSignalsMap) > 1) + require.True(t, len(resultBlockingSignalsMap) > 1) +} diff --git a/app/apptesting/test_suite.go b/app/apptesting/test_suite.go index 860d6192c3..01c71943d3 100644 --- a/app/apptesting/test_suite.go +++ b/app/apptesting/test_suite.go @@ -143,7 +143,8 @@ func (s *KeeperTestHelper) BuildTx( txBuilder client.TxBuilder, msgs []sdk.Msg, sigV2 signing.SignatureV2, - memo string, txFee sdk.Coins, + memo string, + txFee sdk.Coins, gasLimit uint64, ) authsigning.Tx { err := txBuilder.SetMsgs(msgs[0]) diff --git a/app/params/config.go b/app/params/config.go index 07a0c81595..1b6e8c7fe2 100644 --- a/app/params/config.go +++ b/app/params/config.go @@ -21,7 +21,7 @@ const ( Bech32PrefixAccAddr = "sei" ) -var UnsafeBypassCommitTimeoutOverride bool = true +var UnsafeBypassCommitTimeoutOverride = true var ( // Bech32PrefixAccPub defines the Bech32 prefix of an account's public key. diff --git a/app/test_helpers.go b/app/test_helpers.go index 814b3b8604..406c9744fc 100644 --- a/app/test_helpers.go +++ b/app/test_helpers.go @@ -140,6 +140,7 @@ func Setup(isCheckTx bool) *App { wasm.EnableAllProposals, &cosmostestutil.TestAppOpts{}, EmptyWasmOpts, + EmptyACLOpts, ) if !isCheckTx { genesisState := NewDefaultGenesisState(cdc) @@ -183,6 +184,7 @@ func SetupTestingAppWithLevelDb(isCheckTx bool) (*App, func()) { wasm.EnableAllProposals, &cosmostestutil.TestAppOpts{}, EmptyWasmOpts, + EmptyACLOpts, ) if !isCheckTx { genesisState := NewDefaultGenesisState(cdc) diff --git a/cmd/seid/cmd/root.go b/cmd/seid/cmd/root.go index ba67485460..cc1bd28d9c 100644 --- a/cmd/seid/cmd/root.go +++ b/cmd/seid/cmd/root.go @@ -21,6 +21,7 @@ import ( "github.com/cosmos/cosmos-sdk/snapshots" "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" + aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" @@ -249,6 +250,7 @@ func newApp( wasm.EnableAllProposals, appOpts, []wasm.Option{}, + []aclkeeper.Option{}, baseapp.SetPruning(pruningOpts), baseapp.SetMinGasPrices(cast.ToString(appOpts.Get(server.FlagMinGasPrices))), baseapp.SetMinRetainBlocks(cast.ToUint64(appOpts.Get(server.FlagMinRetainBlocks))), @@ -284,12 +286,12 @@ func appExport( } if height != -1 { - exportableApp = app.New(logger, db, traceStore, false, map[int64]bool{}, cast.ToString(appOpts.Get(flags.FlagHome)), uint(1), encCfg, app.GetWasmEnabledProposals(), appOpts, app.EmptyWasmOpts) + exportableApp = app.New(logger, db, traceStore, false, map[int64]bool{}, cast.ToString(appOpts.Get(flags.FlagHome)), uint(1), encCfg, app.GetWasmEnabledProposals(), appOpts, app.EmptyWasmOpts, app.EmptyACLOpts) if err := exportableApp.LoadHeight(height); err != nil { return servertypes.ExportedApp{}, err } } else { - exportableApp = app.New(logger, db, traceStore, true, map[int64]bool{}, cast.ToString(appOpts.Get(flags.FlagHome)), uint(1), encCfg, app.GetWasmEnabledProposals(), appOpts, app.EmptyWasmOpts) + exportableApp = app.New(logger, db, traceStore, true, map[int64]bool{}, cast.ToString(appOpts.Get(flags.FlagHome)), uint(1), encCfg, app.GetWasmEnabledProposals(), appOpts, app.EmptyWasmOpts, app.EmptyACLOpts) } return exportableApp.ExportAppStateAndValidators(forZeroHeight, jailAllowedAddrs) diff --git a/go.mod b/go.mod index 35a40c2c79..52cdae5065 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/golang/protobuf v1.5.2 github.com/gorilla/mux v1.8.0 github.com/grpc-ecosystem/grpc-gateway v1.16.0 + github.com/k0kubun/pp/v3 v3.2.0 github.com/pkg/errors v0.9.1 github.com/regen-network/cosmos-proto v0.3.1 github.com/spf13/cast v1.5.0 @@ -24,8 +25,8 @@ require ( go.opentelemetry.io/otel/exporters/jaeger v1.9.0 go.opentelemetry.io/otel/sdk v1.9.0 go.opentelemetry.io/otel/trace v1.9.0 - google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a - google.golang.org/grpc v1.50.1 + google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc + google.golang.org/grpc v1.48.0 google.golang.org/protobuf v1.28.1 gopkg.in/yaml.v2 v2.4.0 ) @@ -48,6 +49,7 @@ require ( github.com/creachadair/taskgroup v0.3.2 // indirect github.com/danieljoos/wincred v1.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/deckarep/golang-set v1.8.0 // indirect github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect github.com/dgraph-io/badger/v3 v3.2103.2 // indirect github.com/dgraph-io/ristretto v0.1.0 // indirect @@ -90,7 +92,8 @@ require ( github.com/lib/pq v1.10.6 // indirect github.com/libp2p/go-buffer-pool v0.0.2 // indirect github.com/magiconair/properties v1.8.6 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect @@ -108,6 +111,7 @@ require ( github.com/rs/cors v1.8.2 // indirect github.com/rs/zerolog v1.26.1 // indirect github.com/sasha-s/go-deadlock v0.3.1 // indirect + github.com/savaki/jq v0.0.0-20161209013833-0e6baecebbf8 // indirect github.com/spf13/afero v1.8.2 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect @@ -117,13 +121,14 @@ require ( github.com/tendermint/btcd v0.1.1 // indirect github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15 // indirect github.com/tendermint/go-amino v0.16.0 // indirect + github.com/yourbasic/graph v0.0.0-20210606180040-8ecfec1c2869 // indirect github.com/zondax/hid v0.9.0 // indirect go.etcd.io/bbolt v1.3.6 // indirect go.opencensus.io v0.23.0 // indirect golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect golang.org/x/net v0.0.0-20220909164309-bea034e7d591 // indirect golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 // indirect - golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect + golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 // indirect golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 // indirect golang.org/x/text v0.3.8 // indirect gopkg.in/ini.v1 v1.66.4 // indirect @@ -132,7 +137,7 @@ require ( ) replace ( - github.com/cosmos/cosmos-sdk => github.com/sei-protocol/sei-cosmos v0.1.195 + github.com/cosmos/cosmos-sdk => github.com/sei-protocol/sei-cosmos v0.1.268 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 github.com/keybase/go-keychain => github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 github.com/tendermint/tendermint => github.com/sei-protocol/sei-tendermint v0.1.59 diff --git a/go.sum b/go.sum index df954c8d3f..85d1c1d4b4 100644 --- a/go.sum +++ b/go.sum @@ -291,6 +291,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= +github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= +github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/denis-tingaikin/go-header v0.4.3/go.mod h1:0wOCWuN71D5qIgE2nz9KrKmuYBAC2Mra5RassOIQ2/c= github.com/denisenkom/go-mssqldb v0.12.0/go.mod h1:iiK0YP1ZeepvmBQk/QpLEhhTNJgfzrpArPY/aFvc9yU= @@ -736,6 +738,8 @@ github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8 github.com/julz/importas v0.1.0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +github.com/k0kubun/pp/v3 v3.2.0 h1:h33hNTZ9nVFNP3u2Fsgz8JXiF5JINoZfFq4SvKJwNcs= +github.com/k0kubun/pp/v3 v3.2.0/go.mod h1:ODtJQbQcIRfAD3N+theGCV1m/CBxweERz2dapdz1EwA= github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.6.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -802,6 +806,8 @@ github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= @@ -811,8 +817,9 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= @@ -1095,12 +1102,14 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0 github.com/sanposhiho/wastedassign/v2 v2.0.6/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI= github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0= github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM= +github.com/savaki/jq v0.0.0-20161209013833-0e6baecebbf8 h1:ajJQhvqPSQFJJ4aV5mDAMx8F7iFi6Dxfo6y62wymLNs= +github.com/savaki/jq v0.0.0-20161209013833-0e6baecebbf8/go.mod h1:Nw/CCOXNyF5JDd6UpYxBwG5WWZ2FOJ/d5QnXL4KQ6vY= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/securego/gosec/v2 v2.11.0/go.mod h1:SX8bptShuG8reGC0XS09+a4H2BoWSJi+fscA+Pulbpo= github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= -github.com/sei-protocol/sei-cosmos v0.1.195 h1:WfHp+1x6DRH2jl936ogDLo443EtSvbNY62pZ7msVY9s= -github.com/sei-protocol/sei-cosmos v0.1.195/go.mod h1:bNUxufOqMG4cRokrw2Q30rQ1rOwJYjHTWsQoH+UZTFY= +github.com/sei-protocol/sei-cosmos v0.1.268 h1:v1WlauXbjnILJf3y+N9YuEnM0xqN+LKnkQ97KGCkY7M= +github.com/sei-protocol/sei-cosmos v0.1.268/go.mod h1:o4+r4xXqBLvskNeg5eCmFH+K1Rqd08UPMvnzWI+OxRo= github.com/sei-protocol/sei-tendermint v0.1.59 h1:POGL60PumMQHF4EzAHzvkGfDnodQJLHpl65LuiwSO/Y= github.com/sei-protocol/sei-tendermint v0.1.59/go.mod h1:Olwbjyagrpoxj5DAUhHxMTWDVEfQ3FYdpypaJ3+6Hs8= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= @@ -1252,6 +1261,8 @@ github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1: github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= github.com/ybbus/jsonrpc v2.1.2+incompatible/go.mod h1:XJrh1eMSzdIYFbM08flv0wp5G35eRniyeGut1z+LSiE= github.com/yeya24/promlinter v0.2.0/go.mod h1:u54lkmBOZrpEbQQ6gox2zWKKLKu2SGe+2KOiextY+IA= +github.com/yourbasic/graph v0.0.0-20210606180040-8ecfec1c2869 h1:7v7L5lsfw4w8iqBBXETukHo4IPltmD+mWoLRYUmeGN8= +github.com/yourbasic/graph v0.0.0-20210606180040-8ecfec1c2869/go.mod h1:Rfzr+sqaDreiCaoQbFCu3sTXxeFq/9kXRuyOoSlGQHE= github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= @@ -1631,8 +1642,9 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY= +golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1908,8 +1920,8 @@ google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a h1:GH6UPn3ixhWcKDhpnEC55S75cerLPdpp3hrhfKYjZgw= -google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc h1:Nf+EdcTLHR8qDNN/KfkQL0u0ssxt9OhbaWCl5C0ucEI= +google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= google.golang.org/grpc v1.33.2 h1:EQyQC3sa8M+p6Ulc8yy9SWSS2GVwyRc83gAbG8lrl4o= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= diff --git a/loadtest/config.json b/loadtest/config.json index 277844036e..ddbc7b6805 100644 --- a/loadtest/config.json +++ b/loadtest/config.json @@ -1,63 +1,71 @@ { - "batch_size": 1, - "chain_id": "sei-loadtest-testnet", - "orders_per_block": 2, - "rounds": 5, - "price_distribution": { - "min": "45", - "max": "55", - "number_of_distinct_values": 20 - }, - "quantity_distribution": { - "min": "1", - "max": "21", - "number_of_distinct_values": 20 - }, - "dex_message_type_distribution": { - "limit_order_percentage": "0.2", - "market_order_percentage": "0.8" - }, - "message_type": "bank,dex,staking,tokenfactory", - "contract_distribution": [ - { - "contract_address":"sei14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sh9m79m", - "percentage":"0.1" + "msgs_per_tx": 10, + "chain_id": "sei-loadtest-testnet", + "txs_per_block": 400, + "rounds": 5, + "price_distribution": { + "min": "45", + "max": "55", + "number_of_distinct_values": 20 }, - { - "contract_address":"sei1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrqms7u8a", - "percentage":"0.1" + "quantity_distribution": { + "min": "1", + "max": "21", + "number_of_distinct_values": 20 }, - { - "contract_address":"sei17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9jfksztgw5uh69wac2pgsrtqewe", - "percentage":"0.1" + "message_type_distribution": { + "dex": { + "limit_order_percentage": "0.2", + "market_order_percentage": "0.8" + }, + "staking": { + "delegate_percentage": "0.5", + "undelegate_percentage": "0.25", + "begin_redelegate_percentage": "0.25" + } }, - { - "contract_address":"sei1yw4xvtc43me9scqfr2jr2gzvcxd3a9y4eq7gaukreugw2yd2f8tsy4qgdm", - "percentage":"0.1" - }, - { - "contract_address":"sei1hulx7cgvpfcvg83wk5h96sedqgn72n026w6nl47uht554xhvj9nstdktte", - "percentage":"0.1" - }, - { - "contract_address":"sei1fzm6gzyccl8jvdv3qq6hp9vs6ylaruervs4m06c7k0ntzn2f8faq8un0p6", - "percentage":"0.1" - }, - { - "contract_address":"sei1wl59k23zngj34l7d42y9yltask7rjlnxgccawc7ltrknp6n52fpsj6ctln", - "percentage":"0.1" - }, - { - "contract_address":"sei182nff4ttmvshn6yjlqj5czapfcav9434l2qzz8aahf5pxnyd33ts2pdy3l", - "percentage":"0.1" - }, - { - "contract_address":"sei1k8re7jwz6rnnwrktnejdwkwnncte7ek7gt29gvnl3sdrg9mtnqksw4tqd9", - "percentage":"0.1" - }, - { - "contract_address":"sei1nwnejwsdpqktusvh8qhxe5arsznjd5asdwutmaz9n5qcpl3dcmhs9eeuca", - "percentage":"0.1" - } - ] -} \ No newline at end of file + "message_type": "bank,dex,staking,tokenfactory", + "run_oracle": true, + "contract_distribution": [ + { + "contract_address":"sei14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sh9m79m", + "percentage":"0.1" + }, + { + "contract_address":"sei1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrqms7u8a", + "percentage":"0.1" + }, + { + "contract_address":"sei17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9jfksztgw5uh69wac2pgsrtqewe", + "percentage":"0.1" + }, + { + "contract_address":"sei1yw4xvtc43me9scqfr2jr2gzvcxd3a9y4eq7gaukreugw2yd2f8tsy4qgdm", + "percentage":"0.1" + }, + { + "contract_address":"sei1hulx7cgvpfcvg83wk5h96sedqgn72n026w6nl47uht554xhvj9nstdktte", + "percentage":"0.1" + }, + { + "contract_address":"sei1fzm6gzyccl8jvdv3qq6hp9vs6ylaruervs4m06c7k0ntzn2f8faq8un0p6", + "percentage":"0.1" + }, + { + "contract_address":"sei1wl59k23zngj34l7d42y9yltask7rjlnxgccawc7ltrknp6n52fpsj6ctln", + "percentage":"0.1" + }, + { + "contract_address":"sei182nff4ttmvshn6yjlqj5czapfcav9434l2qzz8aahf5pxnyd33ts2pdy3l", + "percentage":"0.1" + }, + { + "contract_address":"sei1k8re7jwz6rnnwrktnejdwkwnncte7ek7gt29gvnl3sdrg9mtnqksw4tqd9", + "percentage":"0.1" + }, + { + "contract_address":"sei1nwnejwsdpqktusvh8qhxe5arsznjd5asdwutmaz9n5qcpl3dcmhs9eeuca", + "percentage":"0.1" + } + ] +} diff --git a/loadtest/contracts/deploy_ten_contracts.sh b/loadtest/contracts/deploy_ten_contracts.sh index 6c0dcb78a0..3d65af5208 100644 --- a/loadtest/contracts/deploy_ten_contracts.sh +++ b/loadtest/contracts/deploy_ten_contracts.sh @@ -15,17 +15,17 @@ echo cd $seihome/loadtest/contracts/mars && cargo build && docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/rust-optimizer:0.12.5 + cosmwasm/rust-optimizer:0.12.6 cd $seihome/loadtest/contracts/saturn && cargo build && docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/rust-optimizer:0.12.5 + cosmwasm/rust-optimizer:0.12.6 cd $seihome/loadtest/contracts/venus && cargo build && docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/rust-optimizer:0.12.5 + cosmwasm/rust-optimizer:0.12.6 # Deploy all contracts echo "Deploying contracts..." diff --git a/loadtest/loadtest_client.go b/loadtest/loadtest_client.go new file mode 100644 index 0000000000..64e2dbcac5 --- /dev/null +++ b/loadtest/loadtest_client.go @@ -0,0 +1,199 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + "sync" + "time" + + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + typestx "github.com/cosmos/cosmos-sdk/types/tx" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "google.golang.org/grpc" +) + +type LoadTestClient struct { + LoadTestConfig Config + TestConfig EncodingConfig + TxClient typestx.ServiceClient + TxHashFile *os.File + SignerClient *SignerClient + ChainID string + TxHashList []string + TxHashListMutex *sync.Mutex + GrpcConn *grpc.ClientConn + StakingQueryClient stakingtypes.QueryClient + //// Staking specific variables + Validators []stakingtypes.Validator + // DelegationMap is a map of delegator -> validator -> delegated amount + DelegationMap map[string]map[string]int + //// Tokenfactory specific variables + TokenFactoryDenomOwner map[string]string +} + +func NewLoadTestClient() *LoadTestClient { + grpcConn, _ := grpc.Dial( + "127.0.0.1:9090", + grpc.WithInsecure(), + ) + TxClient := typestx.NewServiceClient(grpcConn) + + config := Config{} + pwd, _ := os.Getwd() + file, _ := os.ReadFile(pwd + "/loadtest/config.json") + if err := json.Unmarshal(file, &config); err != nil { + panic(err) + } + + // setup output files + userHomeDir, _ := os.UserHomeDir() + _ = os.Mkdir(filepath.Join(userHomeDir, "outputs"), os.ModePerm) + filename := filepath.Join(userHomeDir, "outputs", "test_tx_hash") + _ = os.Remove(filename) + outputFile, _ := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) + + return &LoadTestClient{ + LoadTestConfig: config, + TestConfig: TestConfig, + TxClient: TxClient, + TxHashFile: outputFile, + SignerClient: NewSignerClient(), + ChainID: config.ChainID, + TxHashList: []string{}, + TxHashListMutex: &sync.Mutex{}, + GrpcConn: grpcConn, + StakingQueryClient: stakingtypes.NewQueryClient(grpcConn), + DelegationMap: map[string]map[string]int{}, + TokenFactoryDenomOwner: map[string]string{}, + } +} + +func (c *LoadTestClient) SetValidators() { + if strings.Contains(c.LoadTestConfig.MessageType, "staking") { + if resp, err := c.StakingQueryClient.Validators(context.Background(), &stakingtypes.QueryValidatorsRequest{}); err != nil { + panic(err) + } else { + c.Validators = resp.Validators + } + } +} + +func (c *LoadTestClient) Close() { + c.GrpcConn.Close() +} + +func (c *LoadTestClient) AppendTxHash(txHash string) { + c.TxHashListMutex.Lock() + defer c.TxHashListMutex.Unlock() + + c.TxHashList = append(c.TxHashList, txHash) +} + +func (c *LoadTestClient) WriteTxHashToFile() { + file := c.TxHashFile + for _, txHash := range c.TxHashList { + txHashLine := fmt.Sprintf("%s\n", txHash) + if _, err := file.WriteString(txHashLine); err != nil { + panic(err) + } + } +} + +func (c *LoadTestClient) BuildTxs() (workgroups []*sync.WaitGroup, sendersList [][]func() string) { + config := c.LoadTestConfig + numberOfAccounts := config.TxsPerBlock / config.MsgsPerTx * 2 // * 2 because we need two sets of accounts + activeAccounts := []int{} + inactiveAccounts := []int{} + + for i := 0; i < int(numberOfAccounts); i++ { + if i%2 == 0 { + activeAccounts = append(activeAccounts, i) + } else { + inactiveAccounts = append(inactiveAccounts, i) + } + } + + valKeys := c.SignerClient.GetValKeys() + + for i := 0; i < int(config.Rounds); i++ { + fmt.Printf("Preparing %d-th round\n", i) + + wg := &sync.WaitGroup{} + var senders []func() string + workgroups = append(workgroups, wg) + + for j, account := range activeAccounts { + key := c.SignerClient.GetKey(uint64(account)) + + msg, failureExpected := c.generateMessage(config, key, config.MsgsPerTx) + txBuilder := TestConfig.TxConfig.NewTxBuilder() + _ = txBuilder.SetMsgs(msg) + seqDelta := uint64(i / 2) + mode := typestx.BroadcastMode_BROADCAST_MODE_SYNC + if j == len(activeAccounts)-1 { + mode = typestx.BroadcastMode_BROADCAST_MODE_BLOCK + } + // Note: There is a potential race condition here with seqnos + // in which a later seqno is delievered before an earlier seqno + // In practice, we haven't run into this issue so we'll leave this + // as is. + sender := SendTx(key, &txBuilder, mode, seqDelta, failureExpected, *c) + wg.Add(1) + senders = append(senders, func() string { + defer wg.Done() + return sender() + }) + } + + senders = append(senders, c.GenerateOracleSenders(i, config, valKeys, wg)...) + + sendersList = append(sendersList, senders) + inactiveAccounts, activeAccounts = activeAccounts, inactiveAccounts + } + + return workgroups, sendersList +} + +func (c *LoadTestClient) GenerateOracleSenders(i int, config Config, valKeys []cryptotypes.PrivKey, waitGroup *sync.WaitGroup) []func() string { + senders := []func() string{} + if config.RunOracle && i%2 == 0 { + for _, valKey := range valKeys { + // generate oracle tx + msg := generateOracleMessage(valKey) + txBuilder := TestConfig.TxConfig.NewTxBuilder() + _ = txBuilder.SetMsgs(msg) + seqDelta := uint64(i / 2) + mode := typestx.BroadcastMode_BROADCAST_MODE_SYNC + sender := SendTx(valKey, &txBuilder, mode, seqDelta, false, *c) + waitGroup.Add(1) + senders = append(senders, func() string { + defer waitGroup.Done() + return sender() + }) + } + } + return senders +} + +func (c *LoadTestClient) SendTxs(workgroups []*sync.WaitGroup, sendersList [][]func() string) { + lastHeight := getLastHeight() + for i := 0; i < int(c.LoadTestConfig.Rounds); i++ { + newHeight := getLastHeight() + for newHeight == lastHeight { + time.Sleep(10 * time.Millisecond) + newHeight = getLastHeight() + } + fmt.Printf("Sending %d-th block\n", i) + senders := sendersList[i] + wg := workgroups[i] + for _, sender := range senders { + go sender() + } + wg.Wait() + lastHeight = newHeight + } +} diff --git a/loadtest/main.go b/loadtest/main.go index 6d77980e13..bf512a2f87 100644 --- a/loadtest/main.go +++ b/loadtest/main.go @@ -1,145 +1,32 @@ package main import ( - "context" "encoding/json" - "flag" "fmt" "math/rand" - "os" "os/exec" - "path/filepath" "strconv" "strings" - "sync" "time" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" tokenfactorytypes "github.com/sei-protocol/sei-chain/x/tokenfactory/types" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/std" + "github.com/cosmos/cosmos-sdk/x/auth/tx" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/cosmos-sdk/std" sdk "github.com/cosmos/cosmos-sdk/types" - typestx "github.com/cosmos/cosmos-sdk/types/tx" - "github.com/cosmos/cosmos-sdk/x/auth/tx" "github.com/sei-protocol/sei-chain/app" - "github.com/sei-protocol/sei-chain/utils" dextypes "github.com/sei-protocol/sei-chain/x/dex/types" - "google.golang.org/grpc" + oracletypes "github.com/sei-protocol/sei-chain/x/oracle/types" ) -const ( - Bank string = "bank" - FailureBankMalformed string = "failure_bank_malformed" - FailureBankInvalid string = "failure_bank_invalid" - FailureDexMalformed string = "failure_dex_malformed" - FailureDexInvalid string = "failure_dex_invalid" - Dex string = "dex" - Staking string = "staking" - Tokenfactory string = "tokenfactory" - Limit string = "limit" - Market string = "market" -) - -type EncodingConfig struct { - InterfaceRegistry types.InterfaceRegistry - // NOTE: this field will be renamed to Codec - Marshaler codec.Codec - TxConfig client.TxConfig - Amino *codec.LegacyAmino -} - -type Config struct { - BatchSize uint64 `json:"batch_size"` - ChainID string `json:"chain_id"` - OrdersPerBlock uint64 `json:"orders_per_block"` - Rounds uint64 `json:"rounds"` - MessageType string `json:"message_type"` - PriceDistr NumericDistribution `json:"price_distribution"` - QuantityDistr NumericDistribution `json:"quantity_distribution"` - DexMsgTypeDistr MsgTypeDistribution `json:"dex_message_type_distribution"` - ContractDistr ContractDistributions `json:"contract_distribution"` - Constant bool `json:"constant"` - ConstLoadInterval int64 `json:"const_load_interval"` -} - -type NumericDistribution struct { - Min sdk.Dec `json:"min"` - Max sdk.Dec `json:"max"` - NumDistinct int64 `json:"number_of_distinct_values"` -} - -func (d *NumericDistribution) Sample() sdk.Dec { - steps := sdk.NewDec(rand.Int63n(d.NumDistinct)) - return d.Min.Add(d.Max.Sub(d.Min).QuoInt64(d.NumDistinct).Mul(steps)) -} - -// Invalid numeric distribution sample -func (d *NumericDistribution) InvalidSample() sdk.Dec { - steps := sdk.NewDec(rand.Int63n(d.NumDistinct)) - if rand.Float64() < 0.5 { - return d.Min.Add(d.Max.Sub(d.Min).QuoInt64(d.NumDistinct).Mul(steps)) - } - return d.Max.Add(d.Max.Sub(d.Min).QuoInt64(d.NumDistinct).Mul(steps)) -} - -type MsgTypeDistribution struct { - LimitOrderPct sdk.Dec `json:"limit_order_percentage"` - MarketOrderPct sdk.Dec `json:"market_order_percentage"` -} - -func (d *MsgTypeDistribution) Sample() string { - if !d.LimitOrderPct.Add(d.MarketOrderPct).Equal(sdk.OneDec()) { - panic("Distribution percentages must add up to 1") - } - randNum := sdk.MustNewDecFromStr(fmt.Sprintf("%f", rand.Float64())) - if randNum.LT(d.LimitOrderPct) { - return Limit - } - return "market" -} - -type ContractDistributions []ContractDistribution - -func (d *ContractDistributions) Sample() string { - if !utils.Reduce(*d, func(i ContractDistribution, o sdk.Dec) sdk.Dec { return o.Add(i.Percentage) }, sdk.ZeroDec()).Equal(sdk.OneDec()) { - panic("Distribution percentages must add up to 1") - } - randNum := sdk.MustNewDecFromStr(fmt.Sprintf("%f", rand.Float64())) - cumPct := sdk.ZeroDec() - for _, dist := range *d { - cumPct = cumPct.Add(dist.Percentage) - if randNum.LTE(cumPct) { - return dist.ContractAddr - } - } - panic("this should never be triggered") -} - -type ContractDistribution struct { - ContractAddr string `json:"contract_address"` - Percentage sdk.Dec `json:"percentage"` -} - -var ( - TestConfig EncodingConfig - TxClient typestx.ServiceClient - StakingQueryClient stakingtypes.QueryClient - TxHashFile *os.File - ChainID string - //// Staking specific variables - Validators []stakingtypes.Validator - // DelegationMap is a map of delegator -> validator -> delegated amount - DelegationMap map[string]map[string]int - //// Tokenfactory specific variables - TokenFactoryDenomOwner map[string]string -) +var TestConfig EncodingConfig const ( VortexData = "{\"position_effect\":\"Open\",\"leverage\":\"1\"}" @@ -164,112 +51,31 @@ func init() { app.ModuleBasics.RegisterInterfaces(TestConfig.InterfaceRegistry) } -func run(config Config) { - ChainID = config.ChainID - grpcConn, _ := grpc.Dial( - "127.0.0.1:9090", - grpc.WithInsecure(), - ) - defer grpcConn.Close() - TxClient = typestx.NewServiceClient(grpcConn) - StakingQueryClient = stakingtypes.NewQueryClient(grpcConn) - createOutputFiles() - var mu sync.Mutex - batchSize := config.BatchSize - if config.OrdersPerBlock < batchSize { - panic("Must have more orders per block than batch size") - } - setValidators(config) - DelegationMap = map[string]map[string]int{} - TokenFactoryDenomOwner = map[string]string{} - numberOfAccounts := config.OrdersPerBlock / batchSize * 2 // * 2 because we need two sets of accounts - activeAccounts := []int{} - inactiveAccounts := []int{} - for i := 0; i < int(numberOfAccounts); i++ { - if i%2 == 0 { - activeAccounts = append(activeAccounts, i) - } else { - inactiveAccounts = append(inactiveAccounts, i) - } +func run() { + client := NewLoadTestClient() + client.SetValidators() + config := client.LoadTestConfig + + defer client.Close() + + if config.TxsPerBlock < config.MsgsPerTx { + panic("Must have more TxsPerBlock than MsgsPerTx") } - wgs := []*sync.WaitGroup{} - sendersList := [][]func() string{} configString, _ := json.Marshal(config) - fmt.Printf("Running with \n %s \ns", string(configString)) + fmt.Printf("Running with \n %s \n", string(configString)) fmt.Printf("%s - Starting block prepare\n", time.Now().Format("2006-01-02T15:04:05")) - for i := 0; i < int(config.Rounds); i++ { - fmt.Printf("Preparing %d-th round\n", i) - wg := &sync.WaitGroup{} - var senders []func() string - wgs = append(wgs, wg) - for j, account := range activeAccounts { - key := GetKey(uint64(account)) + workgroups, sendersList := client.BuildTxs() - msg, failureExpected := generateMessage(config, key, batchSize) - txBuilder := TestConfig.TxConfig.NewTxBuilder() - _ = txBuilder.SetMsgs(msg) - seqDelta := uint64(i / 2) - mode := typestx.BroadcastMode_BROADCAST_MODE_SYNC - if j == len(activeAccounts)-1 { - mode = typestx.BroadcastMode_BROADCAST_MODE_BLOCK - } - // Note: There is a potential race condition here with seqnos - // in which a later seqno is delievered before an earlier seqno - // In practice, we haven't run into this issue so we'll leave this - // as is. - sender := SendTx(key, &txBuilder, mode, seqDelta, &mu, failureExpected) - wg.Add(1) - senders = append(senders, func() string { - defer wg.Done() - return sender() - }) - } - sendersList = append(sendersList, senders) - - inactiveAccounts, activeAccounts = activeAccounts, inactiveAccounts - } + client.SendTxs(workgroups, sendersList) - lastHeight := getLastHeight() - for i := 0; i < int(config.Rounds); i++ { - newHeight := getLastHeight() - for newHeight == lastHeight { - time.Sleep(10 * time.Millisecond) - newHeight = getLastHeight() - } - fmt.Printf("Sending %d-th block\n", i) - senders := sendersList[i] - wg := wgs[i] - for _, sender := range senders { - go sender() - } - wg.Wait() - lastHeight = newHeight - } + // Records the resulting TxHash to file + client.WriteTxHashToFile() fmt.Printf("%s - Finished\n", time.Now().Format("2006-01-02T15:04:05")) } -func createOutputFiles() { - userHomeDir, _ := os.UserHomeDir() - _ = os.Mkdir(filepath.Join(userHomeDir, "outputs"), os.ModePerm) - filename := filepath.Join(userHomeDir, "outputs", "test_tx_hash") - _ = os.Remove(filename) - file, _ := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) - TxHashFile = file -} - -func setValidators(config Config) { - if strings.Contains(config.MessageType, "staking") { - if resp, err := StakingQueryClient.Validators(context.Background(), &stakingtypes.QueryValidatorsRequest{}); err != nil { - panic(err) - } else { - Validators = resp.Validators - } - } -} - -func generateMessage(config Config, key cryptotypes.PrivKey, batchSize uint64) (sdk.Msg, bool) { +func (c *LoadTestClient) generateMessage(config Config, key cryptotypes.PrivKey, msgPerTx uint64) (sdk.Msg, bool) { var msg sdk.Msg messageTypes := strings.Split(config.MessageType, ",") rand.Seed(time.Now().UnixNano()) @@ -289,7 +95,7 @@ func generateMessage(config Config, key cryptotypes.PrivKey, batchSize uint64) ( price := config.PriceDistr.Sample() quantity := config.QuantityDistr.Sample() contract := config.ContractDistr.Sample() - orderPlacements := generateDexOrderPlacements(config, key, batchSize, price, quantity) + orderPlacements := generateDexOrderPlacements(config, key, msgPerTx, price, quantity) amount, err := sdk.ParseCoinsNormalized(fmt.Sprintf("%d%s", price.Mul(quantity).Ceil().RoundInt64(), "usei")) if err != nil { panic(err) @@ -301,23 +107,24 @@ func generateMessage(config Config, key cryptotypes.PrivKey, batchSize uint64) ( Funds: amount, } case Staking: + delegatorAddr := sdk.AccAddress(key.PubKey().Address()).String() - chosenValidator := Validators[rand.Intn(len(Validators))].OperatorAddress + chosenValidator := c.Validators[rand.Intn(len(c.Validators))].OperatorAddress // Randomly pick someone to redelegate / unbond from srcAddr := "" - for k := range DelegationMap[delegatorAddr] { + for k := range c.DelegationMap[delegatorAddr] { if k == chosenValidator { continue } srcAddr = k break } - msg = generateStakingMsg(delegatorAddr, chosenValidator, srcAddr) + msg = c.generateStakingMsg(delegatorAddr, chosenValidator, srcAddr) case Tokenfactory: denomCreatorAddr := sdk.AccAddress(key.PubKey().Address()).String() // No denoms, let's mint randNum := rand.Float64() - denom, ok := TokenFactoryDenomOwner[denomCreatorAddr] + denom, ok := c.TokenFactoryDenomOwner[denomCreatorAddr] switch { case !ok || randNum <= 0.33: subDenom := fmt.Sprintf("tokenfactory-created-denom-%d", time.Now().UnixMilli()) @@ -326,11 +133,11 @@ func generateMessage(config Config, key cryptotypes.PrivKey, batchSize uint64) ( Sender: denomCreatorAddr, Subdenom: subDenom, } - TokenFactoryDenomOwner[denomCreatorAddr] = denom + c.TokenFactoryDenomOwner[denomCreatorAddr] = denom case randNum <= 0.66: msg = &tokenfactorytypes.MsgMint{ Sender: denomCreatorAddr, - Amount: sdk.Coin{Denom: denom, Amount: sdk.NewInt(1000000)}, + Amount: sdk.Coin{Denom: denom, Amount: sdk.NewInt(10000000000000)}, } default: msg = &tokenfactorytypes.MsgBurn{ @@ -368,7 +175,7 @@ func generateMessage(config Config, key cryptotypes.PrivKey, batchSize uint64) ( price := config.PriceDistr.InvalidSample() quantity := config.QuantityDistr.InvalidSample() contract := config.ContractDistr.Sample() - orderPlacements := generateDexOrderPlacements(config, key, batchSize, price, quantity) + orderPlacements := generateDexOrderPlacements(config, key, msgPerTx, price, quantity) amount, err := sdk.ParseCoinsNormalized(fmt.Sprintf("%d%s", price.Mul(quantity).Ceil().RoundInt64(), "usei")) if err != nil { panic(err) @@ -383,7 +190,7 @@ func generateMessage(config Config, key cryptotypes.PrivKey, batchSize uint64) ( price := config.PriceDistr.Sample() quantity := config.QuantityDistr.Sample() contract := config.ContractDistr.Sample() - orderPlacements := generateDexOrderPlacements(config, key, batchSize, price, quantity) + orderPlacements := generateDexOrderPlacements(config, key, msgPerTx, price, quantity) var amountUsei int64 if rand.Float64() < 0.5 { amountUsei = 10000 * price.Mul(quantity).Ceil().RoundInt64() @@ -411,22 +218,26 @@ func generateMessage(config Config, key cryptotypes.PrivKey, batchSize uint64) ( return msg, false } -func generateDexOrderPlacements(config Config, key cryptotypes.PrivKey, batchSize uint64, price sdk.Dec, quantity sdk.Dec) []*dextypes.Order { - orderPlacements := []*dextypes.Order{} - var orderType dextypes.OrderType +func sampleDexOrderType(config Config) (orderType dextypes.OrderType) { if config.MessageType == "failure_bank_malformed" { orderType = -1 } else { - dexMsgType := config.DexMsgTypeDistr.Sample() - switch dexMsgType { + msgType := config.MsgTypeDistr.SampleDexMsgs() + switch msgType { case Limit: orderType = dextypes.OrderType_LIMIT case Market: orderType = dextypes.OrderType_MARKET default: - panic(fmt.Sprintf("Unknown message type %s\n", dexMsgType)) + panic(fmt.Sprintf("Unknown message type %s\n", msgType)) } } + return orderType +} + +func generateDexOrderPlacements(config Config, key cryptotypes.PrivKey, msgPerTx uint64, price sdk.Dec, quantity sdk.Dec) (orderPlacements []*dextypes.Order) { + orderType := sampleDexOrderType(config) + var direction dextypes.PositionDirection if rand.Float64() < 0.5 { direction = dextypes.PositionDirection_LONG @@ -435,7 +246,7 @@ func generateDexOrderPlacements(config Config, key cryptotypes.PrivKey, batchSiz } contract := config.ContractDistr.Sample() - for j := 0; j < int(batchSize); j++ { + for j := 0; j < int(msgPerTx); j++ { orderPlacements = append(orderPlacements, &dextypes.Order{ Account: sdk.AccAddress(key.PubKey().Address()).String(), ContractAddr: contract, @@ -451,29 +262,39 @@ func generateDexOrderPlacements(config Config, key cryptotypes.PrivKey, batchSiz return orderPlacements } -func generateStakingMsg(delegatorAddr string, chosenValidator string, srcAddr string) sdk.Msg { +func generateOracleMessage(key cryptotypes.PrivKey) sdk.Msg { + valAddr := sdk.ValAddress(key.PubKey().Address()).String() + addr := sdk.AccAddress(key.PubKey().Address()).String() + msg := &oracletypes.MsgAggregateExchangeRateVote{ + ExchangeRates: "1usei,2uatom", + Feeder: addr, + Validator: valAddr, + } + return msg +} + +func (c *LoadTestClient) generateStakingMsg(delegatorAddr string, chosenValidator string, srcAddr string) sdk.Msg { // Randomly unbond, redelegate or delegate // However, if there are no delegations, do so first var msg sdk.Msg - randNum := rand.Float64() - if _, ok := DelegationMap[delegatorAddr]; !ok || randNum <= 0.33 || srcAddr == "" { + msgType := c.LoadTestConfig.MsgTypeDistr.SampleStakingMsgs() + if _, ok := c.DelegationMap[delegatorAddr]; !ok || msgType == "delegate" || srcAddr == "" { msg = &stakingtypes.MsgDelegate{ DelegatorAddress: delegatorAddr, ValidatorAddress: chosenValidator, Amount: sdk.Coin{Denom: "usei", Amount: sdk.NewInt(1)}, } - DelegationMap[delegatorAddr] = map[string]int{} - DelegationMap[delegatorAddr][chosenValidator] = 1 + c.DelegationMap[delegatorAddr] = map[string]int{} + c.DelegationMap[delegatorAddr][chosenValidator] = 1 } else { - - if randNum <= 0.66 { + if msgType == "redelegate" { msg = &stakingtypes.MsgBeginRedelegate{ DelegatorAddress: delegatorAddr, ValidatorSrcAddress: srcAddr, ValidatorDstAddress: chosenValidator, Amount: sdk.Coin{Denom: "usei", Amount: sdk.NewInt(1)}, } - DelegationMap[delegatorAddr][chosenValidator]++ + c.DelegationMap[delegatorAddr][chosenValidator]++ } else { msg = &stakingtypes.MsgUndelegate{ DelegatorAddress: delegatorAddr, @@ -482,9 +303,9 @@ func generateStakingMsg(delegatorAddr string, chosenValidator string, srcAddr st } } // Update delegation map - DelegationMap[delegatorAddr][srcAddr]-- - if DelegationMap[delegatorAddr][srcAddr] == 0 { - delete(DelegationMap, delegatorAddr) + c.DelegationMap[delegatorAddr][srcAddr]-- + if c.DelegationMap[delegatorAddr][srcAddr] == 0 { + delete(c.DelegationMap, delegatorAddr) } } return msg @@ -507,15 +328,5 @@ func getLastHeight() int { } func main() { - flag.Parse() - config := Config{} - pwd, _ := os.Getwd() - - fileName := "/loadtest/config.json" - file, _ := os.ReadFile(pwd + fileName) - if err := json.Unmarshal(file, &config); err != nil { - panic(err) - } - - run(config) + run() } diff --git a/loadtest/scripts/metrics.py b/loadtest/scripts/metrics.py index 55074b6b30..d59c90e0de 100644 --- a/loadtest/scripts/metrics.py +++ b/loadtest/scripts/metrics.py @@ -41,17 +41,20 @@ def get_metrics(): for height in all_heights: block_info_list.append(get_block_info(height)) # Skip first and last block since it may have high deviation if we start it at the end of the block + skip_edge_blocks = block_info_list[1:-1] total_duration = skip_edge_blocks[-1]["timestamp"] - skip_edge_blocks[0]["timestamp"] average_block_time = total_duration.total_seconds() / (len(skip_edge_blocks) - 1) total_txs_num = sum([block["number_of_txs"] for block in skip_edge_blocks]) average_txs_num = total_txs_num / len(skip_edge_blocks) + return { "Summary (excl. edge block)": { "average_block_time": average_block_time, "average_throughput_per_block": average_txs_num, "average_throughput_per_sec": average_txs_num / average_block_time, "number_of_full_blocks": len(skip_edge_blocks), + "full_blocks": all_heights[1:-1], "total_txs_num": total_txs_num, }, "Detail (incl. edge blocks)": { diff --git a/loadtest/scripts/populate_genesis_accounts.py b/loadtest/scripts/populate_genesis_accounts.py index 100d1b35b6..7255829242 100644 --- a/loadtest/scripts/populate_genesis_accounts.py +++ b/loadtest/scripts/populate_genesis_accounts.py @@ -5,11 +5,14 @@ import threading import time +from pathlib import Path + PARALLEISM=32 # Global Variable used for accounts # Does not need to be thread safe, each thread should only be writing to its own index global_accounts_mapping = {} +home_path = os.path.expanduser('~') def add_key(account_name, local=False): if local: @@ -34,7 +37,6 @@ def add_account(account_name, address, mnemonic, local=False): else: add_account_cmd = f"printf '12345678\n' | ~/go/bin/seid add-genesis-account {address} 1000000000usei" - home_path = os.path.expanduser('~') filename = f"{home_path}/test_accounts/{account_name}.json" os.makedirs(os.path.dirname(filename), exist_ok=True) with open(filename, 'w') as f: @@ -91,6 +93,28 @@ def create_genesis_account(account_index, account_name, local=False): } } + if retry_counter >= 1000: + exit(-1) + + global_accounts_mapping[account_index] = { + "balance": { + "address": address, + "coins": [ + { + "denom": "usei", + "amount": "1000000000" + } + ] + }, + "account": { + "@type": "/cosmos.auth.v1beta1.BaseAccount", + "address": address, + "pub_key": None, + "account_number": "0", + "sequence": "0" + } + } + def bulk_create_genesis_accounts(number_of_accounts, start_idx, is_local=False): for i in range(start_idx, start_idx + number_of_accounts): @@ -98,14 +122,14 @@ def bulk_create_genesis_accounts(number_of_accounts, start_idx, is_local=False): print(f"Created account {i}") -def read_genesis_file(): - with open("/root/.sei/config/genesis.json", 'r') as f: +def read_genesis_file(genesis_json_file_path): + with open(genesis_json_file_path, 'r') as f: return json.load(f) -def write_genesis_file(data): +def write_genesis_file(genesis_json_file_path, data): print("Writing results to genesis file") - with open("/root/.sei/config/genesis.json", 'w') as f: + with open(genesis_json_file_path, 'w') as f: json.dump(data, f, indent=4) @@ -116,7 +140,8 @@ def main(): if len(args) > 1 and args[1] == "loc": is_local = True - genesis_file = read_genesis_file() + genesis_json_file_path = f"{home_path}/.sei/config/genesis.json" + genesis_file = read_genesis_file(genesis_json_file_path) num_threads = number_of_accounts // PARALLEISM threads = [] @@ -145,7 +170,7 @@ def main(): print(f'Created {num_accounts_created} accounts') assert num_accounts_created >= number_of_accounts - write_genesis_file(genesis_file) + write_genesis_file(genesis_json_file_path, genesis_file) if __name__ == "__main__": main() diff --git a/loadtest/sign.go b/loadtest/sign.go index 0ebaccd94e..882b5f1f5c 100644 --- a/loadtest/sign.go +++ b/loadtest/sign.go @@ -6,12 +6,16 @@ import ( "io" "os" "path/filepath" + "sync" "time" "github.com/cosmos/cosmos-sdk/client" clienttx "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/codec/legacy" + "github.com/cosmos/cosmos-sdk/crypto" "github.com/cosmos/cosmos-sdk/crypto/hd" "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/cosmos/cosmos-sdk/crypto/keys/sr25519" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/tx/signing" @@ -26,7 +30,43 @@ type AccountInfo struct { Mnemonic string `json:"mnemonic"` } -func GetKey(accountIdx uint64) cryptotypes.PrivKey { +type SignerInfo struct { + AccountNumber uint64 + SequenceNumber uint64 + mutex *sync.Mutex +} + +func NewSignerInfo(accountNumber uint64, sequenceNumber uint64) *SignerInfo { + return &SignerInfo{ + AccountNumber: accountNumber, + SequenceNumber: sequenceNumber, + mutex: &sync.Mutex{}, + } +} + +func (si *SignerInfo) IncrementAccountNumber() { + si.mutex.Lock() + defer si.mutex.Unlock() + si.AccountNumber++ +} + +type SignerClient struct { + CachedAccountSeqNum *sync.Map + CachedAccountKey *sync.Map +} + +func NewSignerClient() *SignerClient { + return &SignerClient{ + CachedAccountSeqNum: &sync.Map{}, + CachedAccountKey: &sync.Map{}, + } +} + +func (sc *SignerClient) GetKey(accountIdx uint64) cryptotypes.PrivKey { + if val, ok := sc.CachedAccountKey.Load(accountIdx); ok { + privKey := val.(cryptotypes.PrivKey) + return privKey + } userHomeDir, _ := os.UserHomeDir() accountKeyFilePath := filepath.Join(userHomeDir, "test_accounts", fmt.Sprintf("ta%d.json", accountIdx)) jsonFile, err := os.Open(accountKeyFilePath) @@ -48,12 +88,56 @@ func GetKey(accountIdx uint64) cryptotypes.PrivKey { algo, _ := keyring.NewSigningAlgoFromString(algoStr, keyringAlgos) hdpath := hd.CreateHDPath(sdk.GetConfig().GetCoinType(), 0, 0).String() derivedPriv, _ := algo.Derive()(accountInfo.Mnemonic, "", hdpath) - return algo.Generate()(derivedPriv) + privKey := algo.Generate()(derivedPriv) + + // Cache this so we don't need to regenerate it + sc.CachedAccountKey.Store(accountIdx, privKey) + return privKey +} + +func (sc *SignerClient) GetValKeys() []cryptotypes.PrivKey { + valKeys := []cryptotypes.PrivKey{} + userHomeDir, _ := os.UserHomeDir() + valKeysFilePath := filepath.Join(userHomeDir, "exported_keys") + files, _ := os.ReadDir(valKeysFilePath) + for _, fn := range files { + // we dont expect subdirectories, so we can just handle files + valKeyFile := filepath.Join(valKeysFilePath, fn.Name()) + privKeyBz, err := os.ReadFile(valKeyFile) + if err != nil { + panic(err) + } + + privKeyBytes, algo, err := crypto.UnarmorDecryptPrivKey(string(privKeyBz), "12345678") + if err != nil { + panic(err) + } + + var privKey cryptotypes.PrivKey + if algo == string(hd.Sr25519Type) { + typedKey := &sr25519.PrivKey{} + if err := typedKey.UnmarshalJSON(privKeyBytes); err != nil { + panic(err) + } + privKey = typedKey + } else { + privKey, err = legacy.PrivKeyFromBytes(privKeyBytes) + if err != nil { + panic(err) + } + } + + valKeys = append(valKeys, privKey) + } + return valKeys } -func SignTx(txBuilder *client.TxBuilder, privKey cryptotypes.PrivKey, seqDelta uint64) { +func (sc *SignerClient) SignTx(chainID string, txBuilder *client.TxBuilder, privKey cryptotypes.PrivKey, seqDelta uint64) { var sigsV2 []signing.SignatureV2 - accountNum, seqNum := GetAccountNumberSequenceNumber(privKey) + signerInfo := sc.GetAccountNumberSequenceNumber(privKey) + accountNum := signerInfo.AccountNumber + seqNum := signerInfo.SequenceNumber + seqNum += seqDelta sigV2 := signing.SignatureV2{ PubKey: privKey.PubKey(), @@ -67,7 +151,7 @@ func SignTx(txBuilder *client.TxBuilder, privKey cryptotypes.PrivKey, seqDelta u _ = (*txBuilder).SetSignatures(sigsV2...) sigsV2 = []signing.SignatureV2{} signerData := xauthsigning.SignerData{ - ChainID: ChainID, + ChainID: chainID, AccountNumber: accountNum, Sequence: seqNum, } @@ -83,7 +167,13 @@ func SignTx(txBuilder *client.TxBuilder, privKey cryptotypes.PrivKey, seqDelta u _ = (*txBuilder).SetSignatures(sigsV2...) } -func GetAccountNumberSequenceNumber(privKey cryptotypes.PrivKey) (uint64, uint64) { +func (sc *SignerClient) GetAccountNumberSequenceNumber(privKey cryptotypes.PrivKey) SignerInfo { + if val, ok := sc.CachedAccountSeqNum.Load(privKey); ok { + signerinfo := val.(SignerInfo) + signerinfo.IncrementAccountNumber() + return signerinfo + } + hexAccount := privKey.PubKey().Address() address, err := sdk.AccAddressFromHex(hexAccount.String()) if err != nil { @@ -110,5 +200,8 @@ func GetAccountNumberSequenceNumber(privKey cryptotypes.PrivKey) (uint64, uint64 panic(err) } } - return account, seq + + signerInfo := *NewSignerInfo(account, seq) + sc.CachedAccountSeqNum.Store(privKey, signerInfo) + return signerInfo } diff --git a/loadtest/tx.go b/loadtest/tx.go index 10af6ec221..5b0b8698f4 100644 --- a/loadtest/tx.go +++ b/loadtest/tx.go @@ -3,7 +3,6 @@ package main import ( "context" "fmt" - "sync" "time" "github.com/cosmos/cosmos-sdk/client" @@ -18,17 +17,17 @@ func SendTx( txBuilder *client.TxBuilder, mode typestx.BroadcastMode, seqDelta uint64, - mu *sync.Mutex, failureExpected bool, -) func() string { + loadtestClient LoadTestClient, +) func() string { // TODO: do we need to return string? (*txBuilder).SetGasLimit(200000000) (*txBuilder).SetFeeAmount([]sdk.Coin{ sdk.NewCoin("usei", sdk.NewInt(10000000)), }) - SignTx(txBuilder, key, seqDelta) + loadtestClient.SignerClient.SignTx(loadtestClient.ChainID, txBuilder, key, seqDelta) txBytes, _ := TestConfig.TxConfig.TxEncoder()((*txBuilder).GetTx()) return func() string { - grpcRes, err := TxClient.BroadcastTx( + grpcRes, err := loadtestClient.TxClient.BroadcastTx( context.Background(), &typestx.BroadcastTxRequest{ Mode: mode, @@ -46,7 +45,7 @@ func SendTx( // retry after a second until either succeed or fail for some other reason fmt.Printf("Mempool full\n") time.Sleep(1 * time.Second) - grpcRes, err = TxClient.BroadcastTx( + grpcRes, err = loadtestClient.TxClient.BroadcastTx( context.Background(), &typestx.BroadcastTxRequest{ Mode: mode, @@ -64,11 +63,7 @@ func SendTx( if grpcRes.TxResponse.Code != 0 { fmt.Printf("Error: %d, %s\n", grpcRes.TxResponse.Code, grpcRes.TxResponse.RawLog) } else { - mu.Lock() - defer mu.Unlock() - if _, err := TxHashFile.WriteString(fmt.Sprintf("%s\n", grpcRes.TxResponse.TxHash)); err != nil { - panic(err) - } + loadtestClient.AppendTxHash(grpcRes.TxResponse.TxHash) return grpcRes.TxResponse.TxHash } return "" diff --git a/loadtest/types.go b/loadtest/types.go new file mode 100644 index 0000000000..70946a3574 --- /dev/null +++ b/loadtest/types.go @@ -0,0 +1,129 @@ +package main + +import ( + "fmt" + "math/rand" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/sei-protocol/sei-chain/utils" +) + +const ( + Bank string = "bank" + FailureBankMalformed string = "failure_bank_malformed" + FailureBankInvalid string = "failure_bank_invalid" + FailureDexMalformed string = "failure_dex_malformed" + FailureDexInvalid string = "failure_dex_invalid" + Dex string = "dex" + Staking string = "staking" + Tokenfactory string = "tokenfactory" + Limit string = "limit" + Market string = "market" +) + +type Config struct { + ChainID string `json:"chain_id"` + TxsPerBlock uint64 `json:"txs_per_block"` + MsgsPerTx uint64 `json:"msgs_per_tx"` + Rounds uint64 `json:"rounds"` + MessageType string `json:"message_type"` + RunOracle bool `json:"run_oracle"` + PriceDistr NumericDistribution `json:"price_distribution"` + QuantityDistr NumericDistribution `json:"quantity_distribution"` + MsgTypeDistr MsgTypeDistribution `json:"message_type_distribution"` + ContractDistr ContractDistributions `json:"contract_distribution"` + Constant bool `json:"constant"` + ConstLoadInterval int64 `json:"const_load_interval"` +} + +type EncodingConfig struct { + InterfaceRegistry types.InterfaceRegistry + // NOTE: this field will be renamed to Codec + Marshaler codec.Codec + TxConfig client.TxConfig + Amino *codec.LegacyAmino +} + +type NumericDistribution struct { + Min sdk.Dec `json:"min"` + Max sdk.Dec `json:"max"` + NumDistinct int64 `json:"number_of_distinct_values"` +} + +func (d *NumericDistribution) Sample() sdk.Dec { + steps := sdk.NewDec(rand.Int63n(d.NumDistinct)) + return d.Min.Add(d.Max.Sub(d.Min).QuoInt64(d.NumDistinct).Mul(steps)) +} + +// Invalid numeric distribution sample +func (d *NumericDistribution) InvalidSample() sdk.Dec { + steps := sdk.NewDec(rand.Int63n(d.NumDistinct)) + if rand.Float64() < 0.5 { + return d.Min.Add(d.Max.Sub(d.Min).QuoInt64(d.NumDistinct).Mul(steps)) + } + return d.Max.Add(d.Max.Sub(d.Min).QuoInt64(d.NumDistinct).Mul(steps)) +} + +type DexMsgTypeDistribution struct { + LimitOrderPct sdk.Dec `json:"limit_order_percentage"` + MarketOrderPct sdk.Dec `json:"market_order_percentage"` +} + +type StakingMsgTypeDistribution struct { + DelegatePct sdk.Dec `json:"delegate_percentage"` + UndelegatePct sdk.Dec `json:"undelegate_percentage"` + BeginRedelegatePct sdk.Dec `json:"begin_redelegate_percentage"` +} +type MsgTypeDistribution struct { + Dex DexMsgTypeDistribution `json:"dex"` + Staking StakingMsgTypeDistribution `json:"staking"` +} + +func (d *MsgTypeDistribution) SampleDexMsgs() string { + if !d.Dex.LimitOrderPct.Add(d.Dex.MarketOrderPct).Equal(sdk.OneDec()) { + panic("Distribution percentages must add up to 1") + } + randNum := sdk.MustNewDecFromStr(fmt.Sprintf("%f", rand.Float64())) + if randNum.LT(d.Dex.LimitOrderPct) { + return Limit + } + return Market +} + +func (d *MsgTypeDistribution) SampleStakingMsgs() string { + if !d.Staking.DelegatePct.Add(d.Staking.UndelegatePct).Add(d.Staking.BeginRedelegatePct).Equal(sdk.OneDec()) { + panic("Distribution percentages must add up to 1") + } + randNum := sdk.MustNewDecFromStr(fmt.Sprintf("%f", rand.Float64())) + if randNum.LT(d.Staking.DelegatePct) { + return "delegate" + } else if randNum.LT(d.Staking.DelegatePct.Add(d.Staking.UndelegatePct)) { + return "undelegate" + } + return "begin_redelegate" +} + +type ContractDistributions []ContractDistribution + +func (d *ContractDistributions) Sample() string { + if !utils.Reduce(*d, func(i ContractDistribution, o sdk.Dec) sdk.Dec { return o.Add(i.Percentage) }, sdk.ZeroDec()).Equal(sdk.OneDec()) { + panic("Distribution percentages must add up to 1") + } + randNum := sdk.MustNewDecFromStr(fmt.Sprintf("%f", rand.Float64())) + cumPct := sdk.ZeroDec() + for _, dist := range *d { + cumPct = cumPct.Add(dist.Percentage) + if randNum.LTE(cumPct) { + return dist.ContractAddr + } + } + panic("this should never be triggered") +} + +type ContractDistribution struct { + ContractAddr string `json:"contract_address"` + Percentage sdk.Dec `json:"percentage"` +} diff --git a/scripts/old_initialize_local.sh b/scripts/old_initialize_local.sh old mode 100644 new mode 100755 index a5d2352308..5047163db0 --- a/scripts/old_initialize_local.sh +++ b/scripts/old_initialize_local.sh @@ -1,48 +1,44 @@ -#!/bin/bash -echo -n OS Password: -read -s password -echo -echo -n Key Name: -read keyname -echo -echo -n Number of Test Accounts: -read numtestaccount -echo -docker stop jaeger -docker rm jaeger -docker run -d --name jaeger \ - -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \ - -p 5775:5775/udp \ - -p 6831:6831/udp \ - -p 6832:6832/udp \ - -p 5778:5778 \ - -p 16686:16686 \ - -p 14250:14250 \ - -p 14268:14268 \ - -p 14269:14269 \ - -p 9411:9411 \ - jaegertracing/all-in-one:1.33 +#!/bin/bash + +keyname=admin +#docker stop jaeger +#docker rm jaeger +#docker run -d --name jaeger \ +# -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \ +# -p 5775:5775/udp \ +# -p 6831:6831/udp \ +# -p 6832:6832/udp \ +# -p 5778:5778 \ +# -p 16686:16686 \ +# -p 14250:14250 \ +# -p 14268:14268 \ +# -p 14269:14269 \ +# -p 9411:9411 \ +# jaegertracing/all-in-one:1.33 +rm -rf ~/.sei echo "Building..." make install -echo $password | sudo -S rm -r ~/.sei/ -echo $password | sudo -S rm -r ~/test_accounts/ +#echo $password | sudo -S rm -r ~/.sei/ +#echo $password | sudo -S rm -r ~/test_accounts/ ~/go/bin/seid init demo --chain-id sei-chain -yes | ~/go/bin/seid keys add $keyname -yes | ~/go/bin/seid keys add faucet -printf '12345678\n' | ~/go/bin/seid add-genesis-account $(~/go/bin/seid keys show $keyname -a) 100000000000000000000usei,100000000000000000000uusdc,100000000000000000000uatom -printf '12345678\n' | ~/go/bin/seid add-genesis-account $(~/go/bin/seid keys show faucet -a) 100000000000000000000usei,100000000000000000000uusdc,100000000000000000000uatom -python3 ./loadtest/scripts/populate_genesis_accounts.py $numtestaccount loc -printf '12345678\n' | ~/go/bin/seid gentx $keyname 70000000000000000000usei --chain-id sei-chain -sed -i 's/mode = "full"/mode = "validator"/g' $HOME/.sei/config/config.toml -sed -i 's/indexer = \["null"\]/indexer = \["kv"\]/g' $HOME/.sei/config/config.toml +~/go/bin/seid keys add $keyname --keyring-backend test +#yes | ~/go/bin/seid keys add faucet +~/go/bin/seid add-genesis-account $(~/go/bin/seid keys show $keyname -a --keyring-backend test) 100000000000000000000usei,100000000000000000000uusdc,100000000000000000000uatom +~/go/bin/seid gentx $keyname 70000000000000000000usei --chain-id sei-chain --keyring-backend test +sed -i '' 's/mode = "full"/mode = "validator"/g' $HOME/.sei/config/config.toml +sed -i '' 's/indexer = \["null"\]/indexer = \["kv"\]/g' $HOME/.sei/config/config.toml KEY=$(jq '.pub_key' ~/.sei/config/priv_validator_key.json -c) jq '.validators = [{}]' ~/.sei/config/genesis.json > ~/.sei/config/tmp_genesis.json jq '.validators[0] += {"power":"70000000000000"}' ~/.sei/config/tmp_genesis.json > ~/.sei/config/tmp_genesis_2.json jq '.validators[0] += {"pub_key":'$KEY'}' ~/.sei/config/tmp_genesis_2.json > ~/.sei/config/tmp_genesis_3.json mv ~/.sei/config/tmp_genesis_3.json ~/.sei/config/genesis.json && rm ~/.sei/config/tmp_genesis.json && rm ~/.sei/config/tmp_genesis_2.json + +echo "Creating Accounts" +python3 loadtest/scripts/populate_genesis_accounts.py 50 loc + ~/go/bin/seid collect-gentxs cat ~/.sei/config/genesis.json | jq '.app_state["crisis"]["constant_fee"]["denom"]="usei"' > ~/.sei/config/tmp_genesis.json && mv ~/.sei/config/tmp_genesis.json ~/.sei/config/genesis.json cat ~/.sei/config/genesis.json | jq '.app_state["gov"]["deposit_params"]["min_deposit"][0]["denom"]="usei"' > ~/.sei/config/tmp_genesis.json && mv ~/.sei/config/tmp_genesis.json ~/.sei/config/genesis.json @@ -81,4 +77,4 @@ else fi # start the chain with log tracing -GORACE="log_path=/tmp/race/seid_race" ~/go/bin/seid start --trace --chain-id sei-chain \ No newline at end of file +GORACE="log_path=/tmp/race/seid_race" ~/go/bin/seid start --trace --chain-id sei-chain diff --git a/store/testutils.go b/store/testutils.go index 7a710bc745..86b0aa59bb 100644 --- a/store/testutils.go +++ b/store/testutils.go @@ -11,7 +11,7 @@ import ( func NewTestKVStore() types.KVStore { mem := dbadapter.Store{DB: dbm.NewMemDB()} - return cachekv.NewStore(mem) + return cachekv.NewStore(mem, storetypes.NewKVStoreKey("test")) } func NewTestCacheMultiStore(stores map[types.StoreKey]types.CacheWrapper) types.CacheMultiStore { diff --git a/testutil/network/network.go b/testutil/network/network.go index 84a7f7427e..67c394b375 100644 --- a/testutil/network/network.go +++ b/testutil/network/network.go @@ -60,6 +60,7 @@ func DefaultConfig() network.Config { wasm.EnableAllProposals, &simapp.EmptyAppOptions{}, nil, + app.EmptyACLOpts, baseapp.SetPruning(storetypes.NewPruningOptionsFromString(val.AppConfig.Pruning)), baseapp.SetMinGasPrices(val.AppConfig.MinGasPrices), ) diff --git a/utils/metrics/labels.go b/utils/metrics/labels.go new file mode 100644 index 0000000000..282c97d060 --- /dev/null +++ b/utils/metrics/labels.go @@ -0,0 +1,8 @@ +package metrics + +const ( + CONCURRENT = "concurrent" + SYNCHRONOUS = "synchronous" + GovMsgInBlock = "gov-msg-in-block" + FailedToBuild = "failed-to-build" +) diff --git a/utils/metrics/metrics_util.go b/utils/metrics/metrics_util.go index 6bd198ee7c..ec5ed3a0cb 100644 --- a/utils/metrics/metrics_util.go +++ b/utils/metrics/metrics_util.go @@ -44,3 +44,63 @@ func GaugeSeidVersionAndCommit(version string, commit string) { []metrics.Label{telemetry.NewLabel("seid_version", version), telemetry.NewLabel("commit", commit)}, ) } + +// sei_tx_process_type_count +func IncrTxProcessTypeCounter(processType string) { + metrics.IncrCounterWithLabels( + []string{"sei", "tx", "process", "type"}, + 1, + []metrics.Label{telemetry.NewLabel("type", processType)}, + ) +} + +// Measures the time taken to process a block by the process type +// Metric Names: +// +// sei_process_block_miliseconds +// sei_process_block_miliseconds_count +// sei_process_block_miliseconds_sum +func BlockProcessLatency(start time.Time, processType string) { + metrics.MeasureSinceWithLabels( + []string{"sei", "process", "block", "milliseconds"}, + start.UTC(), + []metrics.Label{telemetry.NewLabel("type", processType)}, + ) +} + +// Measures the time taken to execute a sudo msg +// Metric Names: +// +// sei_tx_process_type_count +func IncrDagBuildErrorCounter(reason string) { + metrics.IncrCounterWithLabels( + []string{"sei", "dag", "build", "error"}, + 1, + []metrics.Label{telemetry.NewLabel("reason", reason)}, + ) +} + +// Counts the number of concurrent transactions that failed +// Metric Names: +// +// sei_tx_concurrent_delivertx_error +func IncrFailedConcurrentDeliverTxCounter() { + metrics.IncrCounterWithLabels( + []string{"sei", "tx", "concurrent", "delievertx", "error"}, + 1, + []metrics.Label{}, + ) +} + +// Measures the time taken to execute a sudo msg +// Metric Names: +// +// sei_deliver_tx_duration_miliseconds +// sei_deliver_tx_duration_miliseconds_count +// sei_deliver_tx_duration_miliseconds_sum +func MeasureDeliverTxDuration(start time.Time) { + metrics.MeasureSince( + []string{"sei", "deliver", "tx", "milliseconds"}, + start.UTC(), + ) +} diff --git a/wasmbinding/encoder.go b/wasmbinding/encoder.go new file mode 100644 index 0000000000..b26971ebdf --- /dev/null +++ b/wasmbinding/encoder.go @@ -0,0 +1,43 @@ +package wasmbinding + +import ( + "encoding/json" + + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + dexwasm "github.com/sei-protocol/sei-chain/x/dex/client/wasm" + tokenfactorywasm "github.com/sei-protocol/sei-chain/x/tokenfactory/client/wasm" +) + +type SeiWasmMessage struct { + PlaceOrders json.RawMessage `json:"place_orders,omitempty"` + CancelOrders json.RawMessage `json:"cancel_orders,omitempty"` + CreateDenom json.RawMessage `json:"create_denom,omitempty"` + MintTokens json.RawMessage `json:"mint_tokens,omitempty"` + BurnTokens json.RawMessage `json:"burn_tokens,omitempty"` + ChangeAdmin json.RawMessage `json:"change_admin,omitempty"` +} + +func CustomEncoder(sender sdk.AccAddress, msg json.RawMessage) ([]sdk.Msg, error) { + var parsedMessage SeiWasmMessage + if err := json.Unmarshal(msg, &parsedMessage); err != nil { + return []sdk.Msg{}, sdkerrors.Wrap(err, "Error parsing Sei Wasm Message") + } + switch { + case parsedMessage.PlaceOrders != nil: + return dexwasm.EncodeDexPlaceOrders(parsedMessage.PlaceOrders, sender) + case parsedMessage.CancelOrders != nil: + return dexwasm.EncodeDexCancelOrders(parsedMessage.CancelOrders, sender) + case parsedMessage.CreateDenom != nil: + return tokenfactorywasm.EncodeTokenFactoryCreateDenom(parsedMessage.CreateDenom, sender) + case parsedMessage.MintTokens != nil: + return tokenfactorywasm.EncodeTokenFactoryMint(parsedMessage.MintTokens, sender) + case parsedMessage.BurnTokens != nil: + return tokenfactorywasm.EncodeTokenFactoryBurn(parsedMessage.BurnTokens, sender) + case parsedMessage.ChangeAdmin != nil: + return tokenfactorywasm.EncodeTokenFactoryChangeAdmin(parsedMessage.ChangeAdmin, sender) + default: + return []sdk.Msg{}, wasmvmtypes.UnsupportedRequest{Kind: "Unknown Sei Wasm Message"} + } +} diff --git a/wasmbinding/message_plugin.go b/wasmbinding/message_plugin.go index bdc16ed355..5ea56529e1 100644 --- a/wasmbinding/message_plugin.go +++ b/wasmbinding/message_plugin.go @@ -1,129 +1,160 @@ package wasmbinding import ( - "encoding/json" - wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" wasmvmtypes "github.com/CosmWasm/wasmvm/types" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" - "github.com/sei-protocol/sei-chain/wasmbinding/bindings" - dexwasm "github.com/sei-protocol/sei-chain/x/dex/client/wasm" - tokenfactorywasm "github.com/sei-protocol/sei-chain/x/tokenfactory/client/wasm" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + "github.com/cosmos/cosmos-sdk/x/accesscontrol" + aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + "github.com/sei-protocol/sei-chain/utils" ) -// CustomMessageDecorator returns decorator for custom CosmWasm bindings messages -func CustomMessageDecorator( +// forked from wasm +func CustomMessageHandler( router wasmkeeper.MessageRouter, - accountKeeper *authkeeper.AccountKeeper, -) func(wasmkeeper.Messenger) wasmkeeper.Messenger { - return func(old wasmkeeper.Messenger) wasmkeeper.Messenger { - return &CustomMessenger{ - router: router, - wrapped: old, - accountKeeper: accountKeeper, - } - } + channelKeeper wasmtypes.ChannelKeeper, + capabilityKeeper wasmtypes.CapabilityKeeper, + bankKeeper wasmtypes.Burner, + unpacker codectypes.AnyUnpacker, + portSource wasmtypes.ICS20TransferPortSource, + aclKeeper aclkeeper.Keeper, +) wasmkeeper.Messenger { + encoders := wasmkeeper.DefaultEncoders(unpacker, portSource) + encoders = encoders.Merge( + &wasmkeeper.MessageEncoders{ + Custom: CustomEncoder, + }) + return wasmkeeper.NewMessageHandlerChain( + NewSDKMessageDependencyDecorator(wasmkeeper.NewSDKMessageHandler(router, encoders), aclKeeper, encoders), + wasmkeeper.NewIBCRawPacketHandler(channelKeeper, capabilityKeeper), + wasmkeeper.NewBurnCoinMessageHandler(bankKeeper), + ) } -type CustomMessenger struct { - router wasmkeeper.MessageRouter - wrapped wasmkeeper.Messenger - accountKeeper *authkeeper.AccountKeeper +// SDKMessageHandler can handles messages that can be encoded into sdk.Message types and routed. +type SDKMessageDependencyDecorator struct { + wrapped wasmkeeper.Messenger + aclKeeper aclkeeper.Keeper + encoders wasmkeeper.MessageEncoders } -type SeiWasmMessage struct { - PlaceOrders json.RawMessage `json:"place_orders,omitempty"` - CancelOrders json.RawMessage `json:"cancel_orders,omitempty"` - CreateDenom json.RawMessage `json:"create_denom,omitempty"` - MintTokens json.RawMessage `json:"mint_tokens,omitempty"` - BurnTokens json.RawMessage `json:"burn_tokens,omitempty"` - ChangeAdmin json.RawMessage `json:"change_admin,omitempty"` -} - -var _ wasmkeeper.Messenger = &CustomMessenger{} - -// DispatchMsg executes on the bindingMsgs -func (m *CustomMessenger) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) ([]sdk.Event, [][]byte, error) { - if msg.Custom != nil { - return m.DispatchCustomMsg(ctx, contractAddr, contractIBCPortID, msg) +func NewSDKMessageDependencyDecorator(handler wasmkeeper.Messenger, aclKeeper aclkeeper.Keeper, encoders wasmkeeper.MessageEncoders) SDKMessageDependencyDecorator { + return SDKMessageDependencyDecorator{ + wrapped: handler, + aclKeeper: aclKeeper, + encoders: encoders, } - return m.wrapped.DispatchMsg(ctx, contractAddr, contractIBCPortID, msg) } -// DispatchCustomMsg function is forked from wasmd. sdk.Msg will be validated and routed to the corresponding module msg server in this function. -func (m *CustomMessenger) DispatchCustomMsg( - ctx sdk.Context, - contractAddr sdk.AccAddress, - contractIBCPortID string, - msg wasmvmtypes.CosmosMsg, -) (events []sdk.Event, data [][]byte, err error) { - var parsedMessage SeiWasmMessage - if err := json.Unmarshal(msg.Custom, &parsedMessage); err != nil { - return nil, nil, bindings.ErrParsingSeiWasmMsg +func BuildWasmDependencyLookupMap(accessOps []sdkacltypes.AccessOperation) map[acltypes.ResourceAccess]map[string]struct{} { + lookupMap := make(map[acltypes.ResourceAccess]map[string]struct{}) + for _, accessOp := range accessOps { + resourceAccess := acltypes.ResourceAccess{ + ResourceType: accessOp.ResourceType, + AccessType: accessOp.AccessType, + } + if _, ok := lookupMap[resourceAccess]; !ok { + // we haven't added any identifiers for this resource type, so lets initialize the nested map (set) + lookupMap[resourceAccess] = make(map[string]struct{}) + } + lookupMap[resourceAccess][accessOp.IdentifierTemplate] = struct{}{} } + return lookupMap +} - var sdkMsgs []sdk.Msg - switch { - case parsedMessage.PlaceOrders != nil: - sdkMsgs, err = dexwasm.EncodeDexPlaceOrders(parsedMessage.PlaceOrders, contractAddr) - case parsedMessage.CancelOrders != nil: - sdkMsgs, err = dexwasm.EncodeDexCancelOrders(parsedMessage.CancelOrders, contractAddr) - case parsedMessage.CreateDenom != nil: - sdkMsgs, err = tokenfactorywasm.EncodeTokenFactoryCreateDenom(parsedMessage.CreateDenom, contractAddr) - case parsedMessage.MintTokens != nil: - sdkMsgs, err = tokenfactorywasm.EncodeTokenFactoryMint(parsedMessage.MintTokens, contractAddr) - case parsedMessage.BurnTokens != nil: - sdkMsgs, err = tokenfactorywasm.EncodeTokenFactoryBurn(parsedMessage.BurnTokens, contractAddr) - case parsedMessage.ChangeAdmin != nil: - sdkMsgs, err = tokenfactorywasm.EncodeTokenFactoryChangeAdmin(parsedMessage.ChangeAdmin, contractAddr) - default: - sdkMsgs, err = []sdk.Msg{}, wasmvmtypes.UnsupportedRequest{Kind: "Unknown Sei Wasm Message"} +func GenerateAllowedResourceAccess(resource sdkacltypes.ResourceType, access sdkacltypes.AccessType) []acltypes.ResourceAccess { + // by default, write, and unknown are ok + accesses := []acltypes.ResourceAccess{ + { + ResourceType: resource, + AccessType: sdkacltypes.AccessType_WRITE, + }, + { + ResourceType: resource, + AccessType: sdkacltypes.AccessType_UNKNOWN, + }, } - if err != nil { - return nil, nil, err + if access == sdkacltypes.AccessType_READ { + accesses = append(accesses, acltypes.ResourceAccess{ + ResourceType: resource, + AccessType: access, + }) } + return accesses +} - for _, sdkMsg := range sdkMsgs { - res, err := m.handleSdkMessage(ctx, contractAddr, sdkMsg) - if err != nil { - return nil, nil, err +func AreDependenciesFulfilled(lookupMap map[acltypes.ResourceAccess]map[string]struct{}, accessOp sdkacltypes.AccessOperation) bool { + currResourceAccesses := GenerateAllowedResourceAccess(accessOp.ResourceType, accessOp.AccessType) + for _, currResourceAccess := range currResourceAccesses { + if identifierMap, ok := lookupMap[currResourceAccess]; ok { + if _, ok := identifierMap[accessOp.IdentifierTemplate]; ok { + // we found a proper listed dependency, we can go to the next access op + return true + } } - // append data - data = append(data, res.Data) - // append events - sdkEvents := make([]sdk.Event, len(res.Events)) - for i := range res.Events { - sdkEvents[i] = sdk.Event(res.Events[i]) + } + + // what about parent resources + parentResources := accessOp.ResourceType.GetParentResources() + // for each of the parent resources, we need at least one to be defined in the wasmDependencies + for _, parentResource := range parentResources { + // make parent resource access with same access type + parentResourceAccesses := GenerateAllowedResourceAccess(parentResource, accessOp.AccessType) + // for each of the parent resources, we check to see if its in the lookup map (identifier doesnt matter bc parent) + for _, parentResourceAccess := range parentResourceAccesses { + if _, parentResourcePresent := lookupMap[parentResourceAccess]; parentResourcePresent { + // we can continue to the next access op + return true + } } - events = append(events, sdkEvents...) } - return events, data, nil + return false } -// This function is forked from wasmd. sdk.Msg will be validated and routed to the corresponding module msg server in this function. -func (m *CustomMessenger) handleSdkMessage(ctx sdk.Context, contractAddr sdk.Address, msg sdk.Msg) (*sdk.Result, error) { - if err := msg.ValidateBasic(); err != nil { - return nil, err +func (decorator SDKMessageDependencyDecorator) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { + sdkMsgs, err := decorator.encoders.Encode(ctx, contractAddr, contractIBCPortID, msg) + if err != nil { + return nil, nil, err } - // make sure this account can send it - for _, acct := range msg.GetSigners() { - if !acct.Equals(contractAddr) { - return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "contract doesn't have permission") - } + // get the dependencies for the contract to validate against + // TODO: we need to carry wasmDependency in ctx instead of loading again here since here has no access to original msg payload + // which is required for populating id correctly. + wasmDependency, err := decorator.aclKeeper.GetWasmDependencyMapping(ctx, contractAddr, []byte{}, false) + // If no mapping exists, or mapping is disabled, this message would behave as blocking for all resources + if err == aclkeeper.ErrWasmDependencyMappingNotFound { + // no mapping, we can just continue + return decorator.wrapped.DispatchMsg(ctx, contractAddr, contractIBCPortID, msg) } - - // find the handler and execute it - if handler := m.router.Handler(msg); handler != nil { - // ADR 031 request type routing - msgResult, err := handler(ctx, msg) - return msgResult, err + if err != nil { + return nil, nil, err + } + if !wasmDependency.Enabled { + // if not enabled, just move on + // TODO: confirm that this is ok, is there ever a case where we should still verify dependencies for a disabled dependency? IDTS + return decorator.wrapped.DispatchMsg(ctx, contractAddr, contractIBCPortID, msg) + } + // convert wasm dependency to a map of resource access and identifier we can look up in + lookupMap := BuildWasmDependencyLookupMap( + utils.Map(wasmDependency.AccessOps, func(op sdkacltypes.AccessOperationWithSelector) sdkacltypes.AccessOperation { return *op.Operation }), + ) + // wasm dependency enabled, we need to validate the message dependencies + for _, msg := range sdkMsgs { + accessOps := decorator.aclKeeper.GetMessageDependencies(ctx, msg) + // go through each access op, and check if there is a completion signal for it OR a parent + for _, accessOp := range accessOps { + // first check for our specific resource access AND identifier template + depsFulfilled := AreDependenciesFulfilled(lookupMap, accessOp) + if !depsFulfilled { + emitIncorrectDependencyWasmEvent(ctx, contractAddr.String()) + return nil, nil, accesscontrol.ErrUnexpectedWasmDependency + } + } } - // legacy sdk.Msg routing - // Assuming that the app developer has migrated all their Msgs to - // proto messages and has registered all `Msg services`, then this - // path should never be called, because all those Msgs should be - // registered within the `msgServiceRouter` already. - return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "can't route message %+v", msg) + // we've gone through all of the messages + // and verified their dependencies with the declared dependencies in the wasm contract dependencies, we can process it now + return decorator.wrapped.DispatchMsg(ctx, contractAddr, contractIBCPortID, msg) } diff --git a/wasmbinding/query_plugin.go b/wasmbinding/query_plugin.go index f1dff2c603..db94e3162c 100644 --- a/wasmbinding/query_plugin.go +++ b/wasmbinding/query_plugin.go @@ -3,9 +3,14 @@ package wasmbinding import ( "encoding/json" + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" wasmvmtypes "github.com/CosmWasm/wasmvm/types" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/accesscontrol" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + acl "github.com/cosmos/cosmos-sdk/x/accesscontrol" + aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper" + "github.com/sei-protocol/sei-chain/utils" ) const ( @@ -42,3 +47,161 @@ func CustomQuerier(qp *QueryPlugin) func(ctx sdk.Context, request json.RawMessag } } } + +type CustomQueryHandler struct { + QueryPlugins wasmkeeper.QueryPlugins + aclKeeper aclkeeper.Keeper +} + +func (queryHandler CustomQueryHandler) HandleQuery(ctx sdk.Context, caller sdk.AccAddress, request wasmvmtypes.QueryRequest) ([]byte, error) { + // TODO: we need to carry wasmDependency in ctx instead of loading again here since here has no access to original msg payload + // which is required for populating id correctly. + wasmDependency, err := queryHandler.aclKeeper.GetWasmDependencyMapping(ctx, caller, []byte{}, false) + // If no mapping exists, or mapping is disabled, this message would behave as blocking for all resources + needToCheckDependencies := true + if err == aclkeeper.ErrWasmDependencyMappingNotFound { + // no mapping, we can just continue + needToCheckDependencies = false + } + if err != nil { + return nil, err + } + if !wasmDependency.Enabled { + needToCheckDependencies = false + } + lookupMap := BuildWasmDependencyLookupMap( + utils.Map(wasmDependency.AccessOps, func(op accesscontrol.AccessOperationWithSelector) accesscontrol.AccessOperation { return *op.Operation }), + ) + if request.Bank != nil { + // check for BANK resource type + accessOp := accesscontrol.AccessOperation{ + ResourceType: accesscontrol.ResourceType_KV_BANK, + AccessType: accesscontrol.AccessType_READ, + // TODO: should IdentifierTemplate be based on the actual request? + IdentifierTemplate: "*", + } + if needToCheckDependencies { + if !AreDependenciesFulfilled(lookupMap, accessOp) { + emitIncorrectDependencyWasmEvent(ctx, caller.String()) + return nil, acl.ErrUnexpectedWasmDependency + } + } + return queryHandler.QueryPlugins.Bank(ctx, request.Bank) + + } + if request.Custom != nil { + // TODO: specially break down the custom + var contractQuery SeiQueryWrapper + if err := json.Unmarshal(request.Custom, &contractQuery); err != nil { + return nil, sdkerrors.Wrap(err, "Error parsing request data") + } + resourceType := accesscontrol.ResourceType_ANY + switch contractQuery.Route { + case OracleRoute: + resourceType = accesscontrol.ResourceType_KV_ORACLE + case DexRoute: + resourceType = accesscontrol.ResourceType_KV_DEX + case EpochRoute: + resourceType = accesscontrol.ResourceType_KV_EPOCH + case TokenFactoryRoute: + resourceType = accesscontrol.ResourceType_KV // TODO: change this to tokenfactory when rebasing a newer sei cosmos version with the enum + } + accessOp := accesscontrol.AccessOperation{ + ResourceType: resourceType, + AccessType: accesscontrol.AccessType_READ, + IdentifierTemplate: "*", + } + if needToCheckDependencies { + if !AreDependenciesFulfilled(lookupMap, accessOp) { + emitIncorrectDependencyWasmEvent(ctx, caller.String()) + return nil, acl.ErrUnexpectedWasmDependency + } + } + return queryHandler.QueryPlugins.Custom(ctx, request.Custom) + } + if request.IBC != nil { + // check for ANY resource type + // TODO: do we need a special resource type for IBC? + accessOp := accesscontrol.AccessOperation{ + ResourceType: accesscontrol.ResourceType_ANY, + AccessType: accesscontrol.AccessType_READ, + IdentifierTemplate: "*", + } + if needToCheckDependencies { + if !AreDependenciesFulfilled(lookupMap, accessOp) { + emitIncorrectDependencyWasmEvent(ctx, caller.String()) + return nil, acl.ErrUnexpectedWasmDependency + } + } + return queryHandler.QueryPlugins.IBC(ctx, caller, request.IBC) + } + if request.Staking != nil { + // check for STAKING resource type + accessOp := accesscontrol.AccessOperation{ + ResourceType: accesscontrol.ResourceType_KV_STAKING, + AccessType: accesscontrol.AccessType_READ, + IdentifierTemplate: "*", + } + if needToCheckDependencies { + if !AreDependenciesFulfilled(lookupMap, accessOp) { + emitIncorrectDependencyWasmEvent(ctx, caller.String()) + return nil, acl.ErrUnexpectedWasmDependency + } + } + return queryHandler.QueryPlugins.Staking(ctx, request.Staking) + } + if request.Stargate != nil { + // check for ANY resource type + // TODO: determine what Stargate dependency granularity looks like + accessOp := accesscontrol.AccessOperation{ + ResourceType: accesscontrol.ResourceType_ANY, + AccessType: accesscontrol.AccessType_READ, + IdentifierTemplate: "*", + } + if needToCheckDependencies { + if !AreDependenciesFulfilled(lookupMap, accessOp) { + emitIncorrectDependencyWasmEvent(ctx, caller.String()) + return nil, acl.ErrUnexpectedWasmDependency + } + } + return queryHandler.QueryPlugins.Stargate(ctx, request.Stargate) + } + if request.Wasm != nil { + // check for WASM resource type + accessOp := accesscontrol.AccessOperation{ + ResourceType: accesscontrol.ResourceType_KV_WASM, + AccessType: accesscontrol.AccessType_READ, + IdentifierTemplate: "*", + } + if needToCheckDependencies { + if !AreDependenciesFulfilled(lookupMap, accessOp) { + emitIncorrectDependencyWasmEvent(ctx, caller.String()) + return nil, acl.ErrUnexpectedWasmDependency + } + } + return queryHandler.QueryPlugins.Wasm(ctx, request.Wasm) + } + return nil, wasmvmtypes.Unknown{} +} + +func NewCustomQueryHandler(queryPlugins wasmkeeper.QueryPlugins, aclKeeper aclkeeper.Keeper) wasmkeeper.WasmVMQueryHandler { + return CustomQueryHandler{ + QueryPlugins: queryPlugins, + aclKeeper: aclKeeper, + } +} + +func CustomQueryHandlerDecorator(aclKeeper aclkeeper.Keeper, customQueryPlugin QueryPlugin) func(wasmkeeper.WasmVMQueryHandler) wasmkeeper.WasmVMQueryHandler { + // validate stuff, otherwise use default handler + return func(old wasmkeeper.WasmVMQueryHandler) wasmkeeper.WasmVMQueryHandler { + queryPlugins, ok := old.(wasmkeeper.QueryPlugins) + if !ok { + panic("Invalid query plugins") + } + + queryPlugins = queryPlugins.Merge(&wasmkeeper.QueryPlugins{ + Custom: CustomQuerier(&customQueryPlugin), + }) + return NewCustomQueryHandler(queryPlugins, aclKeeper) + } +} diff --git a/wasmbinding/test/message_handler_test.go b/wasmbinding/test/message_handler_test.go new file mode 100644 index 0000000000..d72bc219d9 --- /dev/null +++ b/wasmbinding/test/message_handler_test.go @@ -0,0 +1,141 @@ +package wasmbinding + +import ( + "encoding/json" + "testing" + + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + "github.com/cosmos/cosmos-sdk/x/accesscontrol" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/sei-protocol/sei-chain/app" + "github.com/sei-protocol/sei-chain/wasmbinding" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/proto/tendermint/types" +) + +type MockMessenger struct{} + +func (m MockMessenger) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { + return []sdk.Event{{ + Type: "test", + Attributes: []abci.EventAttribute{}, + }}, nil, nil +} + +func TestMessageHandlerDependencyDecorator(t *testing.T) { + app := app.Setup(false) + contractAddr, err := sdk.AccAddressFromBech32("sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw") + require.NoError(t, err) + defaultEncoders := wasmkeeper.DefaultEncoders(app.AppCodec(), app.TransferKeeper) + dependencyDecorator := wasmbinding.NewSDKMessageDependencyDecorator(MockMessenger{}, app.AccessControlKeeper, defaultEncoders) + testContext := app.NewContext(false, types.Header{}) + + // setup bank send message with aclkeeper + app.AccessControlKeeper.SetResourceDependencyMapping(testContext, sdkacltypes.MessageDependencyMapping{ + MessageKey: string(acltypes.GenerateMessageKey(&banktypes.MsgSend{})), + AccessOps: []sdkacltypes.AccessOperation{ + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV, + IdentifierTemplate: "*", + }, + *acltypes.CommitAccessOp(), + }, + DynamicEnabled: false, + }) + + // setup the wasm contract's dependency mapping + app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, sdkacltypes.WasmDependencyMapping{ + Enabled: true, + AccessOps: []sdkacltypes.AccessOperationWithSelector{ + { + Operation: &sdkacltypes.AccessOperation{ + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_ANY, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + + events, _, _ := dependencyDecorator.DispatchMsg(testContext, contractAddr, "test", wasmvmtypes.CosmosMsg{ + Bank: &wasmvmtypes.BankMsg{ + Send: &wasmvmtypes.SendMsg{ + ToAddress: "sdfasdf", + Amount: []wasmvmtypes.Coin{ + { + Denom: "usei", + Amount: "12345", + }, + }, + }, + }, + }) + // we should have received the test event + require.Equal(t, []sdk.Event{ + { + Type: "test", + Attributes: []abci.EventAttribute{}, + }, + }, events) + + app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, sdkacltypes.WasmDependencyMapping{ + Enabled: true, + AccessOps: []sdkacltypes.AccessOperationWithSelector{ + { + Operation: &sdkacltypes.AccessOperation{ + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV, + IdentifierTemplate: "otherIdentifier", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + + _, _, err = dependencyDecorator.DispatchMsg(testContext, contractAddr, "test", wasmvmtypes.CosmosMsg{ + Bank: &wasmvmtypes.BankMsg{ + Send: &wasmvmtypes.SendMsg{ + ToAddress: "sdfasdf", + Amount: []wasmvmtypes.Coin{ + { + Denom: "usei", + Amount: "12345", + }, + }, + }, + }, + }) + // we expect an error now + require.Error(t, accesscontrol.ErrUnexpectedWasmDependency, err) + + // reenable wasm mapping that's correct + app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, sdkacltypes.WasmDependencyMapping{ + Enabled: true, + AccessOps: []sdkacltypes.AccessOperationWithSelector{ + { + Operation: &sdkacltypes.AccessOperation{ + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + // lets try with a message that wont decode properly + _, _, err = dependencyDecorator.DispatchMsg(testContext, contractAddr, "test", wasmvmtypes.CosmosMsg{ + Custom: json.RawMessage{}, + }) + require.Error(t, wasmtypes.ErrUnknownMsg, err) +} diff --git a/wasmbinding/test/query_test.go b/wasmbinding/test/query_test.go index 924652deed..21c7dfc18c 100644 --- a/wasmbinding/test/query_test.go +++ b/wasmbinding/test/query_test.go @@ -6,8 +6,13 @@ import ( "testing" "time" + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + wasmvmtypes "github.com/CosmWasm/wasmvm/types" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/accesscontrol" + acl "github.com/cosmos/cosmos-sdk/x/accesscontrol" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" "github.com/sei-protocol/sei-chain/app" keepertest "github.com/sei-protocol/sei-chain/testutil/keeper" "github.com/sei-protocol/sei-chain/wasmbinding" @@ -27,6 +32,7 @@ import ( tokenfactorybinding "github.com/sei-protocol/sei-chain/x/tokenfactory/client/wasm/bindings" tokenfactorytypes "github.com/sei-protocol/sei-chain/x/tokenfactory/types" "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/proto/tendermint/types" ) func SetupWasmbindingTest(t *testing.T) (*app.TestWrapper, func(ctx sdk.Context, request json.RawMessage) ([]byte, error)) { @@ -409,3 +415,500 @@ func TestWasmGetCreatorInDenomFeeWhitelist(t *testing.T) { require.NoError(t, err) require.Equal(t, tokenfactorytypes.QueryCreatorInDenomFeeWhitelistResponse{Whitelisted: true}, parsedRes2) } + +func MockQueryPlugins() wasmkeeper.QueryPlugins { + return wasmkeeper.QueryPlugins{ + Bank: func(ctx sdk.Context, request *wasmvmtypes.BankQuery) ([]byte, error) { return []byte{}, nil }, + IBC: func(ctx sdk.Context, caller sdk.AccAddress, request *wasmvmtypes.IBCQuery) ([]byte, error) { + return []byte{}, nil + }, + Custom: func(ctx sdk.Context, request json.RawMessage) ([]byte, error) { + println("test") + return []byte{}, nil + }, + Stargate: func(ctx sdk.Context, request *wasmvmtypes.StargateQuery) ([]byte, error) { return []byte{}, nil }, + Staking: func(ctx sdk.Context, request *wasmvmtypes.StakingQuery) ([]byte, error) { return []byte{}, nil }, + Wasm: func(ctx sdk.Context, request *wasmvmtypes.WasmQuery) ([]byte, error) { return []byte{}, nil }, + } +} + +func TestQueryHandlerDependencyDecoratorBank(t *testing.T) { + app := app.Setup(false) + contractAddr, err := sdk.AccAddressFromBech32("sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw") + require.NoError(t, err) + queryDecorator := wasmbinding.NewCustomQueryHandler(MockQueryPlugins(), app.AccessControlKeeper) + testContext := app.NewContext(false, types.Header{}) + + // setup the wasm contract's dependency mapping + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV_BANK, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Bank: &wasmvmtypes.BankQuery{}, + }) + require.NoError(t, err) + + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_WRITE, + ResourceType: accesscontrol.ResourceType_KV_DEX, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Bank: &wasmvmtypes.BankQuery{}, + }) + require.Error(t, acl.ErrUnexpectedWasmDependency, err) +} + +func TestQueryHandlerDependencyDecoratorIBC(t *testing.T) { + app := app.Setup(false) + contractAddr, err := sdk.AccAddressFromBech32("sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw") + require.NoError(t, err) + queryDecorator := wasmbinding.NewCustomQueryHandler(MockQueryPlugins(), app.AccessControlKeeper) + testContext := app.NewContext(false, types.Header{}) + + // setup the wasm contract's dependency mapping + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_ANY, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + IBC: &wasmvmtypes.IBCQuery{}, + }) + require.NoError(t, err) + + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_WRITE, + ResourceType: accesscontrol.ResourceType_KV, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + IBC: &wasmvmtypes.IBCQuery{}, + }) + require.Error(t, acl.ErrUnexpectedWasmDependency, err) +} + +func TestQueryHandlerDependencyDecoratorStaking(t *testing.T) { + app := app.Setup(false) + contractAddr, err := sdk.AccAddressFromBech32("sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw") + require.NoError(t, err) + queryDecorator := wasmbinding.NewCustomQueryHandler(MockQueryPlugins(), app.AccessControlKeeper) + testContext := app.NewContext(false, types.Header{}) + + // setup the wasm contract's dependency mapping + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV_STAKING, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Staking: &wasmvmtypes.StakingQuery{}, + }) + require.NoError(t, err) + + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_WRITE, + ResourceType: accesscontrol.ResourceType_KV_DEX, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Staking: &wasmvmtypes.StakingQuery{}, + }) + require.Error(t, acl.ErrUnexpectedWasmDependency, err) +} + +func TestQueryHandlerDependencyDecoratorStargate(t *testing.T) { + app := app.Setup(false) + contractAddr, err := sdk.AccAddressFromBech32("sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw") + require.NoError(t, err) + queryDecorator := wasmbinding.NewCustomQueryHandler(MockQueryPlugins(), app.AccessControlKeeper) + testContext := app.NewContext(false, types.Header{}) + + // setup the wasm contract's dependency mapping + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_ANY, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Stargate: &wasmvmtypes.StargateQuery{}, + }) + require.NoError(t, err) + + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_WRITE, + ResourceType: accesscontrol.ResourceType_KV, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Stargate: &wasmvmtypes.StargateQuery{}, + }) + require.Error(t, acl.ErrUnexpectedWasmDependency, err) +} + +func TestQueryHandlerDependencyDecoratorWasm(t *testing.T) { + app := app.Setup(false) + contractAddr, err := sdk.AccAddressFromBech32("sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw") + require.NoError(t, err) + queryDecorator := wasmbinding.NewCustomQueryHandler(MockQueryPlugins(), app.AccessControlKeeper) + testContext := app.NewContext(false, types.Header{}) + + // setup the wasm contract's dependency mapping + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV_WASM, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Wasm: &wasmvmtypes.WasmQuery{}, + }) + require.NoError(t, err) + + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_WRITE, + ResourceType: accesscontrol.ResourceType_KV_DEX, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Wasm: &wasmvmtypes.WasmQuery{}, + }) + require.Error(t, acl.ErrUnexpectedWasmDependency, err) +} + +func TestQueryHandlerDependencyDecoratorDex(t *testing.T) { + app := app.Setup(false) + contractAddr, err := sdk.AccAddressFromBech32("sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw") + require.NoError(t, err) + queryDecorator := wasmbinding.NewCustomQueryHandler(MockQueryPlugins(), app.AccessControlKeeper) + testContext := app.NewContext(false, types.Header{}) + + // setup the wasm contract's dependency mapping + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV_DEX, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + require.NoError(t, err) + + customQuery, err := json.Marshal(wasmbinding.SeiQueryWrapper{ + Route: wasmbinding.DexRoute, + }) + require.NoError(t, err) + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Custom: customQuery, + }) + require.NoError(t, err) + + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV_ORACLE, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Custom: customQuery, + }) + require.Error(t, acl.ErrUnexpectedWasmDependency, err) +} + +func TestQueryHandlerDependencyDecoratorOracle(t *testing.T) { + app := app.Setup(false) + contractAddr, err := sdk.AccAddressFromBech32("sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw") + require.NoError(t, err) + queryDecorator := wasmbinding.NewCustomQueryHandler(MockQueryPlugins(), app.AccessControlKeeper) + testContext := app.NewContext(false, types.Header{}) + + // setup the wasm contract's dependency mapping + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV_ORACLE, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + require.NoError(t, err) + + customQuery, err := json.Marshal(wasmbinding.SeiQueryWrapper{ + Route: wasmbinding.OracleRoute, + }) + require.NoError(t, err) + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Custom: customQuery, + }) + require.NoError(t, err) + + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV_BANK, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Custom: customQuery, + }) + require.Error(t, acl.ErrUnexpectedWasmDependency, err) +} + +func TestQueryHandlerDependencyDecoratorEpoch(t *testing.T) { + app := app.Setup(false) + contractAddr, err := sdk.AccAddressFromBech32("sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw") + require.NoError(t, err) + queryDecorator := wasmbinding.NewCustomQueryHandler(MockQueryPlugins(), app.AccessControlKeeper) + testContext := app.NewContext(false, types.Header{}) + + // setup the wasm contract's dependency mapping + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV_EPOCH, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + require.NoError(t, err) + + customQuery, err := json.Marshal(wasmbinding.SeiQueryWrapper{ + Route: wasmbinding.EpochRoute, + }) + require.NoError(t, err) + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Custom: customQuery, + }) + require.NoError(t, err) + + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV_BANK, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Custom: customQuery, + }) + require.Error(t, acl.ErrUnexpectedWasmDependency, err) +} + +func TestQueryHandlerDependencyDecoratorTokenFactory(t *testing.T) { + app := app.Setup(false) + contractAddr, err := sdk.AccAddressFromBech32("sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw") + require.NoError(t, err) + queryDecorator := wasmbinding.NewCustomQueryHandler(MockQueryPlugins(), app.AccessControlKeeper) + testContext := app.NewContext(false, types.Header{}) + + // setup the wasm contract's dependency mapping + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV, + IdentifierTemplate: "*", + }, + SelectorType: accesscontrol.AccessOperationSelectorType_NONE, + }, + { + Operation: acltypes.CommitAccessOp(), + SelectorType: accesscontrol.AccessOperationSelectorType_NONE, + }, + }, + }) + require.NoError(t, err) + + customQuery, err := json.Marshal(wasmbinding.SeiQueryWrapper{ + Route: wasmbinding.TokenFactoryRoute, + }) + require.NoError(t, err) + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Custom: customQuery, + }) + require.NoError(t, err) + + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_Mem, + IdentifierTemplate: "*", + }, + SelectorType: accesscontrol.AccessOperationSelectorType_NONE, + }, + { + Operation: acltypes.CommitAccessOp(), + SelectorType: accesscontrol.AccessOperationSelectorType_NONE, + }, + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Custom: customQuery, + }) + require.Error(t, acl.ErrUnexpectedWasmDependency, err) +} diff --git a/wasmbinding/utils.go b/wasmbinding/utils.go new file mode 100644 index 0000000000..b07702e978 --- /dev/null +++ b/wasmbinding/utils.go @@ -0,0 +1,14 @@ +package wasmbinding + +import sdk "github.com/cosmos/cosmos-sdk/types" + +const ( + EventTypeWasmContractWithIncorrectDependency = "wasm-incorrect-dep" + AttributeKeyWasmContractAddress = "contract-addr" +) + +func emitIncorrectDependencyWasmEvent(ctx sdk.Context, contractAddr string) { + ctx.EventManager().EmitEvent( + sdk.NewEvent(EventTypeWasmContractWithIncorrectDependency, sdk.NewAttribute(AttributeKeyWasmContractAddress, contractAddr)), + ) +} diff --git a/wasmbinding/wasm.go b/wasmbinding/wasm.go index 99baf4f180..5d13ae9206 100644 --- a/wasmbinding/wasm.go +++ b/wasmbinding/wasm.go @@ -3,6 +3,9 @@ package wasmbinding import ( "github.com/CosmWasm/wasmd/x/wasm" wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper" authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" dexwasm "github.com/sei-protocol/sei-chain/x/dex/client/wasm" dexkeeper "github.com/sei-protocol/sei-chain/x/dex/keeper" @@ -21,6 +24,12 @@ func RegisterCustomPlugins( tokenfactory *tokenfactorykeeper.Keeper, accountKeeper *authkeeper.AccountKeeper, router wasmkeeper.MessageRouter, + channelKeeper wasmtypes.ChannelKeeper, + capabilityKeeper wasmtypes.CapabilityKeeper, + bankKeeper wasmtypes.Burner, + unpacker codectypes.AnyUnpacker, + portSource wasmtypes.ICS20TransferPortSource, + aclKeeper aclkeeper.Keeper, ) []wasmkeeper.Option { dexHandler := dexwasm.NewDexWasmQueryHandler(dex) oracleHandler := oraclewasm.NewOracleWasmQueryHandler(oracle) @@ -28,15 +37,13 @@ func RegisterCustomPlugins( tokenfactoryHandler := tokenfactorywasm.NewTokenFactoryWasmQueryHandler(tokenfactory) wasmQueryPlugin := NewQueryPlugin(oracleHandler, dexHandler, epochHandler, tokenfactoryHandler) - queryPluginOpt := wasmkeeper.WithQueryPlugins(&wasmkeeper.QueryPlugins{ - Custom: CustomQuerier(wasmQueryPlugin), - }) - messengerDecoratorOpt := wasmkeeper.WithMessageHandlerDecorator( - CustomMessageDecorator(router, accountKeeper), + queryOpt := wasmkeeper.WithQueryHandlerDecorator(CustomQueryHandlerDecorator(aclKeeper, *wasmQueryPlugin)) + messengerHandlerOpt := wasmkeeper.WithMessageHandler( + CustomMessageHandler(router, channelKeeper, capabilityKeeper, bankKeeper, unpacker, portSource, aclKeeper), ) return []wasm.Option{ - queryPluginOpt, - messengerDecoratorOpt, + queryOpt, + messengerHandlerOpt, } } diff --git a/x/dex/keeper/epoch.go b/x/dex/keeper/epoch.go index d9ca566b7d..33f8012af4 100644 --- a/x/dex/keeper/epoch.go +++ b/x/dex/keeper/epoch.go @@ -14,6 +14,7 @@ func (k Keeper) SetEpoch(ctx sdk.Context, epoch uint64) { bz := make([]byte, 8) binary.BigEndian.PutUint64(bz, epoch) store.Set([]byte(EpochKey), bz) + ctx.Logger().Info(fmt.Sprintf("Current epoch %d", epoch)) } func (k Keeper) IsNewEpoch(ctx sdk.Context) (bool, uint64) { @@ -21,6 +22,5 @@ func (k Keeper) IsNewEpoch(ctx sdk.Context) (bool, uint64) { b := store.Get([]byte(EpochKey)) lastEpoch := binary.BigEndian.Uint64(b) currentEpoch := k.EpochKeeper.GetEpoch(ctx).CurrentEpoch - ctx.Logger().Info(fmt.Sprintf("Current epoch %d", currentEpoch)) return currentEpoch > lastEpoch, currentEpoch } diff --git a/x/dex/module_test.go b/x/dex/module_test.go index e431d8ee6f..dd196ceb91 100644 --- a/x/dex/module_test.go +++ b/x/dex/module_test.go @@ -185,7 +185,7 @@ func TestEndBlockLimitOrder(t *testing.T) { bankkeeper := testApp.BankKeeper bankkeeper.MintCoins(ctx, minttypes.ModuleName, amounts) bankkeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, testAccount, amounts) - wasm, err := ioutil.ReadFile("./testdata/clearing_house.wasm") + wasm, err := ioutil.ReadFile("./testdata/mars.wasm") if err != nil { panic(err) } diff --git a/x/mint/types/expected_keepers.go b/x/mint/types/expected_keepers.go index 4ce2e3a31e..da49ab11b2 100644 --- a/x/mint/types/expected_keepers.go +++ b/x/mint/types/expected_keepers.go @@ -27,6 +27,8 @@ type BankKeeper interface { SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error SendCoinsFromModuleToModule(ctx sdk.Context, senderModule, recipientModule string, amt sdk.Coins) error MintCoins(ctx sdk.Context, name string, amt sdk.Coins) error + DeferredMintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error + DeferredBurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error } // EpochKeeper defines the contract needed to be fulfilled for epoch keepers diff --git a/x/oracle/ante.go b/x/oracle/ante.go index 8cd469fda5..2b0a903de3 100644 --- a/x/oracle/ante.go +++ b/x/oracle/ante.go @@ -4,8 +4,10 @@ import ( "sync" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + aclutils "github.com/sei-protocol/sei-chain/aclmapping/utils" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/sei-protocol/sei-chain/x/oracle/keeper" "github.com/sei-protocol/sei-chain/x/oracle/types" ) @@ -45,6 +47,24 @@ func (spd SpammingPreventionDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, si return next(ctx, tx, simulate) } +func (spd SpammingPreventionDecorator) AnteDeps(txDeps []sdkacltypes.AccessOperation, tx sdk.Tx, next sdk.AnteDepGenerator) (newTxDeps []sdkacltypes.AccessOperation, err error) { + deps := []sdkacltypes.AccessOperation{} + for _, msg := range tx.GetMsgs() { + // Error checking will be handled in AnteHandler + switch m := msg.(type) { + case *types.MsgAggregateExchangeRateVote: + feederAddr, _ := sdk.AccAddressFromBech32(m.Feeder) + valAddr, _ := sdk.ValAddressFromBech32(m.Validator) + deps = append(deps, aclutils.GetOracleReadAccessOpsForValAndFeeder(feederAddr, valAddr)...) + // TODO: we also need to add READs for Validator + bonded check + default: + continue + } + } + + return next(append(txDeps, deps...), tx) +} + // CheckOracleSpamming check whether the msgs are spamming purpose or not func (spd SpammingPreventionDecorator) CheckOracleSpamming(ctx sdk.Context, msgs []sdk.Msg) error { spd.mu.Lock() diff --git a/x/oracle/keeper/test_utils.go b/x/oracle/keeper/test_utils.go old mode 100755 new mode 100644 diff --git a/x/tokenfactory/keeper/admins_test.go b/x/tokenfactory/keeper/admins_test.go index 414519f320..0c3f3632c7 100644 --- a/x/tokenfactory/keeper/admins_test.go +++ b/x/tokenfactory/keeper/admins_test.go @@ -17,11 +17,14 @@ func (suite *KeeperTestSuite) TestAdminMsgs() { queryRes, err := suite.queryClient.DenomAuthorityMetadata(suite.Ctx.Context(), &types.QueryDenomAuthorityMetadataRequest{ Denom: suite.defaultDenom, }) + suite.App.BankKeeper.WriteDeferredOperations(suite.Ctx) suite.Require().NoError(err) suite.Require().Equal(suite.TestAccs[0].String(), queryRes.AuthorityMetadata.Admin) // Test minting to admins own account _, err = suite.msgServer.Mint(sdk.WrapSDKContext(suite.Ctx), types.NewMsgMint(suite.TestAccs[0].String(), sdk.NewInt64Coin(suite.defaultDenom, 10))) + suite.App.BankKeeper.WriteDeferredOperations(suite.Ctx) + addr0bal += 10 suite.Require().NoError(err) suite.Require().True(suite.App.BankKeeper.GetBalance(suite.Ctx, suite.TestAccs[0], suite.defaultDenom).Amount.Int64() == addr0bal, suite.App.BankKeeper.GetBalance(suite.Ctx, suite.TestAccs[0], suite.defaultDenom)) @@ -34,12 +37,16 @@ func (suite *KeeperTestSuite) TestAdminMsgs() { // Test burning from own account _, err = suite.msgServer.Burn(sdk.WrapSDKContext(suite.Ctx), types.NewMsgBurn(suite.TestAccs[0].String(), sdk.NewInt64Coin(suite.defaultDenom, 5))) + suite.App.BankKeeper.WriteDeferredOperations(suite.Ctx) + addr0bal -= 5 suite.Require().NoError(err) suite.Require().True(suite.App.BankKeeper.GetBalance(suite.Ctx, suite.TestAccs[1], suite.defaultDenom).Amount.Int64() == addr1bal) // Test Change Admin _, err = suite.msgServer.ChangeAdmin(sdk.WrapSDKContext(suite.Ctx), types.NewMsgChangeAdmin(suite.TestAccs[0].String(), suite.defaultDenom, suite.TestAccs[1].String())) + suite.App.BankKeeper.WriteDeferredOperations(suite.Ctx) + queryRes, err = suite.queryClient.DenomAuthorityMetadata(suite.Ctx.Context(), &types.QueryDenomAuthorityMetadataRequest{ Denom: suite.defaultDenom, }) @@ -48,16 +55,22 @@ func (suite *KeeperTestSuite) TestAdminMsgs() { // Make sure old admin can no longer do actions _, err = suite.msgServer.Burn(sdk.WrapSDKContext(suite.Ctx), types.NewMsgBurn(suite.TestAccs[0].String(), sdk.NewInt64Coin(suite.defaultDenom, 5))) + suite.App.BankKeeper.WriteDeferredOperations(suite.Ctx) + suite.Require().Error(err) // Make sure the new admin works _, err = suite.msgServer.Mint(sdk.WrapSDKContext(suite.Ctx), types.NewMsgMint(suite.TestAccs[1].String(), sdk.NewInt64Coin(suite.defaultDenom, 5))) + suite.App.BankKeeper.WriteDeferredOperations(suite.Ctx) + addr1bal += 5 suite.Require().NoError(err) suite.Require().True(suite.App.BankKeeper.GetBalance(suite.Ctx, suite.TestAccs[1], suite.defaultDenom).Amount.Int64() == addr1bal) // Try setting admin to empty _, err = suite.msgServer.ChangeAdmin(sdk.WrapSDKContext(suite.Ctx), types.NewMsgChangeAdmin(suite.TestAccs[1].String(), suite.defaultDenom, "")) + suite.App.BankKeeper.WriteDeferredOperations(suite.Ctx) + suite.Require().NoError(err) queryRes, err = suite.queryClient.DenomAuthorityMetadata(suite.Ctx.Context(), &types.QueryDenomAuthorityMetadataRequest{ Denom: suite.defaultDenom, @@ -108,6 +121,8 @@ func (suite *KeeperTestSuite) TestMintDenom() { suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { // Test minting to admins own account _, err := suite.msgServer.Mint(sdk.WrapSDKContext(suite.Ctx), types.NewMsgMint(tc.admin, sdk.NewInt64Coin(tc.mintDenom, 10))) + suite.App.BankKeeper.WriteDeferredOperations(suite.Ctx) + if tc.valid { addr0bal += 10 suite.Require().NoError(err) @@ -127,6 +142,8 @@ func (suite *KeeperTestSuite) TestBurnDenom() { // mint 10 default token for testAcc[0] suite.msgServer.Mint(sdk.WrapSDKContext(suite.Ctx), types.NewMsgMint(suite.TestAccs[0].String(), sdk.NewInt64Coin(suite.defaultDenom, 10))) + suite.App.BankKeeper.WriteDeferredOperations(suite.Ctx) + addr0bal += 10 for _, tc := range []struct { @@ -168,6 +185,8 @@ func (suite *KeeperTestSuite) TestBurnDenom() { suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { // Test minting to admins own account _, err := suite.msgServer.Burn(sdk.WrapSDKContext(suite.Ctx), types.NewMsgBurn(tc.admin, sdk.NewInt64Coin(tc.burnDenom, 10))) + suite.App.BankKeeper.WriteDeferredOperations(suite.Ctx) + if tc.valid { addr0bal -= 10 suite.Require().NoError(err) @@ -228,14 +247,19 @@ func (suite *KeeperTestSuite) TestChangeAdminDenom() { // Create a denom and mint res, err := suite.msgServer.CreateDenom(sdk.WrapSDKContext(suite.Ctx), types.NewMsgCreateDenom(suite.TestAccs[0].String(), "bitcoin")) + suite.App.BankKeeper.WriteDeferredOperations(suite.Ctx) suite.Require().NoError(err) testDenom := res.GetNewTokenDenom() _, err = suite.msgServer.Mint(sdk.WrapSDKContext(suite.Ctx), types.NewMsgMint(suite.TestAccs[0].String(), sdk.NewInt64Coin(testDenom, 10))) + suite.App.BankKeeper.WriteDeferredOperations(suite.Ctx) + suite.Require().NoError(err) _, err = suite.msgServer.ChangeAdmin(sdk.WrapSDKContext(suite.Ctx), tc.msgChangeAdmin(testDenom)) + suite.App.BankKeeper.WriteDeferredOperations(suite.Ctx) + if tc.expectedChangeAdminPass { suite.Require().NoError(err) } else { @@ -258,6 +282,8 @@ func (suite *KeeperTestSuite) TestChangeAdminDenom() { // we test mint to test if admin authority is performed properly after admin change. if tc.msgMint != nil { _, err := suite.msgServer.Mint(sdk.WrapSDKContext(suite.Ctx), tc.msgMint(testDenom)) + suite.App.BankKeeper.WriteDeferredOperations(suite.Ctx) + if tc.expectedMintPass { suite.Require().NoError(err) } else { diff --git a/x/tokenfactory/keeper/bankactions.go b/x/tokenfactory/keeper/bankactions.go index 853c517ef8..a052db180a 100644 --- a/x/tokenfactory/keeper/bankactions.go +++ b/x/tokenfactory/keeper/bankactions.go @@ -1,6 +1,8 @@ package keeper import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/sei-protocol/sei-chain/x/tokenfactory/types" @@ -13,7 +15,8 @@ func (k Keeper) mintTo(ctx sdk.Context, amount sdk.Coin, mintTo string) error { return err } - err = k.bankKeeper.MintCoins(ctx, types.ModuleName, sdk.NewCoins(amount)) + ctx.Logger().Info(fmt.Sprintf("Minting amount=%s for module=%s", amount.String(), types.ModuleName)) + err = k.bankKeeper.DeferredMintCoins(ctx, types.ModuleName, sdk.NewCoins(amount)) if err != nil { return err } @@ -23,7 +26,8 @@ func (k Keeper) mintTo(ctx sdk.Context, amount sdk.Coin, mintTo string) error { return err } - return k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, + ctx.Logger().Info(fmt.Sprintf("Sending Minted amount=%s to addr=%s", amount.String(), addr.String())) + return k.bankKeeper.DeferredSendCoinsFromModuleToAccount(ctx, types.ModuleName, addr, sdk.NewCoins(amount)) } @@ -40,7 +44,8 @@ func (k Keeper) burnFrom(ctx sdk.Context, amount sdk.Coin, burnFrom string) erro return err } - err = k.bankKeeper.SendCoinsFromAccountToModule(ctx, + ctx.Logger().Info(fmt.Sprintf("Sending amount=%s to module=%s from account=%s", amount.String(), types.ModuleName, addr.String())) + err = k.bankKeeper.DeferredSendCoinsFromAccountToModule(ctx, addr, types.ModuleName, sdk.NewCoins(amount)) @@ -48,7 +53,8 @@ func (k Keeper) burnFrom(ctx sdk.Context, amount sdk.Coin, burnFrom string) erro return err } - return k.bankKeeper.BurnCoins(ctx, types.ModuleName, sdk.NewCoins(amount)) + ctx.Logger().Info(fmt.Sprintf("Burning amount=%s from module=%s", amount.String(), types.ModuleName)) + return k.bankKeeper.DeferredBurnCoins(ctx, types.ModuleName, sdk.NewCoins(amount)) } // func (k Keeper) forceTransfer(ctx sdk.Context, amount sdk.Coin, fromAddr string, toAddr string) error { diff --git a/x/tokenfactory/types/expected_keepers.go b/x/tokenfactory/types/expected_keepers.go index 1f750fd8d3..3b4e3c9e0a 100644 --- a/x/tokenfactory/types/expected_keepers.go +++ b/x/tokenfactory/types/expected_keepers.go @@ -15,6 +15,12 @@ type BankKeeper interface { SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error + + DeferredSendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amount sdk.Coins) error + DeferredSendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error + DeferredMintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error + DeferredBurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error + DelegateCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error UndelegateCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error