Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Supplier] Add supplier staking fee #883

Merged
merged 8 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions e2e/tests/stake_supplier.feature
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@ Feature: Stake Supplier Namespace

Scenario: User can stake a Supplier
Given the user has the pocketd binary installed
red-0ne marked this conversation as resolved.
Show resolved Hide resolved
# TODO_UPNEXT: Set the supplier stake fee once it is a param.
And the user verifies the "supplier" for account "supplier2" is not staked
And the account "supplier2" has a balance greater than "1000070" uPOKT
And the account "supplier2" has a balance greater than "1000071" uPOKT
When the user stakes a "supplier" with "1000070" uPOKT for "anvil" service from the account "supplier2"
Then the user should be able to see standard output containing "txhash:"
And the user should be able to see standard output containing "code: 0"
And the pocketd binary should exit without error
And the user should wait for the "supplier" module "StakeSupplier" message to be submitted
And the user should wait for the "supplier" module "SupplierStaked" tx event to be broadcast
And the "supplier" for account "supplier2" is staked with "1000070" uPOKT
And the account balance of "supplier2" should be "1000070" uPOKT "less" than before
And the account balance of "supplier2" should be "1000071" uPOKT "less" than before

Scenario: User can unstake a Supplier
Given the user has the pocketd binary installed
Expand Down
20 changes: 11 additions & 9 deletions testutil/keeper/supplier.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ type SupplierModuleKeepers struct {
*keeper.Keeper
types.SharedKeeper
// Tracks the amount of funds returned to the supplier owner when the supplier is unbonded.
SupplierUnstakedFundsMap map[string]int64
SupplierBalanceMap map[string]int64
}

func SupplierKeeper(t testing.TB) (SupplierModuleKeepers, context.Context) {
Expand All @@ -53,17 +53,19 @@ func SupplierKeeper(t testing.TB) (SupplierModuleKeepers, context.Context) {
cdc := codec.NewProtoCodec(registry)
authority := authtypes.NewModuleAddress(govtypes.ModuleName)

// Set a simple map to track the where the supplier stake is returned when
// the supplier is unbonded.
supplierUnstakedFundsMap := make(map[string]int64)
// Set a simple map to track the suppliers balances.
supplierBalanceMap := make(map[string]int64)

ctrl := gomock.NewController(t)
mockBankKeeper := mocks.NewMockBankKeeper(ctrl)
mockBankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), types.ModuleName, gomock.Any()).AnyTimes()
mockBankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), types.ModuleName, gomock.Any()).AnyTimes().
Do(func(ctx context.Context, addr sdk.AccAddress, module string, coins sdk.Coins) {
supplierBalanceMap[addr.String()] -= coins[0].Amount.Int64()
})
mockBankKeeper.EXPECT().SpendableCoins(gomock.Any(), gomock.Any()).AnyTimes()
mockBankKeeper.EXPECT().SendCoinsFromModuleToAccount(gomock.Any(), types.ModuleName, gomock.Any(), gomock.Any()).AnyTimes().
Do(func(ctx context.Context, module string, addr sdk.AccAddress, coins sdk.Coins) {
supplierUnstakedFundsMap[addr.String()] += coins[0].Amount.Int64()
supplierBalanceMap[addr.String()] += coins[0].Amount.Int64()
})

// Construct a real shared keeper.
Expand Down Expand Up @@ -104,9 +106,9 @@ func SupplierKeeper(t testing.TB) (SupplierModuleKeepers, context.Context) {
serviceKeeper.SetService(ctx, sharedtypes.Service{Id: "svcId2"})

supplierModuleKeepers := SupplierModuleKeepers{
Keeper: &supplierKeeper,
SharedKeeper: sharedKeeper,
SupplierUnstakedFundsMap: supplierUnstakedFundsMap,
Keeper: &supplierKeeper,
SharedKeeper: sharedKeeper,
SupplierBalanceMap: supplierBalanceMap,
}

return supplierModuleKeepers, ctx
Expand Down
29 changes: 23 additions & 6 deletions x/supplier/keeper/msg_server_stake_supplier.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,19 @@ import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"

"github.com/pokt-network/poktroll/app/volatile"
"github.com/pokt-network/poktroll/telemetry"
sharedtypes "github.com/pokt-network/poktroll/x/shared/types"
"github.com/pokt-network/poktroll/x/supplier/types"
)

var (
// TODO_BETA: Make supplier staking fee a governance parameter
// TODO_UPNEXT: Update supplier staking documentation to remove the upstaking
red-0ne marked this conversation as resolved.
Show resolved Hide resolved
// requirement and introduce the staking fee.
SupplierStakingFee = sdk.NewInt64Coin(volatile.DenomuPOKT, 1)
)

func (k msgServer) StakeSupplier(ctx context.Context, msg *types.MsgStakeSupplier) (*types.MsgStakeSupplierResponse, error) {
isSuccessful := false
defer telemetry.EventSuccessCounter(
Expand Down Expand Up @@ -104,9 +112,13 @@ func (k msgServer) StakeSupplier(ctx context.Context, msg *types.MsgStakeSupplie
supplier.UnstakeSessionEndHeight = sharedtypes.SupplierNotUnstaking
}

// MUST ALWAYS stake or upstake (> 0 delta)
if coinsToEscrow.IsZero() {
err = types.ErrSupplierInvalidStake.Wrapf("Signer %q must escrow more than 0 additional coins", msg.Signer)
// TODO_BETA: Remove requirement of MUST ALWAYS stake or upstake (>= 0 delta)
// TODO_POST_MAINNET: Should we allow stake decrease down to min stake?
if coinsToEscrow.IsNegative() {
err = types.ErrSupplierInvalidStake.Wrapf(
"Supplier signer %q stake (%s) must be greater than or equal to the current stake (%s)",
msg.Signer, msg.GetStake(), supplier.Stake,
)
logger.Info(fmt.Sprintf("WARN: %s", err))
return nil, status.Error(codes.InvalidArgument, err.Error())
}
Expand All @@ -130,7 +142,8 @@ func (k msgServer) StakeSupplier(ctx context.Context, msg *types.MsgStakeSupplie
}

// Send the coins from the message signer account to the staked supplier pool
err = k.bankKeeper.SendCoinsFromAccountToModule(ctx, msgSignerAddress, types.ModuleName, []sdk.Coin{coinsToEscrow})
stakeWithFee := sdk.NewCoins(coinsToEscrow.Add(SupplierStakingFee))
err = k.bankKeeper.SendCoinsFromAccountToModule(ctx, msgSignerAddress, types.ModuleName, stakeWithFee)
if err != nil {
logger.Info(fmt.Sprintf("ERROR: could not send %v coins from %q to %q module account due to %v", coinsToEscrow, msgSignerAddress, types.ModuleName, err))
return nil, status.Error(codes.InvalidArgument, err.Error())
Expand Down Expand Up @@ -191,8 +204,12 @@ func (k msgServer) updateSupplier(
return types.ErrSupplierInvalidStake.Wrapf("stake amount cannot be nil")
}

if msg.Stake.IsLTE(*supplier.Stake) {
return types.ErrSupplierInvalidStake.Wrapf("stake amount %v must be higher than previous stake amount %v", msg.Stake, supplier.Stake)
// TODO_BETA: No longer require upstaking. Remove this check.
if msg.Stake.IsLT(*supplier.Stake) {
red-0ne marked this conversation as resolved.
Show resolved Hide resolved
return types.ErrSupplierInvalidStake.Wrapf(
"stake amount %v must be greater than or equal than previous stake amount %v",
msg.Stake, supplier.Stake,
)
}
supplier.Stake = msg.Stake

Expand Down
15 changes: 11 additions & 4 deletions x/supplier/keeper/msg_server_stake_supplier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,16 @@ func TestMsgServer_StakeSupplier_SuccessfulCreateAndUpdate(t *testing.T) {
require.Equal(t, "svcId", foundSupplier.Services[0].ServiceId)
require.Len(t, foundSupplier.Services[0].Endpoints, 1)
require.Equal(t, "http://localhost:8080", foundSupplier.Services[0].Endpoints[0].Url)

// Prepare an updated supplier with a higher stake and a different URL for the service
updateMsg := stakeSupplierForServicesMsg(ownerAddr, operatorAddr, 2000000, "svcId2")
// Assert that the supplier's account balance was reduced by the staking fee
balanceDecrease := keeper.SupplierStakingFee.Amount.Int64() + foundSupplier.Stake.Amount.Int64()
// SupplierBalanceMap reflects the relative changes to the supplier's balance
// (i.e. it starts from 0 and can go below it).
// It is not using coins that enforce non-negativity of the balance nor account
// funding and lookups.
require.Equal(t, -balanceDecrease, supplierModuleKeepers.SupplierBalanceMap[ownerAddr])

// Prepare an updated supplier with the same stake and a different URL for the service
updateMsg := stakeSupplierForServicesMsg(ownerAddr, operatorAddr, 1000000, "svcId2")
updateMsg.Services[0].Endpoints[0].Url = "http://localhost:8082"

// Update the staked supplier
Expand All @@ -56,7 +63,7 @@ func TestMsgServer_StakeSupplier_SuccessfulCreateAndUpdate(t *testing.T) {

foundSupplier, isSupplierFound = supplierModuleKeepers.GetSupplier(ctx, operatorAddr)
require.True(t, isSupplierFound)
require.Equal(t, int64(2000000), foundSupplier.Stake.Amount.Int64())
require.Equal(t, int64(1000000), foundSupplier.Stake.Amount.Int64())
require.Len(t, foundSupplier.Services, 1)
require.Equal(t, "svcId2", foundSupplier.Services[0].ServiceId)
require.Len(t, foundSupplier.Services[0].Endpoints, 1)
Expand Down
10 changes: 7 additions & 3 deletions x/supplier/keeper/msg_server_unstake_supplier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,15 +226,19 @@ func TestMsgServer_UnstakeSupplier_OperatorCanUnstake(t *testing.T) {
unbondingHeight := sharedtypes.GetSupplierUnbondingHeight(&sharedParams, &foundSupplier)
ctx = keepertest.SetBlockHeight(ctx, int64(unbondingHeight))

// Balance decrease is the total amount deducted from the supplier's balance, including
// the initial stake and the staking fee.
balanceDecrease := keeper.SupplierStakingFee.Amount.Int64() + foundSupplier.Stake.Amount.Int64()
// Ensure that the initial stake is not returned to the owner yet
require.Equal(t, int64(0), supplierModuleKeepers.SupplierUnstakedFundsMap[ownerAddr])
require.Equal(t, -balanceDecrease, supplierModuleKeepers.SupplierBalanceMap[ownerAddr])

// Run the endblocker to unbond suppliers
err = supplierModuleKeepers.EndBlockerUnbondSuppliers(ctx)
require.NoError(t, err)

// Ensure that the initial stake is returned to the owner
require.Equal(t, initialStake, supplierModuleKeepers.SupplierUnstakedFundsMap[ownerAddr])
// Ensure that the initial stake is returned to the owner while the staking fee
// remains deducted from the supplier's balance.
require.Equal(t, -keeper.SupplierStakingFee.Amount.Int64(), supplierModuleKeepers.SupplierBalanceMap[ownerAddr])
}

func createStakeMsg(supplierOwnerAddr string, stakeAmount int64) *suppliertypes.MsgStakeSupplier {
Expand Down
Loading