code.vegaprotocol.io/vega@v0.79.0/core/integration/steps/transfers.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 "context" 20 "errors" 21 "strconv" 22 "strings" 23 "time" 24 25 "code.vegaprotocol.io/vega/core/banking" 26 "code.vegaprotocol.io/vega/core/types" 27 "code.vegaprotocol.io/vega/libs/num" 28 proto "code.vegaprotocol.io/vega/protos/vega" 29 30 "github.com/cucumber/godog" 31 ) 32 33 func PartiesAvailableFeeDiscounts( 34 engine *banking.Engine, 35 table *godog.Table, 36 ) error { 37 errs := []error{} 38 for _, r := range parseTransferFeeDiscountTable(table) { 39 asset := r.MustStr("asset") 40 party := r.MustStr("party") 41 actual := engine.AvailableFeeDiscount(asset, party) 42 expected := r.MustStr("available discount") 43 if expected != actual.String() { 44 errs = append(errs, errors.New(r.MustStr("party")+" expected "+expected+" but got "+actual.String())) 45 } 46 } 47 if len(errs) > 0 { 48 return ErrStack(errs) 49 } 50 return nil 51 } 52 53 func parseTransferFeeDiscountTable(table *godog.Table) []RowWrapper { 54 return StrictParseTable(table, []string{ 55 "party", 56 "asset", 57 "available discount", 58 }, []string{}) 59 } 60 61 func PartiesSubmitTransfers( 62 engine *banking.Engine, 63 table *godog.Table, 64 ) error { 65 errs := []error{} 66 for _, r := range parseOneOffTransferTable(table) { 67 transfer, _ := rowToOneOffTransfer(r) 68 err := engine.TransferFunds(context.Background(), &types.TransferFunds{ 69 Kind: types.TransferCommandKindOneOff, 70 OneOff: transfer, 71 }) 72 if len(r.Str("error")) > 0 || err != nil { 73 expected := r.Str("error") 74 actual := "" 75 if err != nil { 76 actual = err.Error() 77 } 78 if expected != actual { 79 errs = append(errs, errors.New(r.MustStr("id")+" expected "+expected+" but got "+actual)) 80 } 81 } 82 } 83 if len(errs) > 0 { 84 return ErrStack(errs) 85 } 86 return nil 87 } 88 89 func parseOneOffTransferTable(table *godog.Table) []RowWrapper { 90 return StrictParseTable(table, []string{ 91 "id", "from", "from_account_type", "to", "to_account_type", "asset", "amount", "delivery_time", 92 }, []string{"market", "error"}) 93 } 94 95 func rowToOneOffTransfer(r RowWrapper) (*types.OneOffTransfer, error) { 96 id := r.MustStr("id") 97 from := r.MustStr("from") 98 fromAccountType := r.MustStr("from_account_type") 99 fromAT := proto.AccountType_value[fromAccountType] 100 to := r.MustStr("to") 101 toAccuontType := r.MustStr("to_account_type") 102 toAT := proto.AccountType_value[toAccuontType] 103 asset := r.MustStr("asset") 104 amount := r.MustStr("amount") 105 amountUint, _ := num.UintFromString(amount, 10) 106 deliveryTime, err := time.Parse("2006-01-02T15:04:05Z", r.MustStr("delivery_time")) 107 if err != nil { 108 return nil, err 109 } 110 111 oneOff := &types.OneOffTransfer{ 112 TransferBase: &types.TransferBase{ 113 ID: id, 114 From: from, 115 FromAccountType: types.AccountType(fromAT), 116 To: to, 117 ToAccountType: types.AccountType(toAT), 118 Asset: asset, 119 Amount: amountUint, 120 }, 121 DeliverOn: &deliveryTime, 122 } 123 return oneOff, nil 124 } 125 126 func PartiesSubmitRecurringTransfers( 127 engine *banking.Engine, 128 table *godog.Table, 129 ) error { 130 errs := []error{} 131 for _, r := range parseRecurringTransferTable(table) { 132 transfer := rowToRecurringTransfer(r) 133 err := engine.TransferFunds(context.Background(), &types.TransferFunds{ 134 Kind: types.TransferCommandKindRecurring, 135 Recurring: transfer, 136 }) 137 if len(r.Str("error")) > 0 || err != nil { 138 expected := r.Str("error") 139 actual := "" 140 if err != nil { 141 actual = err.Error() 142 } 143 if expected != actual { 144 errs = append(errs, errors.New(r.MustStr("id")+" expected "+expected+" but got "+actual)) 145 } 146 } 147 } 148 if len(errs) > 0 { 149 return ErrStack(errs) 150 } 151 return nil 152 } 153 154 func parseRecurringTransferTable(table *godog.Table) []RowWrapper { 155 return StrictParseTable(table, []string{ 156 "id", "from", "from_account_type", "to", "to_account_type", "asset", "amount", "start_epoch", "end_epoch", "factor", 157 }, []string{"metric", "metric_asset", "markets", "lock_period", "window_length", "entity_scope", "individual_scope", "teams", "ntop", "staking_requirement", "notional_requirement", "distribution_strategy", "ranks", "cap_reward_fee_multiple", "transfer_interval", "target_notional_volume", "eligible_keys", "error"}) 158 } 159 160 func rowToRecurringTransfer(r RowWrapper) *types.RecurringTransfer { 161 id := r.MustStr("id") 162 from := r.MustStr("from") 163 fromAccountType := r.MustStr("from_account_type") 164 fromAT := proto.AccountType_value[fromAccountType] 165 to := r.MustStr("to") 166 toAccuontType := r.MustStr("to_account_type") 167 toAT := proto.AccountType_value[toAccuontType] 168 asset := r.MustStr("asset") 169 amount := r.MustStr("amount") 170 amountUint, _ := num.UintFromString(amount, 10) 171 startEpoch, _ := num.UintFromString(r.MustStr("start_epoch"), 10) 172 endEpoch := r.MustStr("end_epoch") 173 var endEpochPtr *uint64 174 if len(endEpoch) > 0 { 175 endEpochUint, _ := num.UintFromString(r.MustStr("end_epoch"), 10) 176 endEpochUint64 := endEpochUint.Uint64() 177 endEpochPtr = &endEpochUint64 178 } 179 180 var dispatchStrategy *proto.DispatchStrategy 181 if len(r.Str("metric")) > 0 { 182 mkts := strings.Split(r.MustStr("markets"), ",") 183 if len(mkts) == 1 && mkts[0] == "" { 184 mkts = []string{} 185 } 186 lockPeriod := uint64(1) 187 if r.HasColumn("lock_period") { 188 lockPeriod = r.U64("lock_period") 189 } 190 windowLength := uint64(1) 191 if r.HasColumn("window_length") { 192 windowLength = r.U64("window_length") 193 } 194 195 var transferInterval *int32 196 if r.HasColumn("transfer_interval") { 197 interval := r.I32("transfer_interval") 198 transferInterval = &interval 199 } 200 201 var eligibleKeys []string 202 if r.HasColumn("eligible_keys") { 203 eligibleKeys = r.StrSlice("eligible_keys", ",") 204 } 205 var targetNotionalVolume *string 206 if r.HasColumn("target_notional_volume") { 207 tnv := r.Str("target_notional_volume") 208 targetNotionalVolume = &tnv 209 } 210 211 distributionStrategy := proto.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA 212 var ranks []*proto.Rank 213 if r.HasColumn("distribution_strategy") { 214 distStrat := r.Str("distribution_strategy") 215 if distStrat == "PRO_RATA" { 216 distributionStrategy = proto.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA 217 } else if distStrat == "RANK" || distStrat == "RANK_LOTTERY" { 218 if distStrat == "RANK" { 219 distributionStrategy = proto.DistributionStrategy_DISTRIBUTION_STRATEGY_RANK 220 } else { 221 distributionStrategy = proto.DistributionStrategy_DISTRIBUTION_STRATEGY_RANK_LOTTERY 222 } 223 rankList := strings.Split(r.MustStr("ranks"), ",") 224 ranks = make([]*proto.Rank, 0, len(rankList)) 225 for _, r := range rankList { 226 rr := strings.Split(r, ":") 227 startRank, _ := strconv.ParseUint(rr[0], 10, 32) 228 shareRatio, _ := strconv.ParseUint(rr[1], 10, 32) 229 ranks = append(ranks, &proto.Rank{StartRank: uint32(startRank), ShareRatio: uint32(shareRatio)}) 230 } 231 } 232 } 233 234 entityScope := proto.EntityScope_ENTITY_SCOPE_INDIVIDUALS 235 if r.HasColumn("entity_scope") { 236 scope := r.Str("entity_scope") 237 if scope == "INDIVIDUALS" { 238 entityScope = proto.EntityScope_ENTITY_SCOPE_INDIVIDUALS 239 } else if scope == "TEAMS" { 240 entityScope = proto.EntityScope_ENTITY_SCOPE_TEAMS 241 } 242 } 243 244 indiScope := proto.IndividualScope_INDIVIDUAL_SCOPE_UNSPECIFIED 245 if entityScope == proto.EntityScope_ENTITY_SCOPE_INDIVIDUALS { 246 indiScope = proto.IndividualScope_INDIVIDUAL_SCOPE_ALL 247 if r.HasColumn("individual_scope") { 248 indiScopeStr := r.Str("individual_scope") 249 if indiScopeStr == "ALL" { 250 indiScope = proto.IndividualScope_INDIVIDUAL_SCOPE_ALL 251 } else if indiScopeStr == "IN_TEAM" { 252 indiScope = proto.IndividualScope_INDIVIDUAL_SCOPE_IN_TEAM 253 } else if indiScopeStr == "NOT_IN_TEAM" { 254 indiScope = proto.IndividualScope_INDIVIDUAL_SCOPE_NOT_IN_TEAM 255 } else if indiScopeStr == "INDIVIDUAL_SCOPE_AMM" { 256 indiScope = proto.IndividualScope_INDIVIDUAL_SCOPE_AMM 257 } 258 } 259 } 260 261 teams := []string{} 262 ntop := "" 263 if entityScope == proto.EntityScope_ENTITY_SCOPE_TEAMS { 264 if r.HasColumn("teams") { 265 teams = strings.Split(r.MustStr("teams"), ",") 266 if len(teams) == 1 && teams[0] == "" { 267 teams = []string{} 268 } 269 } 270 ntop = r.MustStr("ntop") 271 } 272 273 stakingRequirement := "" 274 notionalRequirement := "" 275 capRewardFeeMultiple := "" 276 if r.HasColumn("staking_requirement") { 277 stakingRequirement = r.MustStr("staking_requirement") 278 } 279 if r.HasColumn("notional_requirement") { 280 notionalRequirement = r.mustColumn("notional_requirement") 281 } 282 if r.HasColumn("cap_reward_fee_multiple") { 283 capRewardFeeMultiple = r.MustStr("cap_reward_fee_multiple") 284 } 285 286 dispatchStrategy = &proto.DispatchStrategy{ 287 AssetForMetric: r.MustStr("metric_asset"), 288 Markets: mkts, 289 Metric: proto.DispatchMetric(proto.DispatchMetric_value[r.MustStr("metric")]), 290 DistributionStrategy: distributionStrategy, 291 LockPeriod: lockPeriod, 292 EntityScope: entityScope, 293 IndividualScope: indiScope, 294 WindowLength: windowLength, 295 TeamScope: teams, 296 NTopPerformers: ntop, 297 StakingRequirement: stakingRequirement, 298 NotionalTimeWeightedAveragePositionRequirement: notionalRequirement, 299 RankTable: ranks, 300 TransferInterval: transferInterval, 301 EligibleKeys: eligibleKeys, 302 TargetNotionalVolume: targetNotionalVolume, 303 } 304 if capRewardFeeMultiple != "" { 305 dispatchStrategy.CapRewardFeeMultiple = &capRewardFeeMultiple 306 } 307 } 308 309 factor := num.MustDecimalFromString(r.MustStr("factor")) 310 recurring := &types.RecurringTransfer{ 311 TransferBase: &types.TransferBase{ 312 ID: id, 313 From: from, 314 FromAccountType: types.AccountType(fromAT), 315 To: to, 316 ToAccountType: types.AccountType(toAT), 317 Asset: asset, 318 Amount: amountUint, 319 }, 320 StartEpoch: startEpoch.Uint64(), 321 EndEpoch: endEpochPtr, 322 Factor: factor, 323 DispatchStrategy: dispatchStrategy, 324 } 325 return recurring 326 } 327 328 func PartiesCancelTransfers( 329 engine *banking.Engine, 330 table *godog.Table, 331 ) error { 332 errs := []error{} 333 for _, r := range parseOneOffCancellationTable(table) { 334 err := engine.CancelTransferFunds(context.Background(), &types.CancelTransferFunds{ 335 Party: r.MustStr("party"), 336 TransferID: r.MustStr("transfer_id"), 337 }) 338 if len(r.Str("error")) > 0 || err != nil { 339 expected := r.Str("error") 340 actual := "" 341 if err != nil { 342 actual = err.Error() 343 } 344 if expected != actual { 345 errs = append(errs, errors.New(r.MustStr("transfer_id")+" expected "+expected+" but got "+actual)) 346 } 347 } 348 } 349 if len(errs) > 0 { 350 return ErrStack(errs) 351 } 352 return nil 353 } 354 355 func parseOneOffCancellationTable(table *godog.Table) []RowWrapper { 356 return StrictParseTable(table, []string{ 357 "party", "transfer_id", 358 }, []string{"error"}) 359 }