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

[Tokenomics] Implement Global Mint Reimbursement Request #878

Merged
merged 45 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
6738d21
WIP
Olshansk Aug 16, 2024
3e6a146
Merge branch 'main' into inflation
Olshansk Aug 20, 2024
c3a235a
Checkpoint
Olshansk Aug 21, 2024
846bc1d
Finished first version of the documentation
Olshansk Aug 21, 2024
40c56a5
Merge branch 'main' into inflation
Olshansk Aug 21, 2024
98736f3
Revert tokenomic docs
Olshansk Aug 21, 2024
eeabb30
Performed self review
Olshansk Aug 21, 2024
10ef513
Merge with main
Olshansk Aug 22, 2024
0e725a4
Remove everything related to TLMGlobalMintReimbursementRequest
Olshansk Aug 22, 2024
bbe2366
WIP
Olshansk Aug 23, 2024
ad70e70
Fixed the TestProcessTokenLogicModules_TLMBurnEqualsMintValid test
Olshansk Aug 23, 2024
b02ef24
WIP
Olshansk Aug 24, 2024
8663fac
Finished implementing TestProcessTokenLogicModules_TLMBurnEqualsMint_…
Olshansk Aug 25, 2024
085d134
Implemented TestProcessTokenLogicModules_TLMGlobalMint_Valid_MintDist…
Olshansk Aug 25, 2024
1de13ba
Updated comments in e2e/tests/0_settlement.feature
Olshansk Aug 25, 2024
b8dff51
Fixed failing unit test
Olshansk Aug 25, 2024
9231d6f
Merge branch 'main' into issues/732_max_claimable_pokt
Olshansk Aug 25, 2024
f79989a
Merge branch 'main' into issues/732_max_claimable_pokt
Olshansk Aug 26, 2024
4c335e7
update compile proto
Olshansk Aug 26, 2024
0020303
Merge remote-tracking branch 'origin/main' into issues/732_tokenomics…
red-0ne Sep 19, 2024
f7a0da7
fix: Remove merge added lines
red-0ne Sep 19, 2024
79ac3e6
feat: Add GMRR
red-0ne Sep 20, 2024
1befc5e
fix: Skip GMRR if GMI is disabled
red-0ne Sep 20, 2024
e5a732b
Merge remote-tracking branch 'origin/main' into issues/732_tokenomics…
red-0ne Oct 13, 2024
197c5a0
fix: Activate GMRR TLM
red-0ne Oct 14, 2024
61a62fa
chore: Add TODO for the relay miner to account for GMRR
red-0ne Oct 14, 2024
942c525
Merge remote-tracking branch 'origin/main' into feat/gmrr
red-0ne Oct 14, 2024
5db96bc
Merge remote-tracking branch 'origin/main' into feat/gmrr
red-0ne Oct 14, 2024
13784b8
fix: Min stake tests
red-0ne Oct 14, 2024
2be5464
Merge remote-tracking branch 'origin/main' into feat/gmrr
red-0ne Oct 14, 2024
3baec62
Merge remote-tracking branch 'origin/main' into feat/gmrr
red-0ne Oct 17, 2024
e91a20e
feat: Add WithProofRequirement option
red-0ne Oct 18, 2024
568ecb9
fix: ensure app stake is always enough
red-0ne Oct 24, 2024
6c836e7
fix: Ensure no over-servicing
red-0ne Oct 24, 2024
fafd2f3
chore: Address reveiw change requests
red-0ne Oct 24, 2024
ba13177
Merge remote-tracking branch 'origin/main' into feat/gmrr
red-0ne Oct 25, 2024
3185741
fix: Add globalmint to tests
red-0ne Oct 25, 2024
1593bfb
fix: Linting errors
red-0ne Oct 25, 2024
b2a14b2
fix: Check cached stake before query
red-0ne Oct 25, 2024
5e6e514
fix: Remove redundant check
red-0ne Oct 25, 2024
b175916
Merge remote-tracking branch 'origin/main' into feat/gmrr
red-0ne Oct 28, 2024
7bd6058
chore: Address review change requests
red-0ne Oct 29, 2024
2784cfb
Merge remote-tracking branch 'origin/main' into feat/gmrr
red-0ne Oct 30, 2024
2bd7240
chore: Address review change requests
red-0ne Oct 30, 2024
cef4dcb
fix: Rename function
red-0ne Oct 30, 2024
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
953 changes: 909 additions & 44 deletions api/poktroll/tokenomics/event.pulsar.go

Large diffs are not rendered by default.

14 changes: 8 additions & 6 deletions e2e/tests/0_settlement.feature
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# that can be used to clear the state of the chain between tests.

Feature: Tokenomics Namespace
Scenario: Settle the session when a valid claim is within max limits and a valid proof is submitted and required via threshold
Scenario: TLM Mint=Burn when a valid claim is within max limits and a valid proof is submitted and required via threshold
# Baseline
Given the user has the pocketd binary installed
# Network preparation and validation
Expand Down Expand Up @@ -35,12 +35,13 @@ Feature: Tokenomics Namespace
And the user should wait for the ClaimSettled event with "THRESHOLD" proof requirement to be broadcast
# Validate the results
# Please note that supplier mint is > app burn because of inflation
# TODO_TECHDEBT: Update this test such the the inflation is set and enforce that Mint=Burn
# TODO_TECHDEBT: Update this test such the inflation is set and enforce that Mint=Burn
# Then add a separate test that only validates that inflation is enforced correctly
Then the account balance of "supplier1" should be "898" uPOKT "more" than before
And the "application" stake of "app1" should be "840" uPOKT "less" than before
# The application stake should be less 840 * (1 + glbal_inflation) = 840 * 1.1 = 924
And the "application" stake of "app1" should be "924" uPOKT "less" than before

Scenario: Settle the session when a valid claim is create but not required
Scenario: TLM Mint=Burn when a valid claim is create but not required
# Baseline
Given the user has the pocketd binary installed
# Network preparation and validation
Expand Down Expand Up @@ -70,9 +71,10 @@ Feature: Tokenomics Namespace
And the user should wait for the ClaimSettled event with "NOT_REQUIRED" proof requirement to be broadcast
# Validate the results
# Please note that supplier mint is > app burn because of inflation
# TODO_TECHDEBT: Update this test such the the inflation is set and enforce that Mint=Burn
# TODO_TECHDEBT: Update this test such the inflation is set and enforce that Mint=Burn
Then the account balance of "supplier1" should be "449" uPOKT "more" than before
And the "application" stake of "app1" should be "420" uPOKT "less" than before
# The application stake should be less 420 * (1 + glbal_inflation) = 420 * 1.1 = 462
And the "application" stake of "app1" should be "462" uPOKT "less" than before

# TODO_TEST: Implement the following scenarios
# Scenario: Supplier revenue shares are properly distributed
Expand Down
11 changes: 11 additions & 0 deletions proto/poktroll/tokenomics/event.proto
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,15 @@ message EventSupplierSlashed {
// Amount slashed from the supplier's stake due to the expired claims.
// This is a function of the number of expired claims and proof missing penalty.
cosmos.base.v1beta1.Coin slashing_amount = 3;
}

// EventApplicationReimbursementRequest is emitted when an application requests
// a reimbursement.
message EventApplicationReimbursementRequest {
red-0ne marked this conversation as resolved.
Show resolved Hide resolved
string application_addr = 1;
string supplier_operator_addr = 2;
string supplier_owner_addr = 3;
string service_id = 4;
string session_id = 5;
cosmos.base.v1beta1.Coin amount = 6;
}
14 changes: 9 additions & 5 deletions tests/integration/application/min_stake_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"testing"

cosmoslog "cosmossdk.io/log"
"cosmossdk.io/math"
cosmostypes "github.com/cosmos/cosmos-sdk/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/stretchr/testify/require"
Expand All @@ -25,6 +24,7 @@ import (
sessiontypes "github.com/pokt-network/poktroll/x/session/types"
sharedtypes "github.com/pokt-network/poktroll/x/shared/types"
suppliertypes "github.com/pokt-network/poktroll/x/supplier/types"
tokenomicskeeper "github.com/pokt-network/poktroll/x/tokenomics/keeper"
)

type applicationMinStakeTestSuite struct {
Expand All @@ -51,7 +51,7 @@ func TestApplicationMinStakeTestSuite(t *testing.T) {
}

func (s *applicationMinStakeTestSuite) SetupTest() {
s.keepers, s.ctx = keeper.NewTokenomicsModuleKeepers(s.T(), cosmoslog.NewNopLogger())
s.keepers, s.ctx = keeper.NewTokenomicsModuleKeepers(s.T(), cosmoslog.NewNopLogger(), keeper.WithProofRequirement(false))

proofParams := prooftypes.DefaultParams()
proofParams.ProofRequestProbability = 0
Expand Down Expand Up @@ -235,7 +235,8 @@ func (s *applicationMinStakeTestSuite) getExpectedApp(claim *prooftypes.Claim) *
expectedBurnCoin, err := claim.GetClaimeduPOKT(sharedParams, relayMiningDifficulty)
require.NoError(s.T(), err)

expectedEndStake := s.appStake.Sub(expectedBurnCoin)
globalInflationAmt, _ := tokenomicskeeper.CalculateGlobalPerClaimMintInflationFromSettlementAmount(expectedBurnCoin)
expectedEndStake := s.appStake.Sub(expectedBurnCoin).Sub(globalInflationAmt)
return &apptypes.Application{
Address: s.appBech32,
Stake: &expectedEndStake,
Expand Down Expand Up @@ -304,8 +305,11 @@ func (s *applicationMinStakeTestSuite) assertUnbondingEndEventObserved(expectedA
func (s *applicationMinStakeTestSuite) assertAppStakeIsReturnedToBalance() {
s.T().Helper()

expectedAppBurn := math.NewInt(int64(s.numRelays * s.numComputeUnitsPerRelay * sharedtypes.DefaultComputeUnitsToTokensMultiplier))
expectedAppBalance := s.appStake.SubAmount(expectedAppBurn)
expectedAppBurn := int64(s.numRelays * s.numComputeUnitsPerRelay * sharedtypes.DefaultComputeUnitsToTokensMultiplier)
expectedAppBurnCoin := cosmostypes.NewInt64Coin(volatile.DenomuPOKT, expectedAppBurn)
globalInflationCoin, _ := tokenomicskeeper.CalculateGlobalPerClaimMintInflationFromSettlementAmount(expectedAppBurnCoin)
expectedAppBalance := s.appStake.Sub(expectedAppBurnCoin).Sub(globalInflationCoin)

appBalance := s.getAppBalance()
require.Equal(s.T(), expectedAppBalance.Amount.Int64(), appBalance.Amount.Int64())
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package integration_test

import (
"math"
"math/big"
"testing"

Expand Down Expand Up @@ -84,6 +83,7 @@ func TestComputeNewDifficultyHash_RewardsReflectWorkCompleted(t *testing.T) {
testutils.WithService(service),
testutils.WithApplication(application),
testutils.WithSupplier(supplier),
testutils.WithProofRequirement(false),
)
sdkCtx := sdk.UnwrapSDKContext(ctx)
sdkCtx = sdkCtx.WithBlockHeight(1)
Expand All @@ -94,13 +94,6 @@ func TestComputeNewDifficultyHash_RewardsReflectWorkCompleted(t *testing.T) {
err := keepers.SharedKeeper.SetParams(sdkCtx, sharedParams)
require.NoError(t, err)

// Set the global proof params so we never need a proof (for simplicity of this test)
err = keepers.ProofKeeper.SetParams(sdkCtx, prooftypes.Params{
ProofRequestProbability: 0, // we never need a proof randomly
ProofRequirementThreshold: &sdk.Coin{Denom: volatile.DenomuPOKT, Amount: sdkmath.NewInt(math.MaxInt64)}, // a VERY high threshold
})
require.NoError(t, err)

// Update the relay mining difficulty so there's always a difficulty to retrieve
// for the test service.
_, err = keepers.ServiceKeeper.UpdateRelayMiningDifficulty(sdkCtx, map[string]uint64{service.Id: 1})
Expand Down
45 changes: 40 additions & 5 deletions testutil/keeper/tokenomics.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ package keeper

import (
"context"
"math"
"testing"

"cosmossdk.io/log"
"cosmossdk.io/math"
cosmosmath "cosmossdk.io/math"
"cosmossdk.io/store"
"cosmossdk.io/store/metrics"
storetypes "cosmossdk.io/store/types"
Expand All @@ -29,6 +30,7 @@ import (
"github.com/stretchr/testify/require"

"github.com/pokt-network/poktroll/app"
"github.com/pokt-network/poktroll/app/volatile"
"github.com/pokt-network/poktroll/testutil/sample"
"github.com/pokt-network/poktroll/testutil/tokenomics/mocks"
appkeeper "github.com/pokt-network/poktroll/x/application/keeper"
Expand Down Expand Up @@ -117,7 +119,7 @@ func TokenomicsKeeperWithActorAddrs(t testing.TB) (
// Prepare the test application.
application := apptypes.Application{
Address: sample.AccAddress(),
Stake: &sdk.Coin{Denom: "upokt", Amount: math.NewInt(100000)},
Stake: &sdk.Coin{Denom: "upokt", Amount: cosmosmath.NewInt(100000)},
ServiceConfigs: []*sharedtypes.ApplicationServiceConfig{{ServiceId: service.Id}},
}

Expand All @@ -126,7 +128,7 @@ func TokenomicsKeeperWithActorAddrs(t testing.TB) (
supplier := sharedtypes.Supplier{
OwnerAddress: supplierOwnerAddr,
OperatorAddress: supplierOwnerAddr,
Stake: &sdk.Coin{Denom: "upokt", Amount: math.NewInt(100000)},
Stake: &sdk.Coin{Denom: "upokt", Amount: cosmosmath.NewInt(100000)},
Services: []*sharedtypes.SupplierServiceConfig{
{
ServiceId: service.Id,
Expand Down Expand Up @@ -199,6 +201,9 @@ func TokenomicsKeeperWithActorAddrs(t testing.TB) (
mockBankKeeper.EXPECT().
SendCoinsFromModuleToModule(gomock.Any(), tokenomicstypes.ModuleName, suppliertypes.ModuleName, gomock.Any()).
AnyTimes()
mockBankKeeper.EXPECT().
SendCoinsFromModuleToModule(gomock.Any(), apptypes.ModuleName, tokenomicstypes.ModuleName, gomock.Any()).
AnyTimes()

// Mock the account keeper
mockAccountKeeper := mocks.NewMockAccountKeeper(ctrl)
Expand Down Expand Up @@ -346,9 +351,9 @@ func NewTokenomicsModuleKeepers(
require.NoError(t, bankKeeper.SetParams(sdkCtx, banktypes.DefaultParams()))

// Provide some initial funds to the suppliers & applications module accounts.
err = bankKeeper.MintCoins(sdkCtx, suppliertypes.ModuleName, sdk.NewCoins(sdk.NewCoin("upokt", math.NewInt(1000000000000))))
err = bankKeeper.MintCoins(sdkCtx, suppliertypes.ModuleName, sdk.NewCoins(sdk.NewCoin("upokt", cosmosmath.NewInt(1000000000000))))
require.NoError(t, err)
err = bankKeeper.MintCoins(sdkCtx, apptypes.ModuleName, sdk.NewCoins(sdk.NewCoin("upokt", math.NewInt(1000000000000))))
err = bankKeeper.MintCoins(sdkCtx, apptypes.ModuleName, sdk.NewCoins(sdk.NewCoin("upokt", cosmosmath.NewInt(1000000000000))))
require.NoError(t, err)

// Construct a real shared keeper.
Expand Down Expand Up @@ -513,3 +518,33 @@ func WithProposerAddr(addr string) TokenomicsModuleKeepersOpt {
return sdkCtx
}
}

// WithProofRequirement is an option to enable or disable the proof requirement
// in the tokenomics module keepers by setting the proof request probability to
// 1 or 0, respectively whie setting the proof requirement threshold to 0 or
// MaxInt64, respectively.
func WithProofRequirement(proofRequired bool) TokenomicsModuleKeepersOpt {
return func(ctx context.Context, keepers *TokenomicsModuleKeepers) context.Context {

proofParams := keepers.ProofKeeper.GetParams(ctx)
if proofRequired {
// Require a proof 100% of the time probabilistically speaking.
proofParams.ProofRequestProbability = 1
// Require a proof of any claim amount (i.e. anything greater than 0).
proofRequirementThreshold := cosmostypes.NewInt64Coin(volatile.DenomuPOKT, 0)
proofParams.ProofRequirementThreshold = &proofRequirementThreshold
} else {
// Never require a proof probabilistically speaking.
proofParams.ProofRequestProbability = 0
red-0ne marked this conversation as resolved.
Show resolved Hide resolved
// Require a proof for MaxInt64 claim amount (i.e. should never trigger).
proofRequirementThreshold := cosmostypes.NewInt64Coin(volatile.DenomuPOKT, math.MaxInt64)
red-0ne marked this conversation as resolved.
Show resolved Hide resolved
proofParams.ProofRequirementThreshold = &proofRequirementThreshold
}

if err := keepers.ProofKeeper.SetParams(ctx, proofParams); err != nil {
panic(err)
}

return ctx
}
}
47 changes: 46 additions & 1 deletion x/tokenomics/keeper/settle_pending_claims.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package keeper

import (
"context"
"fmt"

"cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/query"

"github.com/pokt-network/poktroll/app/volatile"
apptypes "github.com/pokt-network/poktroll/x/application/types"
prooftypes "github.com/pokt-network/poktroll/x/proof/types"
servicekeeper "github.com/pokt-network/poktroll/x/service/keeper"
sharedtypes "github.com/pokt-network/poktroll/x/shared/types"
Expand Down Expand Up @@ -35,6 +37,15 @@ func (k Keeper) SettlePendingClaims(ctx sdk.Context) (
return settledResult, expiredResult, err
}

// Capture the applications initial stake which will be used to calculate the
// max share any claim could burn from the application stake.
// This ensures that each supplier can calculate the maximum amount it can take
// from an application's stake.
applicationInitialStakeMap, err := k.getApplicationInitialStakeMap(ctx, expiringClaims)
if err != nil {
return settledResult, expiredResult, err
}

blockHeight := ctx.BlockHeight()

logger.Info(fmt.Sprintf("found %d expiring claims at block height %d", len(expiringClaims), blockHeight))
Expand Down Expand Up @@ -176,8 +187,11 @@ func (k Keeper) SettlePendingClaims(ctx sdk.Context) (
// 1. The claim does not require a proof.
// 2. The claim requires a proof and a valid proof was found.

appAddress := claim.GetSessionHeader().GetApplicationAddress()
applicationInitialStake := applicationInitialStakeMap[appAddress]

// Manage the mint & burn accounting for the claim.
if err = k.ProcessTokenLogicModules(ctx, &claim); err != nil {
if err = k.ProcessTokenLogicModules(ctx, &claim, applicationInitialStake); err != nil {
logger.Error(fmt.Sprintf("error processing token logic modules for claim %q: %v", claim.SessionHeader.SessionId, err))
return settledResult, expiredResult, err
}
Expand Down Expand Up @@ -402,3 +416,34 @@ func (k Keeper) slashSupplierStake(

return nil
}

// getApplicationInitialStakeMap returns a map from an application address to the
// initial stake of the application. This is used to calculate the maximum share
// any claim could burn from the application stake.
func (k Keeper) getApplicationInitialStakeMap(
ctx context.Context,
expiringClaims []prooftypes.Claim,
) (applicationInitialStakeMap map[string]sdk.Coin, err error) {
applicationInitialStakeMap = make(map[string]sdk.Coin)
for _, claim := range expiringClaims {
appAddress := claim.SessionHeader.ApplicationAddress
// The same application is participating in other claims being settled,
// so we already capture its initial stake.
if _, isAppFound := applicationInitialStakeMap[appAddress]; isAppFound {
continue
}

app, isAppFound := k.applicationKeeper.GetApplication(ctx, appAddress)
if !isAppFound {
err := apptypes.ErrAppNotFound.Wrapf(
"trying to settle a claim for an application that does not exist (which should never happen) with address: %q",
appAddress,
)
return nil, err
}

applicationInitialStakeMap[appAddress] = *app.GetStake()
}

return applicationInitialStakeMap, nil
}
Loading
Loading