diff --git a/UPGRADING.md b/UPGRADING.md index 491299c8f493..d5c9f75ff98f 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -682,6 +682,14 @@ To learn more see the [docs](https://docs.cosmos.network/main/learn/advanced/tra ### Modules + + #### `**all**` * [RFC 001](https://docs.cosmos.network/main/rfc/rfc-001-tx-validation) has defined a simplification of the message validation process for modules. diff --git a/core/context/context.go b/core/context/context.go index 814917233412..215620e2a142 100644 --- a/core/context/context.go +++ b/core/context/context.go @@ -6,3 +6,8 @@ const ( ExecModeKey contextKey = iota CometInfoKey contextKey = iota ) + +// EnvironmentContextKey is the context key for the environment. +// A caller should not assume the environment is available in each context. +// ref: https://github.com/cosmos/cosmos-sdk/issues/19640 +var EnvironmentContextKey = struct{}{} diff --git a/x/authz/CHANGELOG.md b/x/authz/CHANGELOG.md index 9ccdd14e3eda..0f0802cb095f 100644 --- a/x/authz/CHANGELOG.md +++ b/x/authz/CHANGELOG.md @@ -32,6 +32,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### API Breaking Changes +* [#20502](https://github.com/cosmos/cosmos-sdk/pull/20502) `Accept` on the `Authorization` interface now expects the authz environment in the `context.Context`. This is already done when `Accept` is called by `k.DispatchActions`, but should be done manually if `Accept` is called directly. * [#19783](https://github.com/cosmos/cosmos-sdk/pull/19783) Removes the use of Accounts String() method * `NewMsgExec`, `NewMsgGrant` and `NewMsgRevoke` now takes strings as arguments instead of `sdk.AccAddress`. * `ExportGenesis` also returns an error. diff --git a/x/authz/keeper/keeper.go b/x/authz/keeper/keeper.go index 8cdfe6ffad7c..154dee4f5b9f 100644 --- a/x/authz/keeper/keeper.go +++ b/x/authz/keeper/keeper.go @@ -9,6 +9,7 @@ import ( "github.com/cosmos/gogoproto/proto" "cosmossdk.io/core/appmodule" + corecontext "cosmossdk.io/core/context" errorsmod "cosmossdk.io/errors" storetypes "cosmossdk.io/store/types" "cosmossdk.io/x/authz" @@ -83,8 +84,7 @@ func (k Keeper) update(ctx context.Context, grantee, granter sdk.AccAddress, upd // grants from the message signer to the grantee. func (k Keeper) DispatchActions(ctx context.Context, grantee sdk.AccAddress, msgs []sdk.Msg) ([][]byte, error) { results := make([][]byte, len(msgs)) - sdkCtx := sdk.UnwrapSDKContext(ctx) - now := sdkCtx.HeaderInfo().Time + now := k.Environment.HeaderService.HeaderInfo(ctx).Time for i, msg := range msgs { signers, _, err := k.cdc.GetMsgSigners(msg) @@ -118,7 +118,10 @@ func (k Keeper) DispatchActions(ctx context.Context, grantee sdk.AccAddress, msg return nil, err } - resp, err := authorization.Accept(sdkCtx, msg) + // pass the environment in the context + // users on server/v2 are expected to unwrap the environment from the context + // users on baseapp can still unwrap the sdk context + resp, err := authorization.Accept(context.WithValue(ctx, corecontext.EnvironmentContextKey, k.Environment), msg) if err != nil { return nil, err } diff --git a/x/authz/keys.go b/x/authz/keys.go index b607837e5966..a9fc30471531 100644 --- a/x/authz/keys.go +++ b/x/authz/keys.go @@ -1,6 +1,4 @@ package authz -const ( - // ModuleName is the module name constant used in many places - ModuleName = "authz" -) +// ModuleName is the module name constant used in many places +const ModuleName = "authz" diff --git a/x/authz/simulation/operations.go b/x/authz/simulation/operations.go index 9e25d14aa6ea..ec62f5196b2f 100644 --- a/x/authz/simulation/operations.go +++ b/x/authz/simulation/operations.go @@ -1,10 +1,15 @@ package simulation import ( + "context" "math/rand" "time" "cosmossdk.io/core/address" + "cosmossdk.io/core/appmodule" + corecontext "cosmossdk.io/core/context" + coregas "cosmossdk.io/core/gas" + coreheader "cosmossdk.io/core/header" "cosmossdk.io/x/authz" "cosmossdk.io/x/authz/keeper" banktype "cosmossdk.io/x/bank/types" @@ -315,7 +320,12 @@ func SimulateMsgExec( msg := []sdk.Msg{banktype.NewMsgSend(graStr, greStr, coins)} - _, err = sendAuth.Accept(ctx, msg[0]) + goCtx := context.WithValue(ctx.Context(), corecontext.EnvironmentContextKey, appmodule.Environment{ + HeaderService: headerService{}, + GasService: mockGasService{}, + }) + + _, err = sendAuth.Accept(goCtx, msg[0]) if err != nil { if sdkerrors.ErrInsufficientFunds.Is(err) { return simtypes.NoOpMsg(authz.ModuleName, TypeMsgExec, err.Error()), nil, nil @@ -359,3 +369,25 @@ func SimulateMsgExec( return simtypes.NewOperationMsg(&msgExec, true, "success"), nil, nil } } + +type headerService struct{} + +func (h headerService) HeaderInfo(ctx context.Context) coreheader.Info { + return sdk.UnwrapSDKContext(ctx).HeaderInfo() +} + +type mockGasService struct { + coregas.Service +} + +func (m mockGasService) GasMeter(ctx context.Context) coregas.Meter { + return mockGasMeter{} +} + +type mockGasMeter struct { + coregas.Meter +} + +func (m mockGasMeter) Consume(amount coregas.Gas, descriptor string) error { + return nil +} diff --git a/x/bank/types/send_authorization.go b/x/bank/types/send_authorization.go index a2be16480ed7..9aefd18dddf6 100644 --- a/x/bank/types/send_authorization.go +++ b/x/bank/types/send_authorization.go @@ -4,6 +4,8 @@ import ( "context" "cosmossdk.io/core/address" + "cosmossdk.io/core/appmodule/v2" + corecontext "cosmossdk.io/core/context" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/authz" @@ -40,12 +42,19 @@ func (a SendAuthorization) Accept(ctx context.Context, msg sdk.Msg) (authz.Accep return authz.AcceptResponse{}, sdkerrors.ErrInsufficientFunds.Wrapf("requested amount is more than spend limit") } + authzEnv, ok := ctx.Value(corecontext.EnvironmentContextKey).(appmodule.Environment) + if !ok { + return authz.AcceptResponse{}, sdkerrors.ErrUnauthorized.Wrap("environment not set") + } + isAddrExists := false toAddr := mSend.ToAddress allowedList := a.GetAllowList() - sdkCtx := sdk.UnwrapSDKContext(ctx) for _, addr := range allowedList { - sdkCtx.GasMeter().ConsumeGas(gasCostPerIteration, "send authorization") + if err := authzEnv.GasService.GasMeter(ctx).Consume(gasCostPerIteration, "send authorization"); err != nil { + return authz.AcceptResponse{}, err + } + if addr == toAddr { isAddrExists = true break diff --git a/x/bank/types/send_authorization_test.go b/x/bank/types/send_authorization_test.go index 4d0ad0382c1a..7c9473c50138 100644 --- a/x/bank/types/send_authorization_test.go +++ b/x/bank/types/send_authorization_test.go @@ -1,12 +1,16 @@ package types_test import ( + "context" "fmt" "testing" "github.com/stretchr/testify/require" - "cosmossdk.io/core/header" + "cosmossdk.io/core/appmodule/v2" + corecontext "cosmossdk.io/core/context" + coregas "cosmossdk.io/core/gas" + coreheader "cosmossdk.io/core/header" sdkmath "cosmossdk.io/math" storetypes "cosmossdk.io/store/types" "cosmossdk.io/x/bank/types" @@ -25,9 +29,36 @@ var ( unknownAddrStr = "cosmos1ta047h6lw4hxkmn0wah97h6lta0sml880l" ) +type headerService struct{} + +func (h headerService) HeaderInfo(ctx context.Context) coreheader.Info { + return sdk.UnwrapSDKContext(ctx).HeaderInfo() +} + +type mockGasService struct { + coregas.Service +} + +func (m mockGasService) GasMeter(ctx context.Context) coregas.Meter { + return mockGasMeter{} +} + +type mockGasMeter struct { + coregas.Meter +} + +func (m mockGasMeter) Consume(amount coregas.Gas, descriptor string) error { + return nil +} + func TestSendAuthorization(t *testing.T) { ac := codectestutil.CodecOptions{}.GetAddressCodec() - ctx := testutil.DefaultContextWithDB(t, storetypes.NewKVStoreKey(types.StoreKey), storetypes.NewTransientStoreKey("transient_test")).Ctx.WithHeaderInfo(header.Info{}) + sdkCtx := testutil.DefaultContextWithDB(t, storetypes.NewKVStoreKey(types.StoreKey), storetypes.NewTransientStoreKey("transient_test")).Ctx.WithHeaderInfo(coreheader.Info{}) + ctx := context.WithValue(sdkCtx.Context(), corecontext.EnvironmentContextKey, appmodule.Environment{ + HeaderService: headerService{}, + GasService: mockGasService{}, + }) + allowList := make([]sdk.AccAddress, 1) allowList[0] = toAddr authorization := types.NewSendAuthorization(coins1000, nil, ac) diff --git a/x/staking/types/authz.go b/x/staking/types/authz.go index 90fa85616412..391a5014811e 100644 --- a/x/staking/types/authz.go +++ b/x/staking/types/authz.go @@ -5,6 +5,8 @@ import ( "fmt" "cosmossdk.io/core/address" + "cosmossdk.io/core/appmodule" + corecontext "cosmossdk.io/core/context" errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" @@ -100,11 +102,17 @@ func (a StakeAuthorization) Accept(ctx context.Context, msg sdk.Msg) (authz.Acce return authz.AcceptResponse{}, sdkerrors.ErrInvalidRequest.Wrap("unknown msg type") } + authzEnv, ok := ctx.Value(corecontext.EnvironmentContextKey).(appmodule.Environment) + if !ok { + return authz.AcceptResponse{}, sdkerrors.ErrUnauthorized.Wrap("environment not set") + } isValidatorExists := false allowedList := a.GetAllowList().GetAddress() - sdkCtx := sdk.UnwrapSDKContext(ctx) for _, validator := range allowedList { - sdkCtx.GasMeter().ConsumeGas(gasCostPerIteration, "stake authorization") + if err := authzEnv.GasService.GasMeter(ctx).Consume(gasCostPerIteration, "stake authorization"); err != nil { + return authz.AcceptResponse{}, err + } + if validator == validatorAddress { isValidatorExists = true break @@ -113,7 +121,10 @@ func (a StakeAuthorization) Accept(ctx context.Context, msg sdk.Msg) (authz.Acce denyList := a.GetDenyList().GetAddress() for _, validator := range denyList { - sdkCtx.GasMeter().ConsumeGas(gasCostPerIteration, "stake authorization") + if err := authzEnv.GasService.GasMeter(ctx).Consume(gasCostPerIteration, "stake authorization"); err != nil { + return authz.AcceptResponse{}, err + } + if validator == validatorAddress { return authz.AcceptResponse{}, sdkerrors.ErrUnauthorized.Wrapf("cannot delegate/undelegate to %s validator", validator) } diff --git a/x/staking/types/authz_test.go b/x/staking/types/authz_test.go index f0912a8c41e1..62b939e79478 100644 --- a/x/staking/types/authz_test.go +++ b/x/staking/types/authz_test.go @@ -1,11 +1,15 @@ package types_test import ( + "context" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "cosmossdk.io/core/appmodule/v2" + corecontext "cosmossdk.io/core/context" + coregas "cosmossdk.io/core/gas" coreheader "cosmossdk.io/core/header" storetypes "cosmossdk.io/store/types" stakingtypes "cosmossdk.io/x/staking/types" @@ -39,10 +43,37 @@ func accAddressToString(t *testing.T, addr sdk.AccAddress) string { return r } +type headerService struct{} + +func (h headerService) HeaderInfo(ctx context.Context) coreheader.Info { + return sdk.UnwrapSDKContext(ctx).HeaderInfo() +} + +type mockGasService struct { + coregas.Service +} + +func (m mockGasService) GasMeter(ctx context.Context) coregas.Meter { + return mockGasMeter{} +} + +type mockGasMeter struct { + coregas.Meter +} + +func (m mockGasMeter) Consume(amount coregas.Gas, descriptor string) error { + return nil +} + func TestAuthzAuthorizations(t *testing.T) { key := storetypes.NewKVStoreKey(stakingtypes.StoreKey) testCtx := testutil.DefaultContextWithDB(t, key, storetypes.NewTransientStoreKey("transient_test")) - ctx := testCtx.Ctx.WithHeaderInfo(coreheader.Info{}) + sdkCtx := testCtx.Ctx.WithHeaderInfo(coreheader.Info{}) + ctx := context.WithValue(sdkCtx.Context(), corecontext.EnvironmentContextKey, appmodule.Environment{ + HeaderService: headerService{}, + GasService: mockGasService{}, + }) + valAddressCodec := codectestutil.CodecOptions{}.GetValidatorCodec() // verify ValidateBasic returns error for the AUTHORIZATION_TYPE_UNSPECIFIED authorization type delAuth, err := stakingtypes.NewStakeAuthorization([]sdk.ValAddress{val1, val2}, []sdk.ValAddress{}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_UNSPECIFIED, &coin100, valAddressCodec) @@ -313,7 +344,7 @@ func TestAuthzAuthorizations(t *testing.T) { []sdk.ValAddress{}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_CANCEL_UNBONDING_DELEGATION, &coin100, - stakingtypes.NewMsgCancelUnbondingDelegation(accAddressToString(t, delAddr), valAddressToString(t, val1), ctx.HeaderInfo().Height, coin100), + stakingtypes.NewMsgCancelUnbondingDelegation(accAddressToString(t, delAddr), valAddressToString(t, val1), sdkCtx.HeaderInfo().Height, coin100), false, true, nil, @@ -324,7 +355,7 @@ func TestAuthzAuthorizations(t *testing.T) { []sdk.ValAddress{}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_CANCEL_UNBONDING_DELEGATION, &coin100, - stakingtypes.NewMsgCancelUnbondingDelegation(accAddressToString(t, delAddr), valAddressToString(t, val1), ctx.HeaderInfo().Height, coin50), + stakingtypes.NewMsgCancelUnbondingDelegation(accAddressToString(t, delAddr), valAddressToString(t, val1), sdkCtx.HeaderInfo().Height, coin50), false, false, &stakingtypes.StakeAuthorization{ @@ -341,7 +372,7 @@ func TestAuthzAuthorizations(t *testing.T) { []sdk.ValAddress{}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_CANCEL_UNBONDING_DELEGATION, &coin100, - stakingtypes.NewMsgCancelUnbondingDelegation(accAddressToString(t, delAddr), valAddressToString(t, val3), ctx.HeaderInfo().Height, coin50), + stakingtypes.NewMsgCancelUnbondingDelegation(accAddressToString(t, delAddr), valAddressToString(t, val3), sdkCtx.HeaderInfo().Height, coin50), true, false, nil, @@ -352,7 +383,7 @@ func TestAuthzAuthorizations(t *testing.T) { []sdk.ValAddress{}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_CANCEL_UNBONDING_DELEGATION, nil, - stakingtypes.NewMsgCancelUnbondingDelegation(accAddressToString(t, delAddr), valAddressToString(t, val2), ctx.HeaderInfo().Height, coin100), + stakingtypes.NewMsgCancelUnbondingDelegation(accAddressToString(t, delAddr), valAddressToString(t, val2), sdkCtx.HeaderInfo().Height, coin100), false, false, &stakingtypes.StakeAuthorization{ @@ -369,7 +400,7 @@ func TestAuthzAuthorizations(t *testing.T) { []sdk.ValAddress{val1}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_CANCEL_UNBONDING_DELEGATION, &coin100, - stakingtypes.NewMsgCancelUnbondingDelegation(accAddressToString(t, delAddr), valAddressToString(t, val1), ctx.HeaderInfo().Height, coin100), + stakingtypes.NewMsgCancelUnbondingDelegation(accAddressToString(t, delAddr), valAddressToString(t, val1), sdkCtx.HeaderInfo().Height, coin100), true, false, nil,