-
Notifications
You must be signed in to change notification settings - Fork 15
/
bonds.go
649 lines (572 loc) · 21.3 KB
/
bonds.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
package types
import (
"encoding/json"
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"sort"
)
const (
PowerFunction = "power_function"
SigmoidFunction = "sigmoid_function"
SwapperFunction = "swapper_function"
AugmentedFunction = "augmented_function"
HatchState = "HATCH"
OpenState = "OPEN"
SettleState = "SETTLE"
DoNotModifyField = "[do-not-modify]"
AnyNumberOfReserveTokens = -1
)
type FunctionParamRestrictions func(paramsMap map[string]sdk.Dec) error
var (
RequiredParamsForFunctionType = map[string][]string{
PowerFunction: {"m", "n", "c"},
SigmoidFunction: {"a", "b", "c"},
SwapperFunction: nil,
AugmentedFunction: {"d0", "p0", "theta", "kappa"},
}
NoOfReserveTokensForFunctionType = map[string]int{
PowerFunction: AnyNumberOfReserveTokens,
SigmoidFunction: AnyNumberOfReserveTokens,
SwapperFunction: 2,
AugmentedFunction: AnyNumberOfReserveTokens,
}
ExtraParameterRestrictions = map[string]FunctionParamRestrictions{
PowerFunction: powerParameterRestrictions,
SigmoidFunction: sigmoidParameterRestrictions,
SwapperFunction: nil,
AugmentedFunction: augmentedParameterRestrictions,
}
)
type FunctionParam struct {
Param string `json:"param" yaml:"param"`
Value sdk.Dec `json:"value" yaml:"value"`
}
func NewFunctionParam(param string, value sdk.Dec) FunctionParam {
return FunctionParam{
Param: param,
Value: value,
}
}
type FunctionParams []FunctionParam
func (fps FunctionParams) Validate(functionType string) error {
// Come up with list of expected parameters
expectedParams, err := GetRequiredParamsForFunctionType(functionType)
if err != nil {
return err
}
// Check that number of params is as expected
if len(fps) != len(expectedParams) {
return sdkerrors.Wrapf(ErrIncorrectNumberOfFunctionParameters, "expected %d", len(expectedParams))
}
// Check that params match and all values are non-negative
paramsMap := fps.AsMap()
for _, p := range expectedParams {
val, ok := paramsMap[p]
if !ok {
return sdkerrors.Wrap(ErrFunctionParameterMissingOrNonFloat, p)
} else if val.IsNegative() {
return sdkerrors.Wrap(ErrArgumentCannotBeNegative, p)
}
}
// Get extra function parameter restrictions
extraRestrictions, err := GetExceptionsForFunctionType(functionType)
if err != nil {
return err
}
if extraRestrictions != nil {
err := extraRestrictions(paramsMap)
if err != nil {
return err
}
}
return nil
}
func (fps FunctionParams) String() (result string) {
output, err := json.Marshal(fps)
if err != nil {
panic(err)
}
return string(output)
}
func (fps FunctionParams) AsMap() (paramsMap map[string]sdk.Dec) {
paramsMap = make(map[string]sdk.Dec)
for _, fp := range fps {
paramsMap[fp.Param] = fp.Value
}
return paramsMap
}
func powerParameterRestrictions(paramsMap map[string]sdk.Dec) error {
// Power exception 1: n must be an integer, otherwise x^n loop does not work
val, ok := paramsMap["n"]
if !ok {
panic("did not find parameter n for power function")
} else if !val.TruncateDec().Equal(val) {
return sdkerrors.Wrap(ErrArgumentMustBeInteger, "FunctionParams:n")
}
return nil
}
func sigmoidParameterRestrictions(paramsMap map[string]sdk.Dec) error {
// Sigmoid exception 1: c != 0, otherwise we run into divisions by zero
val, ok := paramsMap["c"]
if !ok {
panic("did not find parameter c for sigmoid function")
} else if !val.IsPositive() {
return sdkerrors.Wrap(ErrArgumentMustBePositive, "FunctionParams:c")
}
return nil
}
func augmentedParameterRestrictions(paramsMap map[string]sdk.Dec) error {
// Augmented exception 1.1: d0 must be an integer, since it is a token amount
// Augmented exception 1.2: d0 != 0, otherwise we run into divisions by zero
val, ok := paramsMap["d0"]
if !ok {
panic("did not find parameter d0 for augmented function")
} else if !val.TruncateDec().Equal(val) {
return sdkerrors.Wrap(ErrArgumentMustBeInteger, "FunctionParams:d0")
} else if !val.IsPositive() {
return sdkerrors.Wrap(ErrArgumentMustBePositive, "FunctionParams:d0")
}
// Augmented exception 2: p0 != 0, otherwise we run into divisions by zero
val, ok = paramsMap["p0"]
if !ok {
panic("did not find parameter p0 for augmented function")
} else if !val.IsPositive() {
return sdkerrors.Wrap(ErrArgumentMustBePositive, "FunctionParams:p0")
}
// Augmented exception 3: theta must be from 0 to 1 (excluding 1)
val, ok = paramsMap["theta"]
if !ok {
panic("did not find parameter theta for augmented function")
} else if val.LT(sdk.ZeroDec()) || val.GTE(sdk.OneDec()) {
return sdkerrors.Wrapf(ErrArgumentMustBeBetween, "%s argument must be between %s and %s", "FunctionParams:theta", "0", "1")
}
// Augmented exception 4.1: kappa must be an integer, since we use it for powers
// Augmented exception 4.2: kappa != 0, otherwise we run into divisions by zero
val, ok = paramsMap["kappa"]
if !ok {
panic("did not find parameter kappa for augmented function")
} else if !val.TruncateDec().Equal(val) {
return sdkerrors.Wrap(ErrArgumentMustBeInteger, "FunctionParams:kappa")
} else if !val.IsPositive() {
return sdkerrors.Wrap(ErrArgumentMustBePositive, "FunctionParams:kappa")
}
return nil
}
type Bond struct {
Token string `json:"token" yaml:"token"`
Name string `json:"name" yaml:"name"`
Description string `json:"description" yaml:"description"`
Creator sdk.AccAddress `json:"creator" yaml:"creator"`
FunctionType string `json:"function_type" yaml:"function_type"`
FunctionParameters FunctionParams `json:"function_parameters" yaml:"function_parameters"`
ReserveTokens []string `json:"reserve_tokens" yaml:"reserve_tokens"`
TxFeePercentage sdk.Dec `json:"tx_fee_percentage" yaml:"tx_fee_percentage"`
ExitFeePercentage sdk.Dec `json:"exit_fee_percentage" yaml:"exit_fee_percentage"`
FeeAddress sdk.AccAddress `json:"fee_address" yaml:"fee_address"`
MaxSupply sdk.Coin `json:"max_supply" yaml:"max_supply"`
OrderQuantityLimits sdk.Coins `json:"order_quantity_limits" yaml:"order_quantity_limits"`
SanityRate sdk.Dec `json:"sanity_rate" yaml:"sanity_rate"`
SanityMarginPercentage sdk.Dec `json:"sanity_margin_percentage" yaml:"sanity_margin_percentage"`
CurrentSupply sdk.Coin `json:"current_supply" yaml:"current_supply"`
CurrentReserve sdk.Coins `json:"current_reserve" yaml:"current_reserve"`
AllowSells bool `json:"allow_sells" yaml:"allow_sells"`
Signers []sdk.AccAddress `json:"signers" yaml:"signers"`
BatchBlocks sdk.Uint `json:"batch_blocks" yaml:"batch_blocks"`
OutcomePayment sdk.Coins `json:"outcome_payment" yaml:"outcome_payment"`
State string `json:"state" yaml:"state"`
}
func NewBond(token, name, description string, creator sdk.AccAddress,
functionType string, functionParameters FunctionParams, reserveTokens []string,
txFeePercentage, exitFeePercentage sdk.Dec, feeAddress sdk.AccAddress,
maxSupply sdk.Coin, orderQuantityLimits sdk.Coins, sanityRate,
sanityMarginPercentage sdk.Dec, allowSells bool, signers []sdk.AccAddress,
batchBlocks sdk.Uint, outcomePayment sdk.Coins, state string) Bond {
// Ensure tokens and coins are sorted
sort.Strings(reserveTokens)
orderQuantityLimits = orderQuantityLimits.Sort()
return Bond{
Token: token,
Name: name,
Description: description,
Creator: creator,
FunctionType: functionType,
FunctionParameters: functionParameters,
ReserveTokens: reserveTokens,
TxFeePercentage: txFeePercentage,
ExitFeePercentage: exitFeePercentage,
FeeAddress: feeAddress,
MaxSupply: maxSupply,
OrderQuantityLimits: orderQuantityLimits,
SanityRate: sanityRate,
SanityMarginPercentage: sanityMarginPercentage,
CurrentSupply: sdk.NewCoin(token, sdk.ZeroInt()),
CurrentReserve: nil,
AllowSells: allowSells,
Signers: signers,
BatchBlocks: batchBlocks,
OutcomePayment: outcomePayment,
State: state,
}
}
//noinspection GoNilness
func (bond Bond) GetNewReserveDecCoins(amount sdk.Dec) (coins sdk.DecCoins) {
for _, r := range bond.ReserveTokens {
coins = coins.Add(sdk.NewDecCoinFromDec(r, amount))
}
return coins
}
func (bond Bond) GetPricesAtSupply(supply sdk.Int) (result sdk.DecCoins, err error) {
if supply.IsNegative() {
panic(fmt.Sprintf("negative supply for bond %s", bond.Token))
}
args := bond.FunctionParameters.AsMap()
x := supply.ToDec()
switch bond.FunctionType {
case PowerFunction:
m := args["m"]
n64 := args["n"].TruncateInt64() // enforced by powerParameterRestrictions
c := args["c"]
result = bond.GetNewReserveDecCoins(
x.Power(uint64(n64)).Mul(m).Add(c))
case SigmoidFunction:
a := args["a"]
b := args["b"]
c := args["c"]
temp1 := x.Sub(b)
temp2 := temp1.Mul(temp1).Add(c)
temp3, err := temp2.ApproxSqrt()
if err != nil {
panic(err) // TODO: consider error handling
}
result = bond.GetNewReserveDecCoins(
a.Mul(temp1.Quo(temp3).Add(sdk.OneDec())))
case AugmentedFunction:
// Note: during the hatch phase, this function returns the hatch price
// p0 even if the supply argument is greater than the initial supply S0
switch bond.State {
case HatchState:
result = bond.GetNewReserveDecCoins(args["p0"])
case OpenState:
kappa := args["kappa"].TruncateInt64()
res := Reserve(x, kappa, args["V0"])
// If reserve < 1, default to zero price to avoid calculation issues
if res.LT(sdk.OneDec()) {
result = bond.GetNewReserveDecCoins(sdk.ZeroDec())
} else {
spotPriceDec := SpotPrice(res, kappa, args["V0"])
result = bond.GetNewReserveDecCoins(spotPriceDec)
}
default:
panic("unrecognized bond state")
}
case SwapperFunction:
return nil, sdkerrors.Wrap(ErrFunctionNotAvailableForFunctionType, bond.FunctionType)
default:
panic("unrecognized function type")
}
if result.IsAnyNegative() {
// assumes that the curve is above the x-axis and does not intersect it
panic(fmt.Sprintf("negative price result for bond %s", bond.Token))
}
return result, nil
}
func (bond Bond) GetCurrentPricesPT(reserveBalances sdk.Coins) (sdk.DecCoins, error) {
// Note: PT stands for "per token"
switch bond.FunctionType {
case PowerFunction:
fallthrough
case SigmoidFunction:
fallthrough
case AugmentedFunction:
return bond.GetPricesAtSupply(bond.CurrentSupply.Amount)
case SwapperFunction:
return bond.GetPricesToMint(sdk.OneInt(), reserveBalances)
default:
panic("unrecognized function type")
}
}
func (bond Bond) ReserveAtSupply(supply sdk.Int) (result sdk.Dec) {
if supply.IsNegative() {
panic(fmt.Sprintf("negative supply for bond %s", bond.Token))
}
args := bond.FunctionParameters.AsMap()
x := supply.ToDec()
switch bond.FunctionType {
case PowerFunction:
m := args["m"]
n, n64 := args["n"], args["n"].TruncateInt64() // enforced by powerParameterRestrictions
c := args["c"]
temp1 := x.Power(uint64(n64 + 1))
temp2 := temp1.Mul(m).Quo(n.Add(sdk.OneDec()))
temp3 := x.Mul(c)
result = temp2.Add(temp3)
case SigmoidFunction:
a := args["a"]
b := args["b"]
c := args["c"]
temp1 := x.Sub(b)
temp2 := temp1.Mul(temp1).Add(c)
temp3, err := temp2.ApproxSqrt()
if err != nil {
panic(err) // TODO: consider error handling
}
temp5 := a.Mul(temp3.Add(x))
approx, err := (b.Mul(b).Add(c)).ApproxSqrt()
if err != nil {
panic(err) // TODO: consider error handling
}
constant := a.Mul(approx)
result = temp5.Sub(constant)
case AugmentedFunction:
kappa := args["kappa"].TruncateInt64()
V0 := args["V0"]
result = Reserve(x, kappa, V0)
case SwapperFunction:
panic("invalid function for function type")
default:
panic("unrecognized function type")
}
if result.IsNegative() {
// For vanilla bonding curves, we assume that the curve does not
// intersect the x-axis and is greater than zero throughout
panic(fmt.Sprintf("negative reserve result for bond %s", bond.Token))
}
return result
}
func (bond Bond) GetReserveDeltaForLiquidityDelta(mintOrBurn sdk.Int, reserveBalances sdk.Coins) sdk.DecCoins {
if mintOrBurn.IsNegative() {
panic(fmt.Sprintf("negative liquidity delta for bond %s", bond.Token))
} else if reserveBalances.IsAnyNegative() {
panic(fmt.Sprintf("negative reserve balance for bond %s", bond.Token))
}
switch bond.FunctionType {
case PowerFunction:
fallthrough
case SigmoidFunction:
fallthrough
case AugmentedFunction:
panic("invalid function for function type")
case SwapperFunction:
resToken1 := bond.ReserveTokens[0]
resToken2 := bond.ReserveTokens[1]
resBalance1 := reserveBalances.AmountOf(resToken1).ToDec()
resBalance2 := reserveBalances.AmountOf(resToken2).ToDec()
// Using Uniswap formulae: x' = (1+-α)x = x +- Δx, where α = Δx/x
// Where x is any of the two reserve balances or the current supply
// and x' is any of the updated reserve balances or the updated supply
// By making Δx subject of the formula: Δx = αx
alpha := mintOrBurn.ToDec().Quo(bond.CurrentSupply.Amount.ToDec())
result := sdk.DecCoins{
sdk.NewDecCoinFromDec(resToken1, alpha.Mul(resBalance1)),
sdk.NewDecCoinFromDec(resToken2, alpha.Mul(resBalance2)),
}
if result.IsAnyNegative() {
panic(fmt.Sprintf("negative reserve delta result for bond %s", bond.Token))
}
return result
default:
panic("unrecognized function type")
}
}
func (bond Bond) GetPricesToMint(mint sdk.Int, reserveBalances sdk.Coins) (sdk.DecCoins, error) {
if mint.IsNegative() {
panic(fmt.Sprintf("negative mint amount for bond %s", bond.Token))
} else if reserveBalances.IsAnyNegative() {
panic(fmt.Sprintf("negative reserve balance for bond %s", bond.Token))
}
// If hatch phase for augmented function, use fixed p0 price
if bond.FunctionType == AugmentedFunction && bond.State == HatchState {
args := bond.FunctionParameters.AsMap()
if bond.State == HatchState {
price := args["p0"].Mul(mint.ToDec())
return bond.GetNewReserveDecCoins(price), nil
}
}
switch bond.FunctionType {
case PowerFunction:
fallthrough
case SigmoidFunction:
fallthrough
case AugmentedFunction:
var priceToMint sdk.Dec
result := bond.ReserveAtSupply(bond.CurrentSupply.Amount.Add(mint))
if reserveBalances.Empty() {
priceToMint = result
} else {
// Reserve balances should all be equal given that we are always
// applying the same additions/subtractions to all reserve balances.
// Thus we can pick the first reserve balance as the global balance.
commonReserveBalance := reserveBalances[0].Amount.ToDec()
priceToMint = result.Sub(commonReserveBalance)
}
if priceToMint.IsNegative() {
// Negative priceToMint means that the previous buyer overpaid
// to the point that the price for this buyer is covered. However,
// we still charge this buyer at least one token.
priceToMint = sdk.OneDec()
}
return bond.GetNewReserveDecCoins(priceToMint), nil
case SwapperFunction:
if bond.CurrentSupply.Amount.IsZero() {
return nil, sdkerrors.Wrap(ErrFunctionRequiresNonZeroCurrentSupply, bond.CurrentSupply.Amount.String())
}
return bond.GetReserveDeltaForLiquidityDelta(mint, reserveBalances), nil
default:
panic("unrecognized function type")
}
// Note: fees have to be added to these prices to get actual prices
}
func (bond Bond) GetReturnsForBurn(burn sdk.Int, reserveBalances sdk.Coins) sdk.DecCoins {
if burn.IsNegative() {
panic(fmt.Sprintf("negative burn amount for bond %s", bond.Token))
} else if reserveBalances.IsAnyNegative() {
panic(fmt.Sprintf("negative reserve balance for bond %s", bond.Token))
}
switch bond.FunctionType {
case PowerFunction:
fallthrough
case SigmoidFunction:
fallthrough
case AugmentedFunction:
result := bond.ReserveAtSupply(bond.CurrentSupply.Amount.Sub(burn))
var reserveBalance sdk.Dec
if reserveBalances.Empty() {
reserveBalance = sdk.ZeroDec()
} else {
// Reserve balances should all be equal given that we are always
// applying the same additions/subtractions to all reserve balances.
// Thus we can pick the first reserve balance as the global balance.
reserveBalance = reserveBalances[0].Amount.ToDec()
}
if result.GT(reserveBalance) {
panic("not enough reserve available for burn")
} else {
returnForBurn := reserveBalance.Sub(result)
return bond.GetNewReserveDecCoins(returnForBurn)
// TODO: investigate possibility of negative returnForBurn
}
case SwapperFunction:
return bond.GetReserveDeltaForLiquidityDelta(burn, reserveBalances)
default:
panic("unrecognized function type")
}
// Note: fees have to be deducted from these returns to get actual returns
}
func (bond Bond) GetReturnsForSwap(from sdk.Coin, toToken string, reserveBalances sdk.Coins) (returns sdk.Coins, txFee sdk.Coin, err error) {
if from.IsNegative() {
panic(fmt.Sprintf("negative from amount for bond %s", bond.Token))
} else if reserveBalances.IsAnyNegative() {
panic(fmt.Sprintf("negative reserve balance for bond %s", bond.Token))
}
switch bond.FunctionType {
case PowerFunction:
fallthrough
case SigmoidFunction:
fallthrough
case AugmentedFunction:
return nil, sdk.Coin{}, sdkerrors.Wrap(ErrFunctionNotAvailableForFunctionType, bond.FunctionType)
case SwapperFunction:
// Check that from and to are reserve tokens
if from.Denom != bond.ReserveTokens[0] && from.Denom != bond.ReserveTokens[1] {
return nil, sdk.Coin{}, sdkerrors.Wrap(ErrTokenIsNotAValidReserveToken, from.Denom)
} else if toToken != bond.ReserveTokens[0] && toToken != bond.ReserveTokens[1] {
return nil, sdk.Coin{}, sdkerrors.Wrap(ErrTokenIsNotAValidReserveToken, toToken)
}
inAmt := from.Amount
inRes := reserveBalances.AmountOf(from.Denom)
outRes := reserveBalances.AmountOf(toToken)
// Calculate fee to get the adjusted input amount
txFee = bond.GetTxFee(sdk.NewDecCoinFromCoin(from))
inAmt = inAmt.Sub(txFee.Amount) // adjusted input
// Check that at least 1 token is going in
if inAmt.IsZero() {
return nil, sdk.Coin{}, sdkerrors.Wrapf(ErrSwapAmountTooSmallToGiveAnyReturn, "%s - %s", from.Denom, toToken)
}
// Calculate output amount using Uniswap formula: Δy = (Δx*y)/(x+Δx)
outAmt := inAmt.Mul(outRes).Quo(inRes.Add(inAmt))
// Check that not giving out all of the available outRes or nothing at all
if outAmt.Equal(outRes) {
return nil, sdk.Coin{}, sdkerrors.Wrapf(ErrSwapAmountCausesReserveDepletion, "%s - %s", from.Denom, toToken)
} else if outAmt.IsZero() {
return nil, sdk.Coin{}, sdkerrors.Wrapf(ErrSwapAmountTooSmallToGiveAnyReturn, "%s - %s", from.Denom, toToken)
} else if outAmt.IsNegative() {
panic(fmt.Sprintf("negative return for swap result for bond %s", bond.Token))
}
return sdk.Coins{sdk.NewCoin(toToken, outAmt)}, txFee, nil
default:
panic("unrecognized function type")
}
}
func (bond Bond) GetFee(reserveAmount sdk.DecCoin, percentage sdk.Dec) sdk.Coin {
feeAmount := percentage.QuoInt64(100).Mul(reserveAmount.Amount)
return RoundFee(sdk.NewDecCoinFromDec(reserveAmount.Denom, feeAmount))
}
func (bond Bond) GetTxFee(reserveAmount sdk.DecCoin) sdk.Coin {
return bond.GetFee(reserveAmount, bond.TxFeePercentage)
}
func (bond Bond) GetExitFee(reserveAmount sdk.DecCoin) sdk.Coin {
return bond.GetFee(reserveAmount, bond.ExitFeePercentage)
}
func (bond Bond) GetFees(reserveAmounts sdk.DecCoins, percentage sdk.Dec) (fees sdk.Coins) {
for _, r := range reserveAmounts {
fees = fees.Add(bond.GetFee(r, percentage))
}
return fees
}
//noinspection GoNilness
func (bond Bond) GetTxFees(reserveAmounts sdk.DecCoins) (fees sdk.Coins) {
return bond.GetFees(reserveAmounts, bond.TxFeePercentage)
}
//noinspection GoNilness
func (bond Bond) GetExitFees(reserveAmounts sdk.DecCoins) (fees sdk.Coins) {
return bond.GetFees(reserveAmounts, bond.ExitFeePercentage)
}
func (bond Bond) SignersEqualTo(signers []sdk.AccAddress) bool {
if len(bond.Signers) != len(signers) {
return false
}
// Note: this also enforces ORDER of signatures to be the same
for i := range signers {
if !bond.Signers[i].Equals(signers[i]) {
return false
}
}
return true
}
func (bond Bond) ReserveDenomsEqualTo(coins sdk.Coins) bool {
if len(bond.ReserveTokens) != len(coins) {
return false
}
for _, d := range bond.ReserveTokens {
if coins.AmountOf(d).IsZero() {
return false
}
}
return true
}
func (bond Bond) AnyOrderQuantityLimitsExceeded(amounts sdk.Coins) bool {
return amounts.IsAnyGT(bond.OrderQuantityLimits)
}
func (bond Bond) ReservesViolateSanityRate(newReserves sdk.Coins) bool {
if bond.SanityRate.IsZero() {
return false
}
// Get new rate from new balances
resToken1 := bond.ReserveTokens[0]
resToken2 := bond.ReserveTokens[1]
resBalance1 := newReserves.AmountOf(resToken1).ToDec()
resBalance2 := newReserves.AmountOf(resToken2).ToDec()
exchangeRate := resBalance1.Quo(resBalance2)
// Get max and min acceptable rates
sanityMarginDecimal := bond.SanityMarginPercentage.Quo(sdk.NewDec(100))
upperPercentage := sdk.OneDec().Add(sanityMarginDecimal)
lowerPercentage := sdk.OneDec().Sub(sanityMarginDecimal)
maxRate := bond.SanityRate.Mul(upperPercentage)
minRate := bond.SanityRate.Mul(lowerPercentage)
// If min rate is negative, change to zero
if minRate.IsNegative() {
minRate = sdk.ZeroDec()
}
return exchangeRate.LT(minRate) || exchangeRate.GT(maxRate)
}