diff --git a/app/oracle-server/src/main/scala/org/bitcoins/oracle/server/OracleRoutes.scala b/app/oracle-server/src/main/scala/org/bitcoins/oracle/server/OracleRoutes.scala index 73ada50499..93d3fd8182 100644 --- a/app/oracle-server/src/main/scala/org/bitcoins/oracle/server/OracleRoutes.scala +++ b/app/oracle-server/src/main/scala/org/bitcoins/oracle/server/OracleRoutes.scala @@ -142,11 +142,13 @@ case class OracleRoutes(oracle: DLCOracle)(implicit } outcomes.map(num => Num(num.toDouble)) case decomp: DigitDecompositionEventDescriptorV0TLV => - val sign = if (decomp.isSigned) { - Vector(Str("+"), Str("-")) - } else { - Vector.empty + val sign = decomp match { + case _: UnsignedDigitDecompositionEventDescriptor => + Vector.empty + case _: SignedDigitDecompositionEventDescriptor => + Vector(Str("+"), Str("-")) } + val digits = 0.until(decomp.numDigits.toInt).map { _ => 0 .until(decomp.base.toInt) diff --git a/core/src/main/scala/org/bitcoins/core/protocol/dlc/DLCMessage.scala b/core/src/main/scala/org/bitcoins/core/protocol/dlc/DLCMessage.scala index b6ad1b2242..45da473f87 100644 --- a/core/src/main/scala/org/bitcoins/core/protocol/dlc/DLCMessage.scala +++ b/core/src/main/scala/org/bitcoins/core/protocol/dlc/DLCMessage.scala @@ -293,12 +293,7 @@ object DLCMessage { } override lazy val toTLV: ContractInfoV1TLV = { - val tlvPoints = outcomeValueFunc.points.map { point => - TLVPoint(point.outcome, - point.roundedPayout, - point.extraPrecision, - point.isEndpoint) - } + val tlvPoints = outcomeValueFunc.points.map(_.toTlvPoint) ContractInfoV1TLV(base, numDigits, totalCollateral, tlvPoints) } @@ -312,6 +307,7 @@ object DLCMessage { val points = tlv.points.map { point => val payoutWithPrecision = point.value.toLong + (BigDecimal(point.extraPrecision) / (1 << 16)) + OutcomePayoutPoint(point.outcome, payoutWithPrecision, point.isEndpoint) } diff --git a/core/src/main/scala/org/bitcoins/core/protocol/dlc/DLCPayoutCurve.scala b/core/src/main/scala/org/bitcoins/core/protocol/dlc/DLCPayoutCurve.scala index 42bc65048e..0a565e8de0 100644 --- a/core/src/main/scala/org/bitcoins/core/protocol/dlc/DLCPayoutCurve.scala +++ b/core/src/main/scala/org/bitcoins/core/protocol/dlc/DLCPayoutCurve.scala @@ -1,6 +1,7 @@ package org.bitcoins.core.protocol.dlc import org.bitcoins.core.currency.Satoshis +import org.bitcoins.core.protocol.tlv.TLVPoint import org.bitcoins.core.util.{Indexed, NumberUtil} import scala.math.BigDecimal.RoundingMode @@ -15,15 +16,26 @@ case class DLCPayoutCurve(points: Vector[OutcomePayoutPoint]) { /** These points (and their indices in this.points) represent the endpoints * between which interpolation happens. * In other words these endpoints define the pieces of the piecewise function. + * + * It's important to note that the index returned here is relative to the _entire_ + * set of points, not the index relative to the set of endpoints. */ - lazy val endpoints: Vector[Indexed[OutcomePayoutPoint]] = - Indexed(points).filter(_.element.isEndpoint) + lazy val endpoints: Vector[Indexed[OutcomePayoutEndpoint]] = { + val endpoints = points.zipWithIndex.collect { + case (o: OutcomePayoutEndpoint, idx) => (o, idx) + } + Indexed.fromGivenIndex(endpoints) + } /** This Vector contains the function pieces between the endpoints */ lazy val functionComponents: Vector[DLCPayoutCurveComponent] = { - endpoints.init.zip(endpoints.tail).map { // All pairs of adjacent endpoints + val zipped: Vector[ + (Indexed[OutcomePayoutEndpoint], Indexed[OutcomePayoutEndpoint])] = + endpoints.init.zip(endpoints.tail) + zipped.map { // All pairs of adjacent endpoints case (Indexed(_, index), Indexed(_, nextIndex)) => - DLCPayoutCurveComponent(points.slice(index, nextIndex + 1)) + val slice = points.slice(index, nextIndex + 1) + DLCPayoutCurveComponent(slice) } } @@ -70,7 +82,13 @@ case class DLCPayoutCurve(points: Vector[OutcomePayoutPoint]) { sealed trait OutcomePayoutPoint { def outcome: Long def payout: BigDecimal - def isEndpoint: Boolean + + def isEndPoint: Boolean = { + this match { + case _: OutcomePayoutEndpoint => true + case _: OutcomePayoutMidpoint => false + } + } def roundedPayout: Satoshis = { Satoshis(payout.setScale(0, RoundingMode.FLOOR).toLongExact) @@ -89,6 +107,22 @@ sealed trait OutcomePayoutPoint { case OutcomePayoutMidpoint(_, _) => OutcomePayoutMidpoint(outcome, payout) } } + + /** Converts our internal representation to a TLV that can be sent over the wire */ + def toTlvPoint: TLVPoint = { + this match { + case _: OutcomePayoutEndpoint => + TLVPoint(outcome = outcome, + value = roundedPayout, + extraPrecision = extraPrecision, + isEndpoint = true) + case _: OutcomePayoutMidpoint => + TLVPoint(outcome = outcome, + value = roundedPayout, + extraPrecision = extraPrecision, + isEndpoint = false) + } + } } object OutcomePayoutPoint { @@ -114,7 +148,6 @@ object OutcomePayoutPoint { case class OutcomePayoutEndpoint(outcome: Long, payout: BigDecimal) extends OutcomePayoutPoint { - override val isEndpoint: Boolean = true def toMidpoint: OutcomePayoutMidpoint = OutcomePayoutMidpoint(outcome, payout) } @@ -128,7 +161,6 @@ object OutcomePayoutEndpoint { case class OutcomePayoutMidpoint(outcome: Long, payout: BigDecimal) extends OutcomePayoutPoint { - override val isEndpoint: Boolean = false def toEndpoint: OutcomePayoutEndpoint = OutcomePayoutEndpoint(outcome, payout) } @@ -179,9 +211,9 @@ sealed trait DLCPayoutCurveComponent { object DLCPayoutCurveComponent { def apply(points: Vector[OutcomePayoutPoint]): DLCPayoutCurveComponent = { - require(points.head.isEndpoint && points.last.isEndpoint, + require(points.head.isEndPoint && points.last.isEndPoint, s"First and last points must be endpoints, $points") - require(points.tail.init.forall(!_.isEndpoint), + require(points.tail.init.forall(!_.isEndPoint), s"Endpoint detected in middle, $points") points match { @@ -320,9 +352,10 @@ case class OutcomePayoutCubic( /** A polynomial interpolating points and defining a piece of a larger payout curve */ case class OutcomePayoutPolynomial(points: Vector[OutcomePayoutPoint]) extends DLCPayoutCurveComponent { - require(points.head.isEndpoint && points.last.isEndpoint, + require(points.head.isInstanceOf[OutcomePayoutEndpoint] && points.last + .isInstanceOf[OutcomePayoutEndpoint], s"First and last points must be endpoints, $points") - require(points.tail.init.forall(!_.isEndpoint), + require(points.tail.init.forall(!_.isInstanceOf[OutcomePayoutEndpoint]), s"Endpoint detected in middle, $points") override lazy val leftEndpoint: OutcomePayoutEndpoint = diff --git a/core/src/main/scala/org/bitcoins/core/protocol/tlv/TLV.scala b/core/src/main/scala/org/bitcoins/core/protocol/tlv/TLV.scala index 950d530fa5..c2de16d659 100644 --- a/core/src/main/scala/org/bitcoins/core/protocol/tlv/TLV.scala +++ b/core/src/main/scala/org/bitcoins/core/protocol/tlv/TLV.scala @@ -603,9 +603,6 @@ sealed trait DigitDecompositionEventDescriptorV0TLV require(numDigits > UInt16.zero, s"Number of digits must be positive, got $numDigits") - /** Whether the outcome can be negative */ - def isSigned: Boolean - /** The number of digits that the oracle will sign */ def numDigits: UInt16 @@ -613,22 +610,32 @@ sealed trait DigitDecompositionEventDescriptorV0TLV private lazy val maxDigit: NormalizedString = (base.toInt - 1).toString - override lazy val max: Vector[NormalizedString] = if (isSigned) { - NormalizedString("+") +: Vector.fill(numDigits.toInt)(maxDigit) - } else { - Vector.fill(numDigits.toInt)(maxDigit) + override lazy val max: Vector[NormalizedString] = { + this match { + case _: SignedDigitDecompositionEventDescriptor => + NormalizedString("+") +: Vector.fill(numDigits.toInt)(maxDigit) + case _: UnsignedDigitDecompositionEventDescriptor => + Vector.fill(numDigits.toInt)(maxDigit) + + } } - override lazy val minNum: BigInt = if (isSigned) { - -maxNum - } else { - 0 + override lazy val minNum: BigInt = { + this match { + case _: SignedDigitDecompositionEventDescriptor => + -maxNum + case _: UnsignedDigitDecompositionEventDescriptor => + 0 + } } - override lazy val min: Vector[NormalizedString] = if (isSigned) { - NormalizedString("-") +: Vector.fill(numDigits.toInt)(maxDigit) - } else { - Vector.fill(numDigits.toInt)("0") + override lazy val min: Vector[NormalizedString] = { + this match { + case _: SignedDigitDecompositionEventDescriptor => + NormalizedString("-") +: Vector.fill(numDigits.toInt)(maxDigit) + case _: UnsignedDigitDecompositionEventDescriptor => + Vector.fill(numDigits.toInt)("0") + } } override lazy val step: UInt16 = UInt16.one @@ -637,16 +644,26 @@ sealed trait DigitDecompositionEventDescriptorV0TLV DigitDecompositionEventDescriptorV0TLV.tpe override lazy val value: ByteVector = { - base.bytes ++ - boolBytes(isSigned) ++ - strBytes(unit) ++ + val start = base.bytes + val signByte = this match { + case _: UnsignedDigitDecompositionEventDescriptor => + boolBytes(false) + case _: SignedDigitDecompositionEventDescriptor => + boolBytes(true) + } + val end = strBytes(unit) ++ precision.bytes ++ numDigits.bytes + start ++ signByte ++ end } override def noncesNeeded: Int = { - if (isSigned) numDigits.toInt + 1 - else numDigits.toInt + this match { + case _: SignedDigitDecompositionEventDescriptor => + numDigits.toInt + 1 + case _: UnsignedDigitDecompositionEventDescriptor => + numDigits.toInt + } } } @@ -656,9 +673,7 @@ case class SignedDigitDecompositionEventDescriptor( numDigits: UInt16, unit: NormalizedString, precision: Int32) - extends DigitDecompositionEventDescriptorV0TLV { - override val isSigned: Boolean = true -} + extends DigitDecompositionEventDescriptorV0TLV /** Represents a large range event that is unsigned */ case class UnsignedDigitDecompositionEventDescriptor( @@ -666,9 +681,7 @@ case class UnsignedDigitDecompositionEventDescriptor( numDigits: UInt16, unit: NormalizedString, precision: Int32) - extends DigitDecompositionEventDescriptorV0TLV { - override val isSigned: Boolean = false -} + extends DigitDecompositionEventDescriptorV0TLV object DigitDecompositionEventDescriptorV0TLV extends TLVFactory[DigitDecompositionEventDescriptorV0TLV] { @@ -895,7 +908,11 @@ object TLVPoint extends Factory[TLVPoint] { val outcome = BigSizeUInt(bytes.tail) val value = UInt64(bytes.drop(1 + outcome.byteSize).take(8)) val extraPrecision = UInt16(bytes.drop(9 + outcome.byteSize).take(2)).toInt - TLVPoint(outcome.toLong, Satoshis(value.toLong), extraPrecision, isEndpoint) + + TLVPoint(outcome = outcome.toLong, + value = Satoshis(value.toLong), + extraPrecision = extraPrecision, + isEndpoint = isEndpoint) } } diff --git a/core/src/main/scala/org/bitcoins/core/util/Indexed.scala b/core/src/main/scala/org/bitcoins/core/util/Indexed.scala index d16025a73f..9c9606cc7e 100644 --- a/core/src/main/scala/org/bitcoins/core/util/Indexed.scala +++ b/core/src/main/scala/org/bitcoins/core/util/Indexed.scala @@ -1,10 +1,20 @@ package org.bitcoins.core.util -case class Indexed[T](element: T, index: Int) +case class Indexed[+T](element: T, index: Int) object Indexed { def apply[T](vec: Vector[T]): Vector[Indexed[T]] = { vec.zipWithIndex.map { case (elem, index) => Indexed(elem, index) } } + + /** Takes in a given vector of T's with their corresponding index + * and returns a Vector[Indexed[T]]. + * + * This is useful in situations where you want to preserve the initial + * index in a set of elements, but have performed subsequent collection operations (like .filter, .filterNot, .collect etc) + */ + def fromGivenIndex[T](vec: Vector[(T, Int)]): Vector[Indexed[T]] = { + vec.map { case (t, idx) => Indexed(t, idx) } + } } diff --git a/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/DLCOracle.scala b/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/DLCOracle.scala index 235c110398..7f78439f32 100644 --- a/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/DLCOracle.scala +++ b/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/DLCOracle.scala @@ -300,12 +300,14 @@ case class DLCOracle(private val extPrivateKey: ExtPrivateKeyHardened)(implicit oracleEventTLV: OracleEventTLV, num: Long): Future[OracleEvent] = { - val eventDescriptorTLV = oracleEventTLV.eventDescriptor match { - case _: EnumEventDescriptorV0TLV | _: RangeEventDescriptorV0TLV => - throw new IllegalArgumentException( - "Must have a DigitDecomposition event descriptor use signEvent instead") - case decomp: DigitDecompositionEventDescriptorV0TLV => - decomp + val eventDescriptorTLV: DigitDecompositionEventDescriptorV0TLV = { + oracleEventTLV.eventDescriptor match { + case _: EnumEventDescriptorV0TLV | _: RangeEventDescriptorV0TLV => + throw new IllegalArgumentException( + "Must have a DigitDecomposition event descriptor use signEvent instead") + case decomp: DigitDecompositionEventDescriptorV0TLV => + decomp + } } // Make this a vec so it is easier to add on @@ -338,9 +340,12 @@ case class DLCOracle(private val extPrivateKey: ExtPrivateKeyHardened)(implicit eventDescriptorTLV.base.toInt, eventDescriptorTLV.numDigits.toInt) - val nonces = - if (eventDescriptorTLV.isSigned) oracleEventTLV.nonces.tail - else oracleEventTLV.nonces + val nonces = eventDescriptorTLV match { + case _: UnsignedDigitDecompositionEventDescriptor => + oracleEventTLV.nonces + case _: SignedDigitDecompositionEventDescriptor => + oracleEventTLV.nonces.tail + } val digitSigFs = nonces.zipWithIndex.map { case (nonce, index) => diff --git a/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/util/EventDbUtil.scala b/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/util/EventDbUtil.scala index d09f10a26f..f9b92e7883 100644 --- a/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/util/EventDbUtil.scala +++ b/dlc-oracle/src/main/scala/org/bitcoins/dlc/oracle/util/EventDbUtil.scala @@ -60,7 +60,12 @@ trait EventDbUtil { Vector.empty } - val digitNonces = if (decomp.isSigned) nonces.tail else nonces + val digitNonces = decomp match { + case _: UnsignedDigitDecompositionEventDescriptor => + nonces + case _: SignedDigitDecompositionEventDescriptor => + nonces.tail + } val digitDbs = digitNonces.flatMap { nonce => 0.until(decomp.base.toInt).map { num =>