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 }