code.vegaprotocol.io/vega@v0.79.0/core/execution/common/liquidity_provision_test.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package common_test
    17  
    18  import (
    19  	"context"
    20  	"encoding/hex"
    21  	"testing"
    22  	"time"
    23  
    24  	bmocks "code.vegaprotocol.io/vega/core/broker/mocks"
    25  	"code.vegaprotocol.io/vega/core/collateral"
    26  	cmocks "code.vegaprotocol.io/vega/core/collateral/mocks"
    27  	"code.vegaprotocol.io/vega/core/execution/common"
    28  	"code.vegaprotocol.io/vega/core/execution/common/mocks"
    29  	ammcmocks "code.vegaprotocol.io/vega/core/execution/common/mocks_amm"
    30  	"code.vegaprotocol.io/vega/core/fee"
    31  	"code.vegaprotocol.io/vega/core/liquidity/v2"
    32  	lmocks "code.vegaprotocol.io/vega/core/liquidity/v2/mocks"
    33  	"code.vegaprotocol.io/vega/core/types"
    34  	vgcrypto "code.vegaprotocol.io/vega/libs/crypto"
    35  	"code.vegaprotocol.io/vega/libs/num"
    36  	"code.vegaprotocol.io/vega/logging"
    37  
    38  	"github.com/golang/mock/gomock"
    39  	"github.com/stretchr/testify/assert"
    40  )
    41  
    42  type marketLiquidityTest struct {
    43  	ctrl     *gomock.Controller
    44  	ctx      context.Context
    45  	marketID string
    46  	asset    string
    47  
    48  	marketLiquidity *common.MarketLiquidity
    49  
    50  	liquidityEngine  *mocks.MockLiquidityEngine
    51  	collateralEngine common.Collateral
    52  	epochEngine      *mocks.MockEpochEngine
    53  	equityShares     *mocks.MockEquityLikeShares
    54  	broker           *bmocks.MockBroker
    55  	orderBook        *lmocks.MockOrderBook
    56  	timeService      *cmocks.MockTimeService
    57  	amm              *ammcmocks.MockAMM
    58  }
    59  
    60  func newMarketLiquidity(t *testing.T) *marketLiquidityTest {
    61  	t.Helper()
    62  
    63  	ctrl := gomock.NewController(t)
    64  	log := logging.NewTestLogger()
    65  
    66  	liquidityEngine := mocks.NewMockLiquidityEngine(ctrl)
    67  	epochEngine := mocks.NewMockEpochEngine(ctrl)
    68  	equityShares := mocks.NewMockEquityLikeShares(ctrl)
    69  	broker := bmocks.NewMockBroker(ctrl)
    70  	orderBook := lmocks.NewMockOrderBook(ctrl)
    71  	timeService := cmocks.NewMockTimeService(ctrl)
    72  
    73  	collateralEngine := collateral.New(log, collateral.NewDefaultConfig(), timeService, broker)
    74  
    75  	marketID := "market-1"
    76  	settlementAsset := "USDT"
    77  
    78  	fees, _ := fee.New(log, fee.NewDefaultConfig(), types.Fees{Factors: &types.FeeFactors{}}, settlementAsset, num.DecimalOne())
    79  
    80  	liquidityEngine.EXPECT().RegisterAllocatedFeesPerParty(gomock.Any()).AnyTimes()
    81  	liquidityEngine.EXPECT().PaidLiquidityFeesStats().Return(types.NewLiquidityFeeStats()).AnyTimes()
    82  	epochEngine.EXPECT().NotifyOnEpoch(gomock.Any(), gomock.Any()).AnyTimes()
    83  
    84  	teams := mocks.NewMockTeams(ctrl)
    85  	bc := mocks.NewMockAccountBalanceChecker(ctrl)
    86  	marketTracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, bc, broker, collateralEngine)
    87  	epochEngine.NotifyOnEpoch(marketTracker.OnEpochEvent, marketTracker.OnEpochRestore)
    88  	amm := ammcmocks.NewMockAMM(ctrl)
    89  
    90  	marketLiquidity := common.NewMarketLiquidity(
    91  		log,
    92  		liquidityEngine,
    93  		collateralEngine,
    94  		broker,
    95  		orderBook,
    96  		equityShares,
    97  		marketTracker,
    98  		fees,
    99  		common.SpotMarketType,
   100  		marketID,
   101  		settlementAsset,
   102  		num.DecimalOne(),
   103  		num.NewDecimalFromFloat(0.5),
   104  		amm,
   105  	)
   106  
   107  	marketLiquidity.OnMinLPStakeQuantumMultiple(num.DecimalOne())
   108  	marketLiquidity.OnEarlyExitPenalty(num.DecimalOne())
   109  
   110  	return &marketLiquidityTest{
   111  		ctrl:             ctrl,
   112  		marketID:         marketID,
   113  		asset:            settlementAsset,
   114  		marketLiquidity:  marketLiquidity,
   115  		liquidityEngine:  liquidityEngine,
   116  		collateralEngine: collateralEngine,
   117  		equityShares:     equityShares,
   118  		epochEngine:      epochEngine,
   119  		broker:           broker,
   120  		orderBook:        orderBook,
   121  		timeService:      timeService,
   122  		amm:              amm,
   123  		ctx:              context.Background(),
   124  	}
   125  }
   126  
   127  func createPartyAndPayLiquidityFee(t *testing.T, amount *num.Uint, testLiquidity *marketLiquidityTest) {
   128  	t.Helper()
   129  
   130  	tradingParty := "party-1"
   131  	_, err := testLiquidity.collateralEngine.CreatePartyGeneralAccount(testLiquidity.ctx, tradingParty, testLiquidity.asset)
   132  	assert.NoError(t, err)
   133  
   134  	_, err = testLiquidity.collateralEngine.Deposit(testLiquidity.ctx, tradingParty, testLiquidity.asset, amount)
   135  	assert.NoError(t, err)
   136  
   137  	_, err = testLiquidity.collateralEngine.GetPartyGeneralAccount(tradingParty, testLiquidity.asset)
   138  	assert.NoError(t, err)
   139  
   140  	transfer := testLiquidity.marketLiquidity.NewTransfer(tradingParty, types.TransferTypeLiquidityFeePay, amount.Clone())
   141  
   142  	_, err = testLiquidity.collateralEngine.TransferSpotFees(
   143  		testLiquidity.ctx,
   144  		testLiquidity.marketID,
   145  		testLiquidity.asset,
   146  		common.NewFeeTransfer([]*types.Transfer{transfer}, nil), types.AccountTypeGeneral,
   147  	)
   148  	assert.NoError(t, err)
   149  }
   150  
   151  func TestLiquidityProvisionsFeeDistribution(t *testing.T) {
   152  	testLiquidity := newMarketLiquidity(t)
   153  	// set fee factor to 1, so fees are not paid out based on score.
   154  	testLiquidity.marketLiquidity.SetELSFeeFraction(num.DecimalOne())
   155  
   156  	weightsPerLP := map[string]num.Decimal{
   157  		"lp-1": num.NewDecimalFromFloat(0.008764241896),
   158  		"lp-2": num.NewDecimalFromFloat(0.0008764241895),
   159  		"lp-3": num.NewDecimalFromFloat(0.0175284838),
   160  		"lp-4": num.NewDecimalFromFloat(0.03505689996),
   161  		"lp-5": num.NewDecimalFromFloat(0.061349693),
   162  		"lp-6": num.NewDecimalFromFloat(0.876424189),
   163  	}
   164  
   165  	expectedAllocatedFess := map[string]num.Uint{
   166  		"lp-1": *num.NewUint(1000),
   167  		"lp-2": *num.NewUint(100),
   168  		"lp-3": *num.NewUint(2000),
   169  		"lp-4": *num.NewUint(4000),
   170  		"lp-5": *num.NewUint(7000),
   171  		"lp-6": *num.NewUint(100000),
   172  	}
   173  
   174  	expectedDistributedFess := map[string]num.Uint{
   175  		"lp-1": *num.NewUint(13923),
   176  		"lp-2": *num.NewUint(1322),
   177  		"lp-3": *num.NewUint(25061),
   178  		"lp-4": *num.NewUint(44553),
   179  		// expected fee is 29238 but the party will be selected to receive reaming distribution account funds (3).
   180  		"lp-5": *num.NewUint(29241),
   181  		"lp-6": *num.NewUint(0),
   182  	}
   183  
   184  	keys := []string{"lp-1", "lp-2", "lp-3", "lp-4", "lp-5", "lp-6"}
   185  
   186  	ctx := context.Background()
   187  
   188  	testLiquidity.broker.EXPECT().Send(gomock.Any()).AnyTimes()
   189  	testLiquidity.orderBook.EXPECT().GetBestStaticAskPrice().Return(num.NewUint(100), nil).AnyTimes()
   190  	testLiquidity.orderBook.EXPECT().GetBestStaticBidPrice().Return(num.NewUint(100), nil).AnyTimes()
   191  	testLiquidity.amm.EXPECT().GetAMMPoolsBySubAccount().Return(map[string]common.AMMPool{}).AnyTimes()
   192  
   193  	testLiquidity.liquidityEngine.EXPECT().UpdatePartyCommitment(gomock.Any(), gomock.Any()).DoAndReturn(
   194  		func(partyID string, amount *num.Uint) (*types.LiquidityProvision, error) {
   195  			return &types.LiquidityProvision{
   196  				Party:            partyID,
   197  				CommitmentAmount: amount.Clone(),
   198  			}, nil
   199  		}).AnyTimes()
   200  
   201  	// enable asset first.
   202  	err := testLiquidity.collateralEngine.EnableAsset(ctx, types.Asset{
   203  		ID: testLiquidity.asset,
   204  		Details: &types.AssetDetails{
   205  			Name:     testLiquidity.asset,
   206  			Symbol:   testLiquidity.asset,
   207  			Decimals: 0,
   208  			Source: types.AssetDetailsErc20{
   209  				ERC20: &types.ERC20{
   210  					ContractAddress: "addrs",
   211  					ChainID:         "1",
   212  				},
   213  			},
   214  		},
   215  	})
   216  	assert.NoError(t, err)
   217  
   218  	// create all required accounts for spot market.
   219  	err = testLiquidity.collateralEngine.CreateSpotMarketAccounts(ctx, testLiquidity.marketID, testLiquidity.asset)
   220  	assert.NoError(t, err)
   221  
   222  	testLiquidity.liquidityEngine.EXPECT().
   223  		SubmitLiquidityProvision(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   224  		AnyTimes()
   225  
   226  	testLiquidity.liquidityEngine.EXPECT().PendingProvision().Return(nil).AnyTimes()
   227  	one := num.UintOne()
   228  	testLiquidity.liquidityEngine.EXPECT().CalculateSuppliedStakeWithoutPending().Return(one).AnyTimes()
   229  	testLiquidity.liquidityEngine.EXPECT().ApplyPendingProvisions(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
   230  
   231  	testLiquidity.timeService.EXPECT().GetTimeNow().DoAndReturn(func() time.Time {
   232  		return time.Now()
   233  	}).AnyTimes()
   234  
   235  	decimalOne := num.DecimalOne()
   236  	uintOne := num.UintOne()
   237  	commitmentAmount := num.NewUint(10)
   238  	scoresPerLP := map[string]num.Decimal{}
   239  	provisionsPerParty := map[string]*types.LiquidityProvision{}
   240  
   241  	// create liquidity providers accounts and submit provision.
   242  	for provider := range weightsPerLP {
   243  		// set score to one.
   244  		scoresPerLP[provider] = decimalOne
   245  
   246  		// create providers general account and deposit funds into it.
   247  		_, err := testLiquidity.collateralEngine.CreatePartyGeneralAccount(ctx, provider, testLiquidity.asset)
   248  		assert.NoError(t, err)
   249  
   250  		_, err = testLiquidity.collateralEngine.Deposit(ctx, provider, testLiquidity.asset, commitmentAmount)
   251  		assert.NoError(t, err)
   252  
   253  		// submit the provision.
   254  		provision := &types.LiquidityProvisionSubmission{
   255  			MarketID:         testLiquidity.marketID,
   256  			CommitmentAmount: commitmentAmount,
   257  			Reference:        provider,
   258  		}
   259  
   260  		deterministicID := hex.EncodeToString(vgcrypto.Hash([]byte(provider)))
   261  		err = testLiquidity.marketLiquidity.SubmitLiquidityProvision(ctx, provision, provider,
   262  			deterministicID, types.MarketStateActive)
   263  		assert.NoError(t, err)
   264  
   265  		// setup provision per party.
   266  		provisionsPerParty[provider] = &types.LiquidityProvision{
   267  			Party:            provider,
   268  			CommitmentAmount: provision.CommitmentAmount.Clone(),
   269  		}
   270  	}
   271  
   272  	// create party and make it pay liquidity fee.
   273  	createPartyAndPayLiquidityFee(t, num.NewUint(114101), testLiquidity)
   274  
   275  	testLiquidity.liquidityEngine.EXPECT().ProvisionsPerParty().DoAndReturn(func() liquidity.ProvisionsPerParty {
   276  		return provisionsPerParty
   277  	}).AnyTimes()
   278  
   279  	testLiquidity.liquidityEngine.EXPECT().ResetSLAEpoch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
   280  
   281  	// start epoch.
   282  	lastDistributionStep := time.Now()
   283  	now := lastDistributionStep.Add(time.Second * 5)
   284  	testLiquidity.liquidityEngine.EXPECT().ReadyForFeesAllocation(gomock.Any()).Return(true)
   285  
   286  	testLiquidity.marketLiquidity.OnEpochStart(testLiquidity.ctx, now, uintOne, uintOne, uintOne, decimalOne)
   287  
   288  	testLiquidity.liquidityEngine.EXPECT().ResetAverageLiquidityScores().AnyTimes()
   289  	testLiquidity.liquidityEngine.EXPECT().ResetFeeAllocationPeriod(gomock.Any()).AnyTimes()
   290  
   291  	testLiquidity.equityShares.EXPECT().AllShares().DoAndReturn(func() map[string]num.Decimal {
   292  		return weightsPerLP
   293  	}).AnyTimes()
   294  
   295  	testLiquidity.liquidityEngine.EXPECT().GetAverageLiquidityScores().DoAndReturn(func() map[string]num.Decimal {
   296  		return scoresPerLP
   297  	})
   298  
   299  	// trigger a time tick - this should start allocation fees to LP fee accounts.
   300  	testLiquidity.marketLiquidity.OnTick(ctx, now)
   301  
   302  	for _, provider := range keys {
   303  		acc, err := testLiquidity.collateralEngine.GetPartyLiquidityFeeAccount(
   304  			testLiquidity.marketID,
   305  			provider,
   306  			testLiquidity.asset,
   307  		)
   308  		assert.NoError(t, err)
   309  
   310  		expected := expectedAllocatedFess[provider]
   311  		assert.True(t, expected.EQ(acc.Balance))
   312  	}
   313  
   314  	zeroPointFive := num.NewDecimalFromFloat(0.5)
   315  	expectedSLAPenalties := map[string]*liquidity.SlaPenalty{
   316  		"lp-1": {
   317  			Fee:  num.NewDecimalFromFloat(0),
   318  			Bond: zeroPointFive,
   319  		},
   320  		"lp-2": {
   321  			Fee:  num.NewDecimalFromFloat(0.05),
   322  			Bond: zeroPointFive,
   323  		},
   324  		"lp-3": {
   325  			Fee:  num.NewDecimalFromFloat(0.1),
   326  			Bond: zeroPointFive,
   327  		},
   328  		"lp-4": {
   329  			Fee:  num.NewDecimalFromFloat(0.2),
   330  			Bond: zeroPointFive,
   331  		},
   332  		"lp-5": {
   333  			Fee:  num.NewDecimalFromFloat(0.7),
   334  			Bond: zeroPointFive,
   335  		},
   336  		"lp-6": {
   337  			Fee:  num.NewDecimalFromFloat(1),
   338  			Bond: zeroPointFive,
   339  		},
   340  	}
   341  
   342  	testLiquidity.liquidityEngine.EXPECT().CalculateSLAPenalties(gomock.Any()).DoAndReturn(
   343  		func(_ time.Time) liquidity.SlaPenalties {
   344  			return liquidity.SlaPenalties{
   345  				PenaltiesPerParty: expectedSLAPenalties,
   346  			}
   347  		},
   348  	)
   349  
   350  	testLiquidity.liquidityEngine.EXPECT().
   351  		LiquidityProvisionByPartyID(gomock.Any()).
   352  		DoAndReturn(func(party string) *types.LiquidityProvision {
   353  			return &types.LiquidityProvision{
   354  				ID:               party,
   355  				Party:            party,
   356  				CommitmentAmount: commitmentAmount,
   357  			}
   358  		}).AnyTimes()
   359  
   360  	// end epoch - this should trigger the SLA fees distribution.
   361  	testLiquidity.marketLiquidity.OnEpochEnd(testLiquidity.ctx, now, types.Epoch{})
   362  
   363  	for _, provider := range keys {
   364  		generalAcc, err := testLiquidity.collateralEngine.GetPartyGeneralAccount(
   365  			provider,
   366  			testLiquidity.asset,
   367  		)
   368  		assert.NoError(t, err)
   369  
   370  		expectedFee := expectedDistributedFess[provider]
   371  		assert.Truef(t, expectedFee.EQ(generalAcc.Balance),
   372  			"party %s general account balance is %s, expected: %s", provider, generalAcc.Balance, expectedFee)
   373  
   374  		bondAcc, err := testLiquidity.collateralEngine.GetPartyBondAccount(testLiquidity.marketID, provider, testLiquidity.asset)
   375  		assert.NoError(t, err)
   376  
   377  		penalty := expectedSLAPenalties[provider]
   378  
   379  		num.UintFromDecimal(penalty.Bond.Mul(commitmentAmount.ToDecimal()))
   380  		expectedBondAccount, _ := num.UintFromDecimal(penalty.Bond.Mul(commitmentAmount.ToDecimal()))
   381  		assert.True(t, bondAcc.Balance.EQ(expectedBondAccount))
   382  	}
   383  
   384  	acc, err := testLiquidity.collateralEngine.GetOrCreateLiquidityFeesBonusDistributionAccount(
   385  		ctx,
   386  		testLiquidity.marketID,
   387  		testLiquidity.asset,
   388  	)
   389  	assert.NoError(t, err)
   390  	assert.True(t, acc.Balance.EQ(num.UintZero()))
   391  
   392  	testLiquidity.equityShares.EXPECT().SetPartyStake(gomock.Any(), gomock.Any()).AnyTimes()
   393  	testLiquidity.equityShares.EXPECT().AllShares().AnyTimes()
   394  	testLiquidity.marketLiquidity.OnEpochStart(testLiquidity.ctx, now, uintOne, uintOne, uintOne, decimalOne)
   395  }
   396  
   397  func TestLiquidityProvisionsWithPoolsFeeDistribution(t *testing.T) {
   398  	testLiquidity := newMarketLiquidity(t)
   399  	// set fee factor to 1, so fees are not paid out based on score.
   400  	testLiquidity.marketLiquidity.SetELSFeeFraction(num.DecimalOne())
   401  
   402  	weightsPerLP := map[string]num.Decimal{
   403  		"lp-1":         num.NewDecimalFromFloat(0.008764241896),
   404  		"lp-2":         num.NewDecimalFromFloat(0.0008764241895),
   405  		"lp-3":         num.NewDecimalFromFloat(0.0175284838),
   406  		"lp-4":         num.NewDecimalFromFloat(0.03505689996),
   407  		"lp-5":         num.NewDecimalFromFloat(0.061349693),
   408  		"lp-6":         num.NewDecimalFromFloat(0.2921413963),
   409  		"pool-party-1": num.NewDecimalFromFloat(0.2921413963),
   410  		"pool-party-2": num.NewDecimalFromFloat(0.2921413963),
   411  	}
   412  
   413  	expectedAllocatedFess := map[string]num.Uint{
   414  		"lp-1":         *num.NewUint(1000),
   415  		"lp-2":         *num.NewUint(100),
   416  		"lp-3":         *num.NewUint(2000),
   417  		"lp-4":         *num.NewUint(4000),
   418  		"lp-5":         *num.NewUint(7000),
   419  		"lp-6":         *num.NewUint(33333),
   420  		"pool-party-1": *num.NewUint(33333),
   421  		"pool-party-2": *num.NewUint(33333),
   422  	}
   423  
   424  	expectedDistributedFess := map[string]num.Uint{
   425  		"lp-1":         *num.NewUint(1527),
   426  		"lp-2":         *num.NewUint(144),
   427  		"lp-3":         *num.NewUint(2743),
   428  		"lp-4":         *num.NewUint(4877),
   429  		"lp-5":         *num.NewUint(3200),
   430  		"lp-6":         *num.NewUint(0),
   431  		"pool-party-1": *num.NewUint(50804),
   432  		"pool-party-2": *num.NewUint(50804),
   433  	}
   434  
   435  	poolsPartyIDs := []string{"pool-party-1", "pool-party-2"}
   436  	keys := append([]string{"lp-1", "lp-2", "lp-3", "lp-4", "lp-5", "lp-6"}, poolsPartyIDs...)
   437  
   438  	ctx := context.Background()
   439  
   440  	testLiquidity.broker.EXPECT().Send(gomock.Any()).AnyTimes()
   441  
   442  	testLiquidity.orderBook.EXPECT().GetBestStaticAskPrice().Return(num.NewUint(100), nil).AnyTimes()
   443  	testLiquidity.orderBook.EXPECT().GetBestStaticBidPrice().Return(num.NewUint(100), nil).AnyTimes()
   444  
   445  	poolMock := ammcmocks.NewMockAMMPool(testLiquidity.ctrl)
   446  	ammPools := map[string]common.AMMPool{
   447  		"pool-party-1": poolMock,
   448  		"pool-party-2": poolMock,
   449  	}
   450  
   451  	testLiquidity.amm.EXPECT().GetAMMPoolsBySubAccount().Return(ammPools).AnyTimes()
   452  
   453  	testLiquidity.liquidityEngine.EXPECT().UpdatePartyCommitment(gomock.Any(), gomock.Any()).DoAndReturn(
   454  		func(partyID string, amount *num.Uint) (*types.LiquidityProvision, error) {
   455  			return &types.LiquidityProvision{
   456  				Party:            partyID,
   457  				CommitmentAmount: amount.Clone(),
   458  			}, nil
   459  		}).AnyTimes()
   460  
   461  	// enable asset first.
   462  	err := testLiquidity.collateralEngine.EnableAsset(ctx, types.Asset{
   463  		ID: testLiquidity.asset,
   464  		Details: &types.AssetDetails{
   465  			Name:     testLiquidity.asset,
   466  			Symbol:   testLiquidity.asset,
   467  			Decimals: 0,
   468  			Source: types.AssetDetailsErc20{
   469  				ERC20: &types.ERC20{
   470  					ContractAddress: "addrs",
   471  				},
   472  			},
   473  		},
   474  	})
   475  	assert.NoError(t, err)
   476  
   477  	for _, partyID := range poolsPartyIDs {
   478  		// create pool party general account.
   479  		_, err = testLiquidity.collateralEngine.CreatePartyGeneralAccount(ctx, partyID, testLiquidity.asset)
   480  		assert.NoError(t, err)
   481  
   482  		// create pool party liquidity fee account.
   483  		_, err := testLiquidity.collateralEngine.GetOrCreatePartyLiquidityFeeAccount(ctx, partyID, testLiquidity.marketID, testLiquidity.asset)
   484  		assert.NoError(t, err)
   485  	}
   486  
   487  	// create all required accounts for spot market.
   488  	err = testLiquidity.collateralEngine.CreateSpotMarketAccounts(ctx, testLiquidity.marketID, testLiquidity.asset)
   489  	assert.NoError(t, err)
   490  
   491  	testLiquidity.liquidityEngine.EXPECT().
   492  		SubmitLiquidityProvision(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   493  		AnyTimes()
   494  
   495  	testLiquidity.liquidityEngine.EXPECT().PendingProvision().Return(nil).AnyTimes()
   496  	one := num.UintOne()
   497  	testLiquidity.liquidityEngine.EXPECT().CalculateSuppliedStakeWithoutPending().Return(one).AnyTimes()
   498  	testLiquidity.liquidityEngine.EXPECT().ApplyPendingProvisions(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
   499  
   500  	testLiquidity.timeService.EXPECT().GetTimeNow().DoAndReturn(func() time.Time {
   501  		return time.Now()
   502  	}).AnyTimes()
   503  
   504  	decimalOne := num.DecimalOne()
   505  	uintOne := num.UintOne()
   506  	commitmentAmount := num.NewUint(10)
   507  	scoresPerLP := map[string]num.Decimal{}
   508  	provisionsPerParty := map[string]*types.LiquidityProvision{}
   509  
   510  	// create liquidity providers accounts and submit provision.
   511  	for provider := range weightsPerLP {
   512  		// set score to one.
   513  		scoresPerLP[provider] = decimalOne
   514  
   515  		// create providers general account and deposit funds into it.
   516  		_, err := testLiquidity.collateralEngine.CreatePartyGeneralAccount(ctx, provider, testLiquidity.asset)
   517  		assert.NoError(t, err)
   518  
   519  		_, err = testLiquidity.collateralEngine.Deposit(ctx, provider, testLiquidity.asset, commitmentAmount)
   520  		assert.NoError(t, err)
   521  
   522  		// submit the provision.
   523  		provision := &types.LiquidityProvisionSubmission{
   524  			MarketID:         testLiquidity.marketID,
   525  			CommitmentAmount: commitmentAmount,
   526  			Reference:        provider,
   527  		}
   528  
   529  		deterministicID := hex.EncodeToString(vgcrypto.Hash([]byte(provider)))
   530  		err = testLiquidity.marketLiquidity.SubmitLiquidityProvision(ctx, provision, provider,
   531  			deterministicID, types.MarketStateActive)
   532  		assert.NoError(t, err)
   533  
   534  		// setup provision per party.
   535  		provisionsPerParty[provider] = &types.LiquidityProvision{
   536  			Party:            provider,
   537  			CommitmentAmount: provision.CommitmentAmount.Clone(),
   538  		}
   539  	}
   540  
   541  	// create party and make it pay liquidity fee.
   542  	createPartyAndPayLiquidityFee(t, num.NewUint(114101), testLiquidity)
   543  
   544  	testLiquidity.liquidityEngine.EXPECT().ProvisionsPerParty().DoAndReturn(func() liquidity.ProvisionsPerParty {
   545  		return provisionsPerParty
   546  	}).AnyTimes()
   547  
   548  	testLiquidity.liquidityEngine.EXPECT().ResetSLAEpoch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
   549  
   550  	// start epoch.
   551  	lastDistributionStep := time.Now()
   552  	now := lastDistributionStep.Add(time.Second * 5)
   553  	testLiquidity.liquidityEngine.EXPECT().ReadyForFeesAllocation(gomock.Any()).Return(true)
   554  
   555  	testLiquidity.marketLiquidity.OnEpochStart(testLiquidity.ctx, now, uintOne, uintOne, uintOne, decimalOne)
   556  
   557  	testLiquidity.liquidityEngine.EXPECT().ResetAverageLiquidityScores().AnyTimes()
   558  	testLiquidity.liquidityEngine.EXPECT().ResetFeeAllocationPeriod(gomock.Any()).AnyTimes()
   559  
   560  	testLiquidity.equityShares.EXPECT().AllShares().DoAndReturn(func() map[string]num.Decimal {
   561  		return weightsPerLP
   562  	})
   563  
   564  	testLiquidity.liquidityEngine.EXPECT().GetAverageLiquidityScores().DoAndReturn(func() map[string]num.Decimal {
   565  		return scoresPerLP
   566  	})
   567  
   568  	// trigger a time tick - this should start allocation fees to LP fee accounts.
   569  	testLiquidity.marketLiquidity.OnTick(ctx, now)
   570  
   571  	for _, provider := range keys {
   572  		acc, err := testLiquidity.collateralEngine.GetPartyLiquidityFeeAccount(
   573  			testLiquidity.marketID,
   574  			provider,
   575  			testLiquidity.asset,
   576  		)
   577  		assert.NoError(t, err)
   578  
   579  		expected := expectedAllocatedFess[provider]
   580  		assert.True(t, expected.EQ(acc.Balance), "party %s liquidity fee account balance is %s, expected: %s", provider, acc.Balance, expected)
   581  	}
   582  
   583  	zeroPointFive := num.NewDecimalFromFloat(0.5)
   584  	expectedSLAPenalties := map[string]*liquidity.SlaPenalty{
   585  		"lp-1": {
   586  			Fee:  num.NewDecimalFromFloat(0),
   587  			Bond: zeroPointFive,
   588  		},
   589  		"lp-2": {
   590  			Fee:  num.NewDecimalFromFloat(0.05),
   591  			Bond: zeroPointFive,
   592  		},
   593  		"lp-3": {
   594  			Fee:  num.NewDecimalFromFloat(0.1),
   595  			Bond: zeroPointFive,
   596  		},
   597  		"lp-4": {
   598  			Fee:  num.NewDecimalFromFloat(0.2),
   599  			Bond: zeroPointFive,
   600  		},
   601  		"lp-5": {
   602  			Fee:  num.NewDecimalFromFloat(0.7),
   603  			Bond: zeroPointFive,
   604  		},
   605  		"lp-6": {
   606  			Fee:  num.NewDecimalFromFloat(1),
   607  			Bond: zeroPointFive,
   608  		},
   609  	}
   610  
   611  	testLiquidity.liquidityEngine.EXPECT().CalculateSLAPenalties(gomock.Any()).DoAndReturn(
   612  		func(_ time.Time) liquidity.SlaPenalties {
   613  			return liquidity.SlaPenalties{
   614  				PenaltiesPerParty: expectedSLAPenalties,
   615  			}
   616  		},
   617  	)
   618  
   619  	testLiquidity.liquidityEngine.EXPECT().
   620  		LiquidityProvisionByPartyID(gomock.Any()).
   621  		DoAndReturn(func(party string) *types.LiquidityProvision {
   622  			return &types.LiquidityProvision{
   623  				ID:               party,
   624  				Party:            party,
   625  				CommitmentAmount: commitmentAmount,
   626  			}
   627  		}).AnyTimes()
   628  
   629  	// end epoch - this should trigger the SLA fees distribution.
   630  	testLiquidity.marketLiquidity.OnEpochEnd(testLiquidity.ctx, now, types.Epoch{})
   631  
   632  	for _, provider := range keys {
   633  		generalAcc, err := testLiquidity.collateralEngine.GetPartyGeneralAccount(
   634  			provider,
   635  			testLiquidity.asset,
   636  		)
   637  		assert.NoError(t, err)
   638  
   639  		expectedFee := expectedDistributedFess[provider]
   640  		assert.Truef(t, expectedFee.EQ(generalAcc.Balance),
   641  			"party %s general account balance is %s, expected: %s", provider, generalAcc.Balance, expectedFee)
   642  	}
   643  
   644  	acc, err := testLiquidity.collateralEngine.GetOrCreateLiquidityFeesBonusDistributionAccount(
   645  		ctx,
   646  		testLiquidity.marketID,
   647  		testLiquidity.asset,
   648  	)
   649  	assert.NoError(t, err)
   650  	assert.True(t, acc.Balance.EQ(num.UintZero()))
   651  
   652  	testLiquidity.equityShares.EXPECT().SetPartyStake(gomock.Any(), gomock.Any()).AnyTimes()
   653  	testLiquidity.equityShares.EXPECT().AllShares().AnyTimes()
   654  	testLiquidity.marketLiquidity.OnEpochStart(testLiquidity.ctx, now, uintOne, uintOne, uintOne, decimalOne)
   655  }
   656  
   657  func TestLiquidityProvisionsAmendments(t *testing.T) {
   658  	testLiquidity := newMarketLiquidity(t)
   659  
   660  	ctx := context.Background()
   661  
   662  	testLiquidity.timeService.EXPECT().GetTimeNow().DoAndReturn(func() time.Time {
   663  		return time.Now()
   664  	}).AnyTimes()
   665  
   666  	testLiquidity.broker.EXPECT().Send(gomock.Any()).AnyTimes()
   667  	testLiquidity.amm.EXPECT().GetAMMPoolsBySubAccount().Return(map[string]common.AMMPool{}).AnyTimes()
   668  
   669  	testLiquidity.liquidityEngine.EXPECT().UpdatePartyCommitment(gomock.Any(), gomock.Any()).DoAndReturn(
   670  		func(partyID string, amount *num.Uint) (*types.LiquidityProvision, error) {
   671  			return &types.LiquidityProvision{
   672  				Party:            partyID,
   673  				CommitmentAmount: amount.Clone(),
   674  			}, nil
   675  		}).AnyTimes()
   676  
   677  	// enable asset first.
   678  	err := testLiquidity.collateralEngine.EnableAsset(ctx, types.Asset{
   679  		ID: testLiquidity.asset,
   680  		Details: &types.AssetDetails{
   681  			Name:     testLiquidity.asset,
   682  			Symbol:   testLiquidity.asset,
   683  			Decimals: 0,
   684  			Source: types.AssetDetailsErc20{
   685  				ERC20: &types.ERC20{
   686  					ContractAddress: "addrs",
   687  					ChainID:         "1",
   688  				},
   689  			},
   690  		},
   691  	})
   692  	assert.NoError(t, err)
   693  
   694  	// create all required accounts for spot market.
   695  	_, _, err = testLiquidity.collateralEngine.CreateMarketAccounts(ctx, testLiquidity.marketID, testLiquidity.asset)
   696  	assert.NoError(t, err)
   697  
   698  	testLiquidity.liquidityEngine.EXPECT().
   699  		SubmitLiquidityProvision(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   700  		AnyTimes()
   701  
   702  	testLiquidity.liquidityEngine.EXPECT().PendingProvision().Return(nil).AnyTimes()
   703  	one := num.UintOne()
   704  	testLiquidity.liquidityEngine.EXPECT().CalculateSuppliedStakeWithoutPending().Return(one).AnyTimes()
   705  	testLiquidity.liquidityEngine.EXPECT().ApplyPendingProvisions(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
   706  
   707  	provider := "lp-1"
   708  	commitmentAmount := num.NewUint(10000)
   709  
   710  	// create providers general account and deposit funds into it.
   711  	_, err = testLiquidity.collateralEngine.CreatePartyGeneralAccount(ctx, provider, testLiquidity.asset)
   712  	assert.NoError(t, err)
   713  
   714  	_, err = testLiquidity.collateralEngine.Deposit(ctx, provider, testLiquidity.asset, commitmentAmount)
   715  	assert.NoError(t, err)
   716  
   717  	// submit the provision.
   718  	provision := &types.LiquidityProvisionSubmission{
   719  		MarketID:         testLiquidity.marketID,
   720  		CommitmentAmount: commitmentAmount,
   721  		Reference:        provider,
   722  	}
   723  
   724  	deterministicID := hex.EncodeToString(vgcrypto.Hash([]byte(provider)))
   725  	err = testLiquidity.marketLiquidity.SubmitLiquidityProvision(ctx, provision, provider,
   726  		deterministicID, types.MarketStateActive)
   727  	assert.NoError(t, err)
   728  
   729  	bAcc, err := testLiquidity.collateralEngine.GetPartyBondAccount(testLiquidity.marketID, provider, testLiquidity.asset)
   730  	assert.NoError(t, err)
   731  	assert.Equal(t, "10000", bAcc.Balance.String())
   732  
   733  	gAcc, err := testLiquidity.collateralEngine.GetPartyGeneralAccount(provider, testLiquidity.asset)
   734  	assert.NoError(t, err)
   735  	assert.Equal(t, "0", gAcc.Balance.String())
   736  
   737  	testLiquidity.liquidityEngine.EXPECT().
   738  		AmendLiquidityProvision(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   739  		Return(true, nil).
   740  		AnyTimes()
   741  
   742  	testLiquidity.liquidityEngine.EXPECT().
   743  		ValidateLiquidityProvisionAmendment(gomock.Any()).
   744  		Return(nil).
   745  		AnyTimes()
   746  
   747  	testLiquidity.liquidityEngine.EXPECT().
   748  		PendingProvisionByPartyID(gomock.Any()).
   749  		Return(nil).
   750  		AnyTimes()
   751  
   752  	testLiquidity.liquidityEngine.EXPECT().
   753  		IsLiquidityProvider(gomock.Any()).
   754  		Return(true).
   755  		AnyTimes()
   756  
   757  	testLiquidity.liquidityEngine.EXPECT().
   758  		LiquidityProvisionByPartyID(gomock.Any()).
   759  		Return(&types.LiquidityProvision{
   760  			ID:               provider,
   761  			Party:            provider,
   762  			CommitmentAmount: commitmentAmount,
   763  		}).
   764  		AnyTimes()
   765  
   766  	lpa := &types.LiquidityProvisionAmendment{
   767  		MarketID:         testLiquidity.marketID,
   768  		CommitmentAmount: num.NewUint(1000),
   769  	}
   770  	err = testLiquidity.marketLiquidity.AmendLiquidityProvision(ctx, lpa, provider,
   771  		deterministicID, types.MarketStateActive)
   772  	assert.NoError(t, err)
   773  
   774  	bAcc, err = testLiquidity.collateralEngine.GetPartyBondAccount(testLiquidity.marketID, provider, testLiquidity.asset)
   775  	assert.NoError(t, err)
   776  	assert.Equal(t, "1000", bAcc.Balance.String())
   777  
   778  	gAcc, err = testLiquidity.collateralEngine.GetPartyGeneralAccount(provider, testLiquidity.asset)
   779  	assert.NoError(t, err)
   780  	assert.Equal(t, "9000", gAcc.Balance.String())
   781  }
   782  
   783  func TestCancelLiquidityProvisionDuringOpeningAuction(t *testing.T) {
   784  	testLiquidity := newMarketLiquidity(t)
   785  
   786  	ctx := context.Background()
   787  
   788  	testLiquidity.timeService.EXPECT().GetTimeNow().DoAndReturn(func() time.Time {
   789  		return time.Now()
   790  	}).AnyTimes()
   791  
   792  	testLiquidity.broker.EXPECT().Send(gomock.Any()).AnyTimes()
   793  
   794  	testLiquidity.amm.EXPECT().GetAMMPoolsBySubAccount().Return(map[string]common.AMMPool{}).AnyTimes()
   795  
   796  	// enable asset first.
   797  	err := testLiquidity.collateralEngine.EnableAsset(ctx, types.Asset{
   798  		ID: testLiquidity.asset,
   799  		Details: &types.AssetDetails{
   800  			Name:     testLiquidity.asset,
   801  			Symbol:   testLiquidity.asset,
   802  			Decimals: 0,
   803  			Source: types.AssetDetailsErc20{
   804  				ERC20: &types.ERC20{
   805  					ContractAddress: "addrs",
   806  					ChainID:         "1",
   807  				},
   808  			},
   809  		},
   810  	})
   811  	assert.NoError(t, err)
   812  
   813  	// create all required accounts for spot market.
   814  	_, _, err = testLiquidity.collateralEngine.CreateMarketAccounts(ctx, testLiquidity.marketID, testLiquidity.asset)
   815  	assert.NoError(t, err)
   816  
   817  	testLiquidity.liquidityEngine.EXPECT().
   818  		SubmitLiquidityProvision(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   819  		Return(true, nil).
   820  		AnyTimes()
   821  
   822  	testLiquidity.equityShares.EXPECT().SetPartyStake(gomock.Any(), gomock.Any())
   823  	testLiquidity.equityShares.EXPECT().AllShares()
   824  
   825  	testLiquidity.liquidityEngine.EXPECT().PendingProvision().Return(nil).AnyTimes()
   826  	one := num.UintOne()
   827  	testLiquidity.liquidityEngine.EXPECT().CalculateSuppliedStakeWithoutPending().Return(one).AnyTimes()
   828  	testLiquidity.liquidityEngine.EXPECT().ApplyPendingProvisions(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
   829  
   830  	provider := "lp-1"
   831  	commitmentAmount := num.NewUint(10000)
   832  
   833  	// create providers general account and deposit funds into it.
   834  	_, err = testLiquidity.collateralEngine.CreatePartyGeneralAccount(ctx, provider, testLiquidity.asset)
   835  	assert.NoError(t, err)
   836  
   837  	_, err = testLiquidity.collateralEngine.Deposit(ctx, provider, testLiquidity.asset, commitmentAmount)
   838  	assert.NoError(t, err)
   839  
   840  	// submit the provision.
   841  	provision := &types.LiquidityProvisionSubmission{
   842  		MarketID:         testLiquidity.marketID,
   843  		CommitmentAmount: commitmentAmount,
   844  		Reference:        provider,
   845  	}
   846  
   847  	deterministicID := hex.EncodeToString(vgcrypto.Hash([]byte(provider)))
   848  	err = testLiquidity.marketLiquidity.SubmitLiquidityProvision(ctx, provision, provider,
   849  		deterministicID, types.MarketStateActive)
   850  	assert.NoError(t, err)
   851  
   852  	bAcc, err := testLiquidity.collateralEngine.GetPartyBondAccount(testLiquidity.marketID, provider, testLiquidity.asset)
   853  	assert.NoError(t, err)
   854  	assert.Equal(t, "10000", bAcc.Balance.String())
   855  
   856  	gAcc, err := testLiquidity.collateralEngine.GetPartyGeneralAccount(provider, testLiquidity.asset)
   857  	assert.NoError(t, err)
   858  	assert.Equal(t, "0", gAcc.Balance.String())
   859  
   860  	testLiquidity.liquidityEngine.EXPECT().
   861  		AmendLiquidityProvision(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   862  		Return(true, nil).
   863  		AnyTimes()
   864  
   865  	testLiquidity.liquidityEngine.EXPECT().
   866  		ValidateLiquidityProvisionAmendment(gomock.Any()).
   867  		Return(nil).
   868  		AnyTimes()
   869  
   870  	testLiquidity.liquidityEngine.EXPECT().
   871  		PendingProvisionByPartyID(gomock.Any()).
   872  		Return(nil).
   873  		AnyTimes()
   874  
   875  	testLiquidity.liquidityEngine.EXPECT().
   876  		IsLiquidityProvider(gomock.Any()).
   877  		Return(true).
   878  		AnyTimes()
   879  
   880  	testLiquidity.liquidityEngine.EXPECT().
   881  		LiquidityProvisionByPartyID(gomock.Any()).
   882  		Return(&types.LiquidityProvision{
   883  			ID:               provider,
   884  			Party:            provider,
   885  			CommitmentAmount: commitmentAmount,
   886  		}).
   887  		AnyTimes()
   888  
   889  	testLiquidity.equityShares.EXPECT().SetPartyStake(provider, gomock.Any()).Times(1)
   890  	testLiquidity.equityShares.EXPECT().AllShares().Times(1).Return(nil)
   891  	err = testLiquidity.marketLiquidity.CancelLiquidityProvision(ctx, provider)
   892  	assert.NoError(t, err)
   893  
   894  	bAcc, err = testLiquidity.collateralEngine.GetPartyBondAccount(testLiquidity.marketID, provider, testLiquidity.asset)
   895  	assert.NoError(t, err)
   896  	assert.Equal(t, "0", bAcc.Balance.String())
   897  
   898  	gAcc, err = testLiquidity.collateralEngine.GetPartyGeneralAccount(provider, testLiquidity.asset)
   899  	assert.NoError(t, err)
   900  	assert.Equal(t, "10000", gAcc.Balance.String())
   901  }