diff --git a/x/gateway/keeper/msg_server_stake_gateway.go b/x/gateway/keeper/msg_server_stake_gateway.go index 7d500ce1a..74c87e058 100644 --- a/x/gateway/keeper/msg_server_stake_gateway.go +++ b/x/gateway/keeper/msg_server_stake_gateway.go @@ -5,6 +5,8 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "github.com/pokt-network/poktroll/telemetry" "github.com/pokt-network/poktroll/x/gateway/types" @@ -24,46 +26,62 @@ func (k msgServer) StakeGateway( ctx := sdk.UnwrapSDKContext(goCtx) logger := k.Logger().With("method", "StakeGateway") - logger.Info(fmt.Sprintf("About to stake gateway with msg: %v", msg)) + logger.Info(fmt.Sprintf("about to stake gateway with msg: %v", msg)) if err := msg.ValidateBasic(); err != nil { - return nil, err + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + // Retrieve the address of the gateway + gatewayAddress, err := sdk.AccAddressFromBech32(msg.Address) + // NB: This SHOULD NEVER happen because msg.ValidateBasic() validates the address as bech32. + if err != nil { + // TODO_TECHDEBT(#384): determine whether to continue using cosmos logger for debug level. + logger.Info(fmt.Sprintf("ERROR: could not parse address %q", msg.Address)) + return nil, status.Error(codes.InvalidArgument, err.Error()) } // Check if the gateway already exists or not - var err error var coinsToEscrow sdk.Coin gateway, isGatewayFound := k.GetGateway(ctx, msg.Address) if !isGatewayFound { - logger.Info(fmt.Sprintf("Gateway not found. Creating new gateway for address %q", msg.Address)) + logger.Info(fmt.Sprintf("gateway not found; creating new gateway for address %q", msg.Address)) gateway = k.createGateway(ctx, msg) coinsToEscrow = *msg.Stake } else { - logger.Info(fmt.Sprintf("Gateway found. About to try and update gateway for address %q", msg.Address)) + logger.Info(fmt.Sprintf("gateway found; about to try and update gateway for address %q", msg.Address)) currGatewayStake := *gateway.Stake if err = k.updateGateway(ctx, &gateway, msg); err != nil { logger.Error(fmt.Sprintf("could not update gateway for address %q due to error %v", msg.Address, err)) - return nil, err + return nil, status.Error(codes.InvalidArgument, err.Error()) } coinsToEscrow, err = (*msg.Stake).SafeSub(currGatewayStake) if err != nil { - return nil, err + return nil, status.Error( + codes.InvalidArgument, + types.ErrGatewayInvalidStake.Wrapf( + "stake (%s) must be higher than previous stake (%s)", + msg.Stake, currGatewayStake, + ).Error(), + ) } - logger.Info(fmt.Sprintf("Gateway is going to escrow an additional %+v coins", coinsToEscrow)) + logger.Info(fmt.Sprintf("gateway is going to escrow an additional %+v coins", coinsToEscrow)) } - // Must always stake or upstake (> 0 delta) + // MUST ALWAYS stake or upstake (> 0 delta). + // TODO_MAINNET(#853): Consider removing the requirement above. if coinsToEscrow.IsZero() { - logger.Warn(fmt.Sprintf("Gateway %q must escrow more than 0 additional coins", msg.Address)) - return nil, types.ErrGatewayInvalidStake.Wrapf("gateway %q must escrow more than 0 additional coins", msg.Address) + err = types.ErrGatewayInvalidStake.Wrapf("gateway %q must escrow more than 0 additional coins", msg.GetAddress()) + logger.Info(fmt.Sprintf("ERROR: %s", err)) + return nil, status.Error(codes.InvalidArgument, err.Error()) } - // Retrieve the address of the gateway - gatewayAddress, err := sdk.AccAddressFromBech32(msg.Address) - if err != nil { - // TODO_TECHDEBT(#384): determine whether to continue using cosmos logger for debug level. - logger.Error(fmt.Sprintf("could not parse address %q", msg.Address)) - return nil, err + // MUST ALWAYS have at least minimum stake. + minStake := k.GetParams(ctx).MinStake + if msg.Stake.Amount.LT(minStake.Amount) { + err = types.ErrGatewayInvalidStake.Wrapf("gateway %q must stake at least %s", msg.Address, minStake) + logger.Info(fmt.Sprintf("ERROR: %s", err)) + return nil, status.Error(codes.InvalidArgument, err.Error()) } // Send the coins from the gateway to the staked gateway pool diff --git a/x/gateway/keeper/msg_server_stake_gateway_test.go b/x/gateway/keeper/msg_server_stake_gateway_test.go index 9645c1a0a..6e21a2edc 100644 --- a/x/gateway/keeper/msg_server_stake_gateway_test.go +++ b/x/gateway/keeper/msg_server_stake_gateway_test.go @@ -4,51 +4,52 @@ import ( "testing" "cosmossdk.io/math" - sdk "github.com/cosmos/cosmos-sdk/types" + cosmostypes "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" + "github.com/pokt-network/poktroll/app/volatile" keepertest "github.com/pokt-network/poktroll/testutil/keeper" "github.com/pokt-network/poktroll/testutil/sample" "github.com/pokt-network/poktroll/x/gateway/keeper" - "github.com/pokt-network/poktroll/x/gateway/types" + gatewaytypes "github.com/pokt-network/poktroll/x/gateway/types" ) func TestMsgServer_StakeGateway_SuccessfulCreateAndUpdate(t *testing.T) { k, ctx := keepertest.GatewayKeeper(t) srv := keeper.NewMsgServerImpl(k) - // Generate an address for the gateway + // Generate an address for the gateway. addr := sample.AccAddress() - // Verify that the gateway does not exist yet + // Verify that the gateway does not exist yet. _, isGatewayFound := k.GetGateway(ctx, addr) require.False(t, isGatewayFound) - // Prepare the gateway - initialStake := sdk.NewCoin("upokt", math.NewInt(100)) - stakeMsg := &types.MsgStakeGateway{ + // Prepare the gateway. + initialStake := cosmostypes.NewCoin("upokt", math.NewInt(100)) + stakeMsg := &gatewaytypes.MsgStakeGateway{ Address: addr, Stake: &initialStake, } - // Stake the gateway + // Stake the gateway. _, err := srv.StakeGateway(ctx, stakeMsg) require.NoError(t, err) - // Verify that the gateway exists + // Verify that the gateway exists. foundGateway, isGatewayFound := k.GetGateway(ctx, addr) require.True(t, isGatewayFound) require.Equal(t, addr, foundGateway.Address) require.Equal(t, initialStake.Amount, foundGateway.Stake.Amount) - // Prepare an updated gateway with a higher stake - updatedStake := sdk.NewCoin("upokt", math.NewInt(200)) - updateMsg := &types.MsgStakeGateway{ + // Prepare an updated gateway with a higher stake. + updatedStake := cosmostypes.NewCoin("upokt", math.NewInt(200)) + updateMsg := &gatewaytypes.MsgStakeGateway{ Address: addr, Stake: &updatedStake, } - // Update the staked gateway + // Update the staked gateway. _, err = srv.StakeGateway(ctx, updateMsg) require.NoError(t, err) foundGateway, isGatewayFound = k.GetGateway(ctx, addr) @@ -60,33 +61,61 @@ func TestMsgServer_StakeGateway_FailLoweringStake(t *testing.T) { k, ctx := keepertest.GatewayKeeper(t) srv := keeper.NewMsgServerImpl(k) - // Prepare the gateway + // Prepare the gateway. addr := sample.AccAddress() - initialStake := sdk.NewCoin("upokt", math.NewInt(100)) - stakeMsg := &types.MsgStakeGateway{ + initialStake := cosmostypes.NewCoin("upokt", math.NewInt(100)) + stakeMsg := &gatewaytypes.MsgStakeGateway{ Address: addr, Stake: &initialStake, } - // Stake the gateway & verify that the gateway exists + // Stake the gateway & verify that the gateway exists. _, err := srv.StakeGateway(ctx, stakeMsg) require.NoError(t, err) _, isGatewayFound := k.GetGateway(ctx, addr) require.True(t, isGatewayFound) - // Prepare an updated gateway with a lower stake - updatedStake := sdk.NewCoin("upokt", math.NewInt(50)) - updateMsg := &types.MsgStakeGateway{ + // Prepare an updated gateway with a lower stake. + updatedStake := cosmostypes.NewCoin("upokt", math.NewInt(50)) + updateMsg := &gatewaytypes.MsgStakeGateway{ Address: addr, Stake: &updatedStake, } - // Verify that it fails + // Verify that it fails. _, err = srv.StakeGateway(ctx, updateMsg) require.Error(t, err) - // Verify that the gateway stake is unchanged + // Verify that the gateway stake is unchanged. gatewayFound, isGatewayFound := k.GetGateway(ctx, addr) require.True(t, isGatewayFound) require.Equal(t, initialStake.Amount, gatewayFound.Stake.Amount) } + +func TestMsgServer_StakeGateway_FailBelowMinStake(t *testing.T) { + k, ctx := keepertest.GatewayKeeper(t) + srv := keeper.NewMsgServerImpl(k) + + addr := sample.AccAddress() + gatewayStake := cosmostypes.NewInt64Coin(volatile.DenomuPOKT, 100) + minStake := gatewayStake.AddAmount(math.NewInt(1)) + expectedErr := gatewaytypes.ErrGatewayInvalidStake.Wrapf("gateway %q must stake at least %s", addr, minStake) + + // Set the minimum stake to be greater than the gateway stake. + params := k.GetParams(ctx) + params.MinStake = &minStake + err := k.SetParams(ctx, params) + require.NoError(t, err) + + // Prepare the gateway. + stakeMsg := &gatewaytypes.MsgStakeGateway{ + Address: addr, + Stake: &gatewayStake, + } + + // Attempt to stake the gateway & verify that the gateway does NOT exist. + _, err = srv.StakeGateway(ctx, stakeMsg) + require.ErrorContains(t, err, expectedErr.Error()) + _, isGatewayFound := k.GetGateway(ctx, addr) + require.False(t, isGatewayFound) +}