All files / lib/dlc HyperbolaPayoutCurve.ts

73.91% Statements 34/46
48% Branches 12/25
85.71% Functions 6/7
73.33% Lines 33/45

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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 1891x             1x     1x     1x   46x 46x 46x 46x 46x 46x 46x       14914x 14914x   14914x                                                             14914x       9914x       9914x 9914x   9914x 4x     9910x                     9910x                 15x     15x                   15x       6x                             10x 10x 10x 10x 10x     10x       10x                                                                                                      
import {
  HyperbolaPayoutCurvePiece,
  MessageType,
  PayoutCurvePieceType,
  PayoutFunctionV0,
  RoundingIntervals,
} from '@node-dlc/messaging';
import BigNumber from 'bignumber.js';
 
import { CETPayout } from '..';
import { splitIntoRanges } from './CETCalculator';
import PayoutCurve from './PayoutCurve';
 
export class HyperbolaPayoutCurve implements PayoutCurve {
  constructor(
    private a: BigNumber,
    private b: BigNumber,
    private c: BigNumber,
    private d: BigNumber,
    private translateOutcome: BigNumber,
    private translatePayout: BigNumber,
    private positive: boolean = true, // TODO: support negative pieces
  ) {}
 
  getPayout(_x: bigint): BigNumber {
    const { a, b, c, d, translateOutcome, translatePayout } = this;
    const x = new BigNumber(Number(_x));
 
    const payout = c
      .times(
        x
          .minus(translateOutcome)
          .plus(
            x
              .minus(translateOutcome)
              .exponentiatedBy(2)
              .minus(a.times(b).times(4))
              .squareRoot(),
          )
          .div(a.times(2)),
      )
      .plus(
        a
          .times(d)
          .times(2)
          .div(
            x
              .minus(translateOutcome)
              .plus(
                x
                  .minus(translateOutcome)
                  .exponentiatedBy(2)
                  .minus(a.times(b).times(4))
                  .squareRoot(),
              ),
          ),
      )
      .plus(translatePayout);
 
    return payout;
  }
 
  getOutcomeForPayout(payout: BigNumber): bigint {
    const { a, b, c, d, translateOutcome, translatePayout } = this;
 
    // Inverse function
    // y=(-ad^{2}-bf_{2}^{2}+2bf_{2}x-bx^{2}+df_{1}f_{2}-df_{1}x)/(d(f_{2}-x))
    Eif (c.eq(0)) {
      const denominator = d.times(translatePayout.minus(payout));
 
      if (denominator.eq(0)) {
        return BigInt(-1);
      }
 
      const outcome = a
        .negated()
        .times(d.exponentiatedBy(2))
        .minus(b.times(translatePayout.exponentiatedBy(2)))
        .plus(b.times(translatePayout).times(payout).times(2))
        .minus(b.times(payout.exponentiatedBy(2)))
        .plus(d.times(translateOutcome).times(translatePayout))
        .minus(d.times(translateOutcome).times(payout))
        .dividedBy(denominator)
        .integerValue();
 
      Eif (outcome.isFinite()) return BigInt(outcome.toString());
      return BigInt(-1);
    } else {
      // y=\left((\sqrt{((adf_{2}-adx+bcf_{2}-bcx-2c\cdot d\cdot f_{1})^{2}-4cd(a^{2}d^{2}-2abcd+abf_{2}^{2}-2abf_{2}x+abx^{2}-adf_{1}f_{2}+adf_{1}x+b^{2}c^{2}-bcf_{1}f_{2}+bcf_{1}x+c\cdot d\cdot f_{1}^{2}))}-adf_{2}+adx-bcf_{2}+bcx+2c\cdot d\cdot f_{1})\right)/(2cd)
      throw new Error('Not supported');
    }
  }
 
  toPayoutCurvePiece(): HyperbolaPayoutCurvePiece {
    const { a, b, c, d, translateOutcome, translatePayout, positive } = this;
 
    // Use the constructor with string values to avoid F64 dependency issues
    const piece = new HyperbolaPayoutCurvePiece(
      positive,
      translateOutcome.toString(),
      translatePayout.toString(),
      a.toString(),
      b.toString(),
      c.toString(),
      d.toString(),
    );
 
    return piece;
  }
 
  equals(curve: HyperbolaPayoutCurve): boolean {
    return (
      this.a.eq(curve.a) &&
      this.b.eq(curve.b) &&
      this.c.eq(curve.c) &&
      this.d.eq(curve.d) &&
      this.translateOutcome.eq(curve.translateOutcome) &&
      this.translatePayout.eq(curve.translatePayout) &&
      this.positive === curve.positive
    );
  }
 
  static fromPayoutCurvePiece(
    piece: HyperbolaPayoutCurvePiece,
  ): HyperbolaPayoutCurve {
    // Convert F64 values to BigNumber using toDecimal().toString() to preserve precision
    const a = new BigNumber(piece.a.toDecimal().toString());
    const b = new BigNumber(piece.b.toDecimal().toString());
    const c = new BigNumber(piece.c.toDecimal().toString());
    const d = new BigNumber(piece.d.toDecimal().toString());
    const translateOutcome = new BigNumber(
      piece.translateOutcome.toDecimal().toString(),
    );
    const translatePayout = new BigNumber(
      piece.translatePayout.toDecimal().toString(),
    );
 
    return new HyperbolaPayoutCurve(
      a,
      b,
      c,
      d,
      translateOutcome,
      translatePayout,
      piece.usePositivePiece,
    );
  }
 
  static computePayouts(
    payoutFunction: PayoutFunctionV0,
    totalCollateral: bigint,
    roundingIntervals: RoundingIntervals,
  ): CETPayout[] {
    if (payoutFunction.payoutFunctionPieces.length !== 1)
      throw new Error('Must have at least one piece');
    const {
      endPoint,
      payoutCurvePiece,
    } = payoutFunction.payoutFunctionPieces[0];
 
    if (
      payoutCurvePiece.payoutCurvePieceType !==
        PayoutCurvePieceType.Hyperbola &&
      payoutCurvePiece.type !== MessageType.HyperbolaPayoutCurvePiece &&
      payoutCurvePiece.type !== MessageType.OldHyperbolaPayoutCurvePiece
    )
      throw new Error('Payout curve piece must be a hyperbola');
 
    const _payoutCurvePiece = payoutCurvePiece as HyperbolaPayoutCurvePiece;
    const curve = this.fromPayoutCurvePiece(_payoutCurvePiece);
 
    // For the new PayoutFunction structure, get the starting point from the hyperbola piece's leftEndPoint
    // This matches the rust-dlc implementation where get_first_outcome() returns left_end_point.event_outcome
    const initialEventOutcome =
      _payoutCurvePiece.leftEndPoint?.eventOutcome || BigInt(0);
    const initialOutcomePayout =
      _payoutCurvePiece.leftEndPoint?.outcomePayout || BigInt(0);
    return splitIntoRanges(
      initialEventOutcome,
      endPoint.eventOutcome,
      initialOutcomePayout,
      endPoint.outcomePayout,
      totalCollateral,
      curve,
      roundingIntervals.intervals,
    );
  }
}