code.vegaprotocol.io/vega@v0.79.0/core/integration/steps/volume_discount_program.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 steps
    17  
    18  import (
    19  	"fmt"
    20  	"time"
    21  
    22  	"code.vegaprotocol.io/vega/core/types"
    23  	"code.vegaprotocol.io/vega/core/volumediscount"
    24  	"code.vegaprotocol.io/vega/libs/num"
    25  
    26  	"github.com/cucumber/godog"
    27  )
    28  
    29  func VolumeDiscountProgramTiers(
    30  	tiers map[string][]*types.VolumeBenefitTier,
    31  	volumeDiscountTierName string,
    32  	table *godog.Table,
    33  ) error {
    34  	rows := parseVolumeDiscountTiersTable(table)
    35  	vbts := make([]*types.VolumeBenefitTier, 0, len(rows))
    36  	for _, r := range rows {
    37  		row := volumeDiscountTiersRow{row: r}
    38  		p := &types.VolumeBenefitTier{
    39  			MinimumRunningNotionalTakerVolume: row.volume(),
    40  			VolumeDiscountFactors: types.Factors{
    41  				Infra:     row.infraFactor(),
    42  				Maker:     row.makerFactor(),
    43  				Liquidity: row.liqFactor(),
    44  			},
    45  		}
    46  
    47  		vbts = append(vbts, p)
    48  	}
    49  	tiers[volumeDiscountTierName] = vbts
    50  	return nil
    51  }
    52  
    53  func parseVolumeDiscountTiersTable(table *godog.Table) []RowWrapper {
    54  	return StrictParseTable(table, []string{
    55  		"volume",
    56  		"infra factor",
    57  		"maker factor",
    58  		"liquidity factor",
    59  	}, []string{})
    60  }
    61  
    62  type volumeDiscountTiersRow struct {
    63  	row RowWrapper
    64  }
    65  
    66  func (r volumeDiscountTiersRow) volume() *num.Uint {
    67  	return r.row.MustUint("volume")
    68  }
    69  
    70  func (r volumeDiscountTiersRow) infraFactor() num.Decimal {
    71  	return r.row.MustDecimal("infra factor")
    72  }
    73  
    74  func (r volumeDiscountTiersRow) makerFactor() num.Decimal {
    75  	return r.row.MustDecimal("maker factor")
    76  }
    77  
    78  func (r volumeDiscountTiersRow) liqFactor() num.Decimal {
    79  	return r.row.MustDecimal("liquidity factor")
    80  }
    81  
    82  func VolumeDiscountProgram(
    83  	vde *volumediscount.Engine,
    84  	tiers map[string][]*types.VolumeBenefitTier,
    85  	table *godog.Table,
    86  ) error {
    87  	rows := parseVolumeDiscountTable(table)
    88  	vdp := types.VolumeDiscountProgram{}
    89  
    90  	for _, r := range rows {
    91  		row := volumeDiscountRow{row: r}
    92  		vdp.ID = row.id()
    93  		vdp.WindowLength = row.windowLength()
    94  		if row.closingTimestamp() == 0 {
    95  			vdp.EndOfProgramTimestamp = time.Time{}
    96  		} else {
    97  			vdp.EndOfProgramTimestamp = time.Unix(row.closingTimestamp(), 0)
    98  		}
    99  		tierName := row.tiers()
   100  		if tier := tiers[tierName]; tier != nil {
   101  			vdp.VolumeBenefitTiers = tier
   102  		}
   103  		vde.UpdateProgram(&vdp)
   104  	}
   105  	return nil
   106  }
   107  
   108  func parseVolumeDiscountTable(table *godog.Table) []RowWrapper {
   109  	return StrictParseTable(table, []string{
   110  		"id",
   111  		"tiers",
   112  		"closing timestamp",
   113  		"window length",
   114  	}, []string{})
   115  }
   116  
   117  func parseFactorRow(table *godog.Table) []RowWrapper {
   118  	return StrictParseTable(table, []string{
   119  		"party",
   120  		"maker factor",
   121  		"liquidity factor",
   122  		"infra factor",
   123  	}, []string{})
   124  }
   125  
   126  type factorRow struct {
   127  	r RowWrapper
   128  }
   129  
   130  func (f factorRow) party() types.PartyID {
   131  	return types.PartyID(f.r.MustStr("party"))
   132  }
   133  
   134  func (f factorRow) maker() num.Decimal {
   135  	return f.r.MustDecimal("maker factor")
   136  }
   137  
   138  func (f factorRow) liquidity() num.Decimal {
   139  	return f.r.MustDecimal("liquidity factor")
   140  }
   141  
   142  func (f factorRow) infra() num.Decimal {
   143  	return f.r.MustDecimal("infra factor")
   144  }
   145  
   146  func (f factorRow) String() string {
   147  	return fmt.Sprintf("maker: %s, liquidity: %s, infra: %s", f.maker(), f.liquidity(), f.infra())
   148  }
   149  
   150  type volumeDiscountRow struct {
   151  	row RowWrapper
   152  }
   153  
   154  func (r volumeDiscountRow) id() string {
   155  	return r.row.MustStr("id")
   156  }
   157  
   158  func (r volumeDiscountRow) tiers() string {
   159  	return r.row.MustStr("tiers")
   160  }
   161  
   162  func (r volumeDiscountRow) closingTimestamp() int64 {
   163  	return r.row.MustI64("closing timestamp")
   164  }
   165  
   166  func (r volumeDiscountRow) windowLength() uint64 {
   167  	return r.row.MustU64("window length")
   168  }
   169  
   170  func PartiesHaveTheFollowingDiscountFactors(vde *volumediscount.Engine, table *godog.Table) error {
   171  	for _, r := range parseFactorRow(table) {
   172  		row := factorRow{
   173  			r: r,
   174  		}
   175  		party := row.party()
   176  		factors := vde.VolumeDiscountFactorForParty(party)
   177  		if !factors.Maker.Equal(row.maker()) || !factors.Liquidity.Equal(row.liquidity()) || !factors.Infra.Equal(row.infra()) {
   178  			return fmt.Errorf(
   179  				"factors for party %s don't match. Expected (%s), got (maker: %s, liquidity: %s, infra: %s)",
   180  				party,
   181  				row,
   182  				factors.Maker,
   183  				factors.Liquidity,
   184  				factors.Infra,
   185  			)
   186  		}
   187  	}
   188  	return nil
   189  }
   190  
   191  func PartyHasTheFollowingDiscountInfraFactor(party, discountFactor string, vde *volumediscount.Engine) error {
   192  	df := vde.VolumeDiscountFactorForParty(types.PartyID(party))
   193  	df2, _ := num.DecimalFromString(discountFactor)
   194  	if !df.Infra.Equal(df2) {
   195  		return fmt.Errorf("%s has the discount factor of %s when we expected %s", party, df, df2)
   196  	}
   197  	return nil
   198  }
   199  
   200  func PartyHasTheFollowingDiscountMakerFactor(party, discountFactor string, vde *volumediscount.Engine) error {
   201  	df := vde.VolumeDiscountFactorForParty(types.PartyID(party))
   202  	df2, _ := num.DecimalFromString(discountFactor)
   203  	if !df.Maker.Equal(df2) {
   204  		return fmt.Errorf("%s has the discount factor of %s when we expected %s", party, df, df2)
   205  	}
   206  	return nil
   207  }
   208  
   209  func PartyHasTheFollowingDiscountLiquidityFactor(party, discountFactor string, vde *volumediscount.Engine) error {
   210  	df := vde.VolumeDiscountFactorForParty(types.PartyID(party))
   211  	df2, _ := num.DecimalFromString(discountFactor)
   212  	if !df.Liquidity.Equal(df2) {
   213  		return fmt.Errorf("%s has the discount factor of %s when we expected %s", party, df, df2)
   214  	}
   215  	return nil
   216  }
   217  
   218  func PartyHasTheFollowingTakerNotional(party, notional string, vde *volumediscount.Engine) error {
   219  	tn := vde.TakerNotionalForParty(types.PartyID(party))
   220  	tn2, _ := num.DecimalFromString(notional)
   221  	if !tn.Equal(tn2) {
   222  		return fmt.Errorf("%s has the taker notional of %s when we expected %s", party, tn, tn2)
   223  	}
   224  	return nil
   225  }
   226  
   227  func AMMHasTheFollowingNotionalValue(exec Execution, vde *volumediscount.Engine, alias, value string) error {
   228  	id, ok := exec.GetAMMSubAccountID(alias)
   229  	if !ok {
   230  		return fmt.Errorf("unknown vAMM alias %s", alias)
   231  	}
   232  	// from this point, it's the same as for a normal party
   233  	return PartyHasTheFollowingTakerNotional(id, value, vde)
   234  }
   235  
   236  func AMMHasTheFollowingDiscountInfraFactor(exec Execution, vde *volumediscount.Engine, alias, factor string) error {
   237  	id, ok := exec.GetAMMSubAccountID(alias)
   238  	if !ok {
   239  		return fmt.Errorf("unknown vAMM alias %s", alias)
   240  	}
   241  	return PartyHasTheFollowingDiscountInfraFactor(id, factor, vde)
   242  }
   243  
   244  func AMMHasTheFollowingDiscountMakerFactor(exec Execution, vde *volumediscount.Engine, alias, factor string) error {
   245  	id, ok := exec.GetAMMSubAccountID(alias)
   246  	if !ok {
   247  		return fmt.Errorf("unknown vAMM alias %s", alias)
   248  	}
   249  	return PartyHasTheFollowingDiscountMakerFactor(id, factor, vde)
   250  }
   251  
   252  func AMMHasTheFollowingDiscountLiquidityFactor(exec Execution, vde *volumediscount.Engine, alias, factor string) error {
   253  	id, ok := exec.GetAMMSubAccountID(alias)
   254  	if !ok {
   255  		return fmt.Errorf("unknown vAMM alias %s", alias)
   256  	}
   257  	return PartyHasTheFollowingDiscountInfraFactor(id, factor, vde)
   258  }