mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-01-18 21:34:39 +01:00
Implemented general payout curves (#3854)
* Implemented general payout curves including hyperbola curve piece support WIP Get GUI compiling * WIP * Get backward compatible serialization working, everything is equivalent except for in memory Scala data structures * Get numeric contracts backward compatible * Get rest of codebase compiling * Add test case for old contract info * Add sanity test vector for old enum contract descriptors * Fix docs * Remove comment * Scalafmt * Add DLCSerializationVersion, replace isOldSerialization * Remove ValueIterator.takeNoSkip() * Fix docs Co-authored-by: nkohen <nadavk25@gmail.com>
This commit is contained in:
parent
a7af8cac4c
commit
8c5288d758
@ -1,13 +1,14 @@
|
||||
package org.bitcoins.commons.jsonmodels.cli
|
||||
|
||||
import org.bitcoins.commons.serializers.Picklers
|
||||
import org.bitcoins.core.protocol.dlc.models.DLCPayoutCurve
|
||||
import org.bitcoins.core.protocol.tlv.{
|
||||
ContractDescriptorTLV,
|
||||
ContractDescriptorV0TLV,
|
||||
ContractDescriptorV1TLV,
|
||||
DLCSerializationVersion,
|
||||
DigitDecompositionEventDescriptorV0TLV,
|
||||
OracleAnnouncementTLV,
|
||||
PayoutFunctionV0TLV,
|
||||
RoundingIntervalsV0TLV,
|
||||
TLVPoint
|
||||
}
|
||||
@ -26,12 +27,16 @@ object ContractDescriptorParser {
|
||||
//we read the number of digits from the announcement,
|
||||
//take in tlv points for the payout curve
|
||||
//and don't provide access to give a rounding mode as a parameter
|
||||
val payoutPoints = arr.value.toVector.map { pointJs =>
|
||||
val payoutPoints: Vector[TLVPoint] = arr.value.toVector.map { pointJs =>
|
||||
upickle.default
|
||||
.read[TLVPoint](pointJs)(Picklers.tlvPointReader)
|
||||
}
|
||||
|
||||
val payoutCurve = PayoutFunctionV0TLV(payoutPoints)
|
||||
val payoutCurve = DLCPayoutCurve
|
||||
.fromPoints(payoutPoints,
|
||||
serializationVersion =
|
||||
DLCSerializationVersion.Post144Pre163)
|
||||
.toTLV
|
||||
val numDigits = announcementTLV.eventTLV.eventDescriptor
|
||||
.asInstanceOf[DigitDecompositionEventDescriptorV0TLV]
|
||||
.numDigits
|
||||
|
@ -570,8 +570,7 @@ object Picklers {
|
||||
val outcome = map(PicklerKeys.outcomeKey).num.toLong
|
||||
val payout = jsToSatoshis(map(PicklerKeys.payoutKey))
|
||||
val extraPrecision = map(PicklerKeys.extraPrecisionKey).num.toInt
|
||||
val isEndPoint = map(PicklerKeys.isEndpointKey).bool
|
||||
TLVPoint(outcome, payout, extraPrecision, isEndPoint)
|
||||
TLVPoint(outcome, payout, extraPrecision)
|
||||
}
|
||||
}
|
||||
|
||||
@ -580,17 +579,14 @@ object Picklers {
|
||||
Obj(
|
||||
PicklerKeys.outcomeKey -> Num(point.outcome.toDouble),
|
||||
PicklerKeys.payoutKey -> Num(point.value.toLong.toDouble),
|
||||
PicklerKeys.extraPrecisionKey -> Num(point.extraPrecision.toDouble),
|
||||
PicklerKeys.isEndpointKey -> Bool(point.isEndpoint)
|
||||
PicklerKeys.extraPrecisionKey -> Num(point.extraPrecision.toDouble)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
implicit val payoutFunctionV0TLVWriter: Writer[PayoutFunctionV0TLV] =
|
||||
writer[Obj].comap { payoutFunc =>
|
||||
import payoutFunc._
|
||||
|
||||
val pointsJs = points.map { point =>
|
||||
val pointsJs = payoutFunc.endpoints.map { point =>
|
||||
writeJs(point)
|
||||
}
|
||||
|
||||
@ -608,8 +604,11 @@ object Picklers {
|
||||
upickle.default.read[TLVPoint](obj)
|
||||
}.toVector
|
||||
|
||||
PayoutFunctionV0TLV(points)
|
||||
|
||||
DLCPayoutCurve
|
||||
.fromPoints(points,
|
||||
serializationVersion =
|
||||
DLCSerializationVersion.Post144Pre163)
|
||||
.toTLV
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -458,20 +458,24 @@ class CreateDLCOfferDialog
|
||||
throw new RuntimeException(
|
||||
"Got incompatible contract info and announcement")
|
||||
case descriptor: ContractDescriptorV1TLV =>
|
||||
descriptor.payoutFunction.points.init.foreach { point =>
|
||||
addPointRow(
|
||||
xOpt = Some(numberFormatter.format(point.outcome)),
|
||||
yOpt = Some(numberFormatter.format(point.value.toLong)),
|
||||
isEndPoint = point.isEndpoint)
|
||||
}
|
||||
descriptor.payoutFunction.piecewisePolynomialEndpoints.init
|
||||
.foreach { point =>
|
||||
addPointRow(
|
||||
xOpt = Some(numberFormatter.format(point.outcome)),
|
||||
yOpt = Some(
|
||||
numberFormatter.format(point.payout.toLongExact)),
|
||||
isEndPoint = point.isEndpoint)
|
||||
}
|
||||
// handle last specially so user can add more rows
|
||||
val last = descriptor.payoutFunction.points.last
|
||||
val last =
|
||||
descriptor.payoutFunction.piecewisePolynomialEndpoints.last
|
||||
|
||||
addPointRow(xOpt = Some(numberFormatter.format(last.outcome)),
|
||||
yOpt =
|
||||
Some(numberFormatter.format(last.value.toLong)),
|
||||
isEndPoint = last.isEndpoint,
|
||||
row = 9999)
|
||||
addPointRow(
|
||||
xOpt = Some(numberFormatter.format(last.outcome)),
|
||||
yOpt =
|
||||
Some(numberFormatter.format(last.payout.toLongExact)),
|
||||
isEndPoint = last.isEndpoint,
|
||||
row = 9999)
|
||||
nextPointRow -= 1 // do this so the max is the last row
|
||||
|
||||
// add rounding intervals
|
||||
@ -629,8 +633,12 @@ object CreateDLCOfferDialog {
|
||||
if (xTF.text.value.nonEmpty && yTF.text.value.nonEmpty) {
|
||||
val x = numberFormatter.parse(xTF.text.value).longValue()
|
||||
val y = numberFormatter.parse(yTF.text.value).longValue()
|
||||
Some(
|
||||
OutcomePayoutPoint(x, Satoshis(y), checkBox.selected.value))
|
||||
val point = if (checkBox.selected.value) {
|
||||
PiecewisePolynomialMidpoint(x, y)
|
||||
} else {
|
||||
PiecewisePolynomialEndpoint(x, y)
|
||||
}
|
||||
Some(point)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@ -656,7 +664,9 @@ object CreateDLCOfferDialog {
|
||||
require(sorted == outcomesValuePoints,
|
||||
s"Must be sorted by outcome, got $outcomesValuePoints")
|
||||
|
||||
val func = DLCPayoutCurve(outcomesValuePoints)
|
||||
val func = DLCPayoutCurve.polynomialInterpolate(
|
||||
outcomesValuePoints,
|
||||
serializationVersion = DLCSerializationVersion.Post144Pre163)
|
||||
(totalCollateral,
|
||||
NumericContractDescriptor(
|
||||
func,
|
||||
|
@ -5,9 +5,10 @@ import org.bitcoins.core.protocol.dlc.compute.CETCalculator
|
||||
import org.bitcoins.core.protocol.dlc.compute.CETCalculator._
|
||||
import org.bitcoins.core.protocol.dlc.models.{
|
||||
DLCPayoutCurve,
|
||||
OutcomePayoutPoint,
|
||||
PiecewisePolynomialPoint,
|
||||
RoundingIntervals
|
||||
}
|
||||
import org.bitcoins.core.protocol.tlv.DLCSerializationVersion
|
||||
import org.bitcoins.testkitcore.gen.NumberGenerator
|
||||
import org.bitcoins.testkitcore.util.BitcoinSUnitTest
|
||||
import org.scalacheck.Gen
|
||||
@ -23,20 +24,22 @@ class CETCalculatorTest extends BitcoinSUnitTest {
|
||||
private val baseGen: Gen[Int] = Gen.choose(2, 256)
|
||||
|
||||
it should "correctly split into ranges" in {
|
||||
val func = DLCPayoutCurve(
|
||||
val func = DLCPayoutCurve.polynomialInterpolate(
|
||||
Vector(
|
||||
OutcomePayoutPoint(0, Satoshis(-1000), isEndpoint = true),
|
||||
OutcomePayoutPoint(10, Satoshis(-1000), isEndpoint = true),
|
||||
OutcomePayoutPoint(20, Satoshis(0), isEndpoint = false),
|
||||
OutcomePayoutPoint(30, Satoshis(3000), isEndpoint = true),
|
||||
OutcomePayoutPoint(40, Satoshis(4000), isEndpoint = true),
|
||||
OutcomePayoutPoint(50, Satoshis(4000), isEndpoint = true),
|
||||
OutcomePayoutPoint(70, Satoshis(0), isEndpoint = false),
|
||||
OutcomePayoutPoint(80, Satoshis(1000), isEndpoint = true),
|
||||
OutcomePayoutPoint(90, Satoshis(1000), isEndpoint = true),
|
||||
OutcomePayoutPoint(100, Satoshis(11000), isEndpoint = false),
|
||||
OutcomePayoutPoint(110, Satoshis(9000), isEndpoint = true)
|
||||
))
|
||||
PiecewisePolynomialPoint(0, -1000, isEndpoint = true),
|
||||
PiecewisePolynomialPoint(10, -1000, isEndpoint = true),
|
||||
PiecewisePolynomialPoint(20, 0, isEndpoint = false),
|
||||
PiecewisePolynomialPoint(30, 3000, isEndpoint = true),
|
||||
PiecewisePolynomialPoint(40, 4000, isEndpoint = true),
|
||||
PiecewisePolynomialPoint(50, 4000, isEndpoint = true),
|
||||
PiecewisePolynomialPoint(70, 0, isEndpoint = false),
|
||||
PiecewisePolynomialPoint(80, 1000, isEndpoint = true),
|
||||
PiecewisePolynomialPoint(90, 1000, isEndpoint = true),
|
||||
PiecewisePolynomialPoint(100, 11000, isEndpoint = false),
|
||||
PiecewisePolynomialPoint(110, 9000, isEndpoint = true)
|
||||
),
|
||||
serializationVersion = DLCSerializationVersion.Post144Pre163
|
||||
)
|
||||
|
||||
val expected = Vector(
|
||||
ConstantPayoutRange(0, 20), // 0
|
||||
@ -58,17 +61,19 @@ class CETCalculatorTest extends BitcoinSUnitTest {
|
||||
}
|
||||
|
||||
it should "correctly split into ranges when payout is constantly changing" in {
|
||||
val func = DLCPayoutCurve(
|
||||
val func = DLCPayoutCurve.polynomialInterpolate(
|
||||
Vector(
|
||||
OutcomePayoutPoint(0, 1000, isEndpoint = true),
|
||||
OutcomePayoutPoint(1, 0, isEndpoint = true),
|
||||
OutcomePayoutPoint(2, 1000, isEndpoint = true),
|
||||
OutcomePayoutPoint(3, 0, isEndpoint = true),
|
||||
OutcomePayoutPoint(4, 1000, isEndpoint = true),
|
||||
OutcomePayoutPoint(5, 0, isEndpoint = true),
|
||||
OutcomePayoutPoint(6, 1000, isEndpoint = true),
|
||||
OutcomePayoutPoint(7, 0, isEndpoint = true)
|
||||
))
|
||||
PiecewisePolynomialPoint(0, 1000, isEndpoint = true),
|
||||
PiecewisePolynomialPoint(1, 0, isEndpoint = true),
|
||||
PiecewisePolynomialPoint(2, 1000, isEndpoint = true),
|
||||
PiecewisePolynomialPoint(3, 0, isEndpoint = true),
|
||||
PiecewisePolynomialPoint(4, 1000, isEndpoint = true),
|
||||
PiecewisePolynomialPoint(5, 0, isEndpoint = true),
|
||||
PiecewisePolynomialPoint(6, 1000, isEndpoint = true),
|
||||
PiecewisePolynomialPoint(7, 0, isEndpoint = true)
|
||||
),
|
||||
serializationVersion = DLCSerializationVersion.Post144Pre163
|
||||
)
|
||||
|
||||
val expected = Vector(VariablePayoutRange(0, 7))
|
||||
|
||||
@ -257,20 +262,22 @@ class CETCalculatorTest extends BitcoinSUnitTest {
|
||||
}
|
||||
|
||||
it should "correctly compute all needed CETs" in {
|
||||
val func = DLCPayoutCurve(
|
||||
val func = DLCPayoutCurve.polynomialInterpolate(
|
||||
Vector(
|
||||
OutcomePayoutPoint(0, Satoshis(-1000), isEndpoint = true),
|
||||
OutcomePayoutPoint(10, Satoshis(-1000), isEndpoint = true),
|
||||
OutcomePayoutPoint(20, Satoshis(0), isEndpoint = false),
|
||||
OutcomePayoutPoint(30, Satoshis(3000), isEndpoint = true),
|
||||
OutcomePayoutPoint(40, Satoshis(4000), isEndpoint = true),
|
||||
OutcomePayoutPoint(50, Satoshis(4000), isEndpoint = true),
|
||||
OutcomePayoutPoint(70, Satoshis(0), isEndpoint = false),
|
||||
OutcomePayoutPoint(80, Satoshis(1000), isEndpoint = true),
|
||||
OutcomePayoutPoint(90, Satoshis(1000), isEndpoint = true),
|
||||
OutcomePayoutPoint(100, Satoshis(11000), isEndpoint = false),
|
||||
OutcomePayoutPoint(110, Satoshis(9000), isEndpoint = true)
|
||||
))
|
||||
PiecewisePolynomialPoint(0, -1000, isEndpoint = true),
|
||||
PiecewisePolynomialPoint(10, -1000, isEndpoint = true),
|
||||
PiecewisePolynomialPoint(20, 0, isEndpoint = false),
|
||||
PiecewisePolynomialPoint(30, 3000, isEndpoint = true),
|
||||
PiecewisePolynomialPoint(40, 4000, isEndpoint = true),
|
||||
PiecewisePolynomialPoint(50, 4000, isEndpoint = true),
|
||||
PiecewisePolynomialPoint(70, 0, isEndpoint = false),
|
||||
PiecewisePolynomialPoint(80, 1000, isEndpoint = true),
|
||||
PiecewisePolynomialPoint(90, 1000, isEndpoint = true),
|
||||
PiecewisePolynomialPoint(100, 11000, isEndpoint = false),
|
||||
PiecewisePolynomialPoint(110, 9000, isEndpoint = true)
|
||||
),
|
||||
serializationVersion = DLCSerializationVersion.Post144Pre163
|
||||
)
|
||||
|
||||
val firstZeroRange = Vector(
|
||||
Vector(0, 0) -> Satoshis(0),
|
||||
|
@ -2,88 +2,134 @@ package org.bitcoins.core.protocol.dlc
|
||||
|
||||
import org.bitcoins.core.currency._
|
||||
import org.bitcoins.core.protocol.dlc.models._
|
||||
import org.bitcoins.core.protocol.tlv.ContractDescriptorV1TLV
|
||||
import org.bitcoins.core.protocol.tlv.{
|
||||
ContractDescriptorV1TLV,
|
||||
DLCSerializationVersion,
|
||||
EnumOutcome
|
||||
}
|
||||
import org.bitcoins.testkitcore.util.BitcoinSUnitTest
|
||||
|
||||
class ContractDescriptorTest extends BitcoinSUnitTest {
|
||||
|
||||
behavior of "ContractDescriptor"
|
||||
|
||||
it must "construct a basic enum contract descriptor" in {
|
||||
val expectedHex =
|
||||
"fda7102903055452554d50000000000000000005424944454e0000000005f5e100035449450000000002faf080"
|
||||
val vec: Vector[(EnumOutcome, Satoshis)] = Vector(
|
||||
(EnumOutcome("TRUMP"), Satoshis.zero),
|
||||
(EnumOutcome("BIDEN"), Bitcoins.one.satoshis),
|
||||
(EnumOutcome("TIE"), Satoshis(50000000))
|
||||
)
|
||||
val contract = EnumContractDescriptor(vec)
|
||||
assert(contract.hex == expectedHex)
|
||||
}
|
||||
|
||||
it should "fail to create an empty EnumContractDescriptor" in {
|
||||
assertThrows[IllegalArgumentException](EnumContractDescriptor(Vector.empty))
|
||||
}
|
||||
|
||||
it should "fail for not starting with a endpoint" in {
|
||||
val func = DLCPayoutCurve(
|
||||
Vector(
|
||||
OutcomePayoutPoint(0, Satoshis(0), isEndpoint = false),
|
||||
OutcomePayoutPoint(3, Satoshis(100), isEndpoint = true)
|
||||
))
|
||||
assertThrows[IllegalArgumentException](
|
||||
NumericContractDescriptor(func, 2, RoundingIntervals.noRounding))
|
||||
assertThrows[IllegalArgumentException] {
|
||||
val func = DLCPayoutCurve.polynomialInterpolate(
|
||||
Vector(
|
||||
PiecewisePolynomialPoint(0, Satoshis(0), isEndpoint = false),
|
||||
PiecewisePolynomialPoint(3, Satoshis(100), isEndpoint = true)
|
||||
),
|
||||
serializationVersion = DLCSerializationVersion.Post144Pre163
|
||||
)
|
||||
NumericContractDescriptor(func, 2, RoundingIntervals.noRounding)
|
||||
}
|
||||
}
|
||||
|
||||
it should "fail for not ending with a endpoint" in {
|
||||
val func = DLCPayoutCurve(
|
||||
Vector(
|
||||
OutcomePayoutPoint(0, Satoshis(0), isEndpoint = true),
|
||||
OutcomePayoutPoint(3, Satoshis(100), isEndpoint = false)
|
||||
))
|
||||
assertThrows[IllegalArgumentException](
|
||||
NumericContractDescriptor(func, 2, RoundingIntervals.noRounding))
|
||||
assertThrows[IllegalArgumentException] {
|
||||
val func = DLCPayoutCurve.polynomialInterpolate(
|
||||
Vector(
|
||||
PiecewisePolynomialPoint(0, Satoshis(0), isEndpoint = true),
|
||||
PiecewisePolynomialPoint(3, Satoshis(100), isEndpoint = false)
|
||||
),
|
||||
serializationVersion = DLCSerializationVersion.Post144Pre163
|
||||
)
|
||||
NumericContractDescriptor(func, 2, RoundingIntervals.noRounding)
|
||||
}
|
||||
}
|
||||
|
||||
it should "fail for starting below the minimum" in {
|
||||
val func = DLCPayoutCurve(
|
||||
val func = DLCPayoutCurve.polynomialInterpolate(
|
||||
Vector(
|
||||
OutcomePayoutPoint(-1, Satoshis(0), isEndpoint = true),
|
||||
OutcomePayoutPoint(3, Satoshis(100), isEndpoint = true)
|
||||
))
|
||||
PiecewisePolynomialPoint(-1, Satoshis(0), isEndpoint = true),
|
||||
PiecewisePolynomialPoint(3, Satoshis(100), isEndpoint = true)
|
||||
),
|
||||
serializationVersion = DLCSerializationVersion.Post144Pre163
|
||||
)
|
||||
assertThrows[IllegalArgumentException](
|
||||
NumericContractDescriptor(func, 2, RoundingIntervals.noRounding))
|
||||
}
|
||||
|
||||
it should "fail for starting above the minimum" in {
|
||||
val func = DLCPayoutCurve(
|
||||
val func = DLCPayoutCurve.polynomialInterpolate(
|
||||
Vector(
|
||||
OutcomePayoutPoint(1, Satoshis(0), isEndpoint = true),
|
||||
OutcomePayoutPoint(3, Satoshis(100), isEndpoint = true)
|
||||
))
|
||||
PiecewisePolynomialPoint(1, Satoshis(0), isEndpoint = true),
|
||||
PiecewisePolynomialPoint(3, Satoshis(100), isEndpoint = true)
|
||||
),
|
||||
serializationVersion = DLCSerializationVersion.Post144Pre163
|
||||
)
|
||||
assertThrows[IllegalArgumentException](
|
||||
NumericContractDescriptor(func, 2, RoundingIntervals.noRounding))
|
||||
}
|
||||
|
||||
it should "fail for ending below the maximum" in {
|
||||
val func = DLCPayoutCurve(
|
||||
val func = DLCPayoutCurve.polynomialInterpolate(
|
||||
Vector(
|
||||
OutcomePayoutPoint(0, Satoshis(0), isEndpoint = true),
|
||||
OutcomePayoutPoint(2, Satoshis(100), isEndpoint = true)
|
||||
))
|
||||
PiecewisePolynomialPoint(0, Satoshis(0), isEndpoint = true),
|
||||
PiecewisePolynomialPoint(2, Satoshis(100), isEndpoint = true)
|
||||
),
|
||||
serializationVersion = DLCSerializationVersion.Post144Pre163
|
||||
)
|
||||
assertThrows[IllegalArgumentException](
|
||||
NumericContractDescriptor(func, 2, RoundingIntervals.noRounding))
|
||||
}
|
||||
|
||||
it should "fail for ending above the maximum" in {
|
||||
val func = DLCPayoutCurve(
|
||||
val func = DLCPayoutCurve.polynomialInterpolate(
|
||||
Vector(
|
||||
OutcomePayoutPoint(0, Satoshis(0), isEndpoint = true),
|
||||
OutcomePayoutPoint(4, Satoshis(100), isEndpoint = true)
|
||||
))
|
||||
PiecewisePolynomialPoint(0, Satoshis(0), isEndpoint = true),
|
||||
PiecewisePolynomialPoint(4, Satoshis(100), isEndpoint = true)
|
||||
),
|
||||
serializationVersion = DLCSerializationVersion.Post144Pre163
|
||||
)
|
||||
assertThrows[IllegalArgumentException](
|
||||
NumericContractDescriptor(func, 2, RoundingIntervals.noRounding))
|
||||
}
|
||||
|
||||
it should "correctly create a NumericContractDescriptor" in {
|
||||
val func = DLCPayoutCurve(
|
||||
it should "parse a numeric contract descriptor pre 144" in {
|
||||
//we have to be able to parse old numeric contract descriptors
|
||||
//pre pr 144 on the DLC spec as we have old wallets deployed with this
|
||||
//https://github.com/discreetlogcontracts/dlcspecs/pull/144
|
||||
val func = DLCPayoutCurve.polynomialInterpolate(
|
||||
Vector(
|
||||
OutcomePayoutPoint(0, Satoshis(0), isEndpoint = true),
|
||||
OutcomePayoutPoint(3, Satoshis(100), isEndpoint = true)
|
||||
))
|
||||
PiecewisePolynomialPoint(outcome = 0,
|
||||
payout = Satoshis(0),
|
||||
isEndpoint = true),
|
||||
PiecewisePolynomialPoint(outcome = 3,
|
||||
payout = Satoshis(100),
|
||||
isEndpoint = true)
|
||||
),
|
||||
serializationVersion = DLCSerializationVersion.Post144Pre163
|
||||
)
|
||||
|
||||
val descriptor =
|
||||
NumericContractDescriptor(func, 2, RoundingIntervals.noRounding)
|
||||
val expected =
|
||||
NumericContractDescriptor(outcomeValueFunc = func,
|
||||
numDigits = 2,
|
||||
roundingIntervals =
|
||||
RoundingIntervals.noRounding)
|
||||
|
||||
assert(descriptor.toTLV == ContractDescriptorV1TLV(
|
||||
"fda720260002fda7261a0002010000000000000000000000010300000000000000640000fda724020000"))
|
||||
val oldHex =
|
||||
"fda720260002fda7261a0002010000000000000000000000010300000000000000640000fda724020000"
|
||||
|
||||
val actual = ContractDescriptorV1TLV.fromHex(oldHex)
|
||||
|
||||
assert(actual.hex == expected.toTLV.hex)
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,11 @@ package org.bitcoins.core.protocol.dlc
|
||||
|
||||
import org.bitcoins.core.currency.Satoshis
|
||||
import org.bitcoins.core.protocol.dlc.models._
|
||||
import org.bitcoins.core.protocol.tlv.{EnumOutcome, OracleAnnouncementV0TLV}
|
||||
import org.bitcoins.core.protocol.tlv.{
|
||||
DLCSerializationVersion,
|
||||
EnumOutcome,
|
||||
OracleAnnouncementV0TLV
|
||||
}
|
||||
import org.bitcoins.core.util.sorted.OrderedAnnouncements
|
||||
import org.bitcoins.crypto.ECPrivateKey
|
||||
import org.bitcoins.testkitcore.util.BitcoinSUnitTest
|
||||
@ -54,11 +58,13 @@ class ContractOraclePairTest extends BitcoinSUnitTest {
|
||||
it should "not be able to construct an invalid numeric contract oracle pair" in {
|
||||
val contractDescriptor =
|
||||
NumericContractDescriptor(
|
||||
DLCPayoutCurve(
|
||||
Vector(OutcomePayoutEndpoint(0, 0),
|
||||
OutcomePayoutEndpoint((1L << 7) - 1, 1))),
|
||||
DLCPayoutCurve.polynomialInterpolate(
|
||||
Vector(PiecewisePolynomialEndpoint(0, 0),
|
||||
PiecewisePolynomialEndpoint((1L << 7) - 1, 1)),
|
||||
serializationVersion = DLCSerializationVersion.Post144Pre163),
|
||||
numDigits = 7,
|
||||
RoundingIntervals.noRounding)
|
||||
RoundingIntervals.noRounding
|
||||
)
|
||||
|
||||
def numericOracleInfo(numDigits: Int): NumericSingleOracleInfo = {
|
||||
NumericSingleOracleInfo(
|
||||
|
@ -2,15 +2,17 @@ package org.bitcoins.core.protocol.dlc
|
||||
|
||||
import org.bitcoins.core.currency.Satoshis
|
||||
import org.bitcoins.core.protocol.dlc.models.{
|
||||
DLCHyperbolaPayoutCurvePiece,
|
||||
DLCPayoutCurve,
|
||||
OutcomePayoutCubic,
|
||||
OutcomePayoutEndpoint,
|
||||
OutcomePayoutLine,
|
||||
OutcomePayoutMidpoint,
|
||||
OutcomePayoutPoint,
|
||||
OutcomePayoutPolynomial,
|
||||
OutcomePayoutQuadratic
|
||||
OutcomePayoutQuadratic,
|
||||
PiecewisePolynomialEndpoint,
|
||||
PiecewisePolynomialMidpoint
|
||||
}
|
||||
import org.bitcoins.core.protocol.tlv.DLCSerializationVersion
|
||||
import org.bitcoins.testkitcore.util.BitcoinSUnitTest
|
||||
import org.scalacheck.Gen
|
||||
|
||||
@ -31,21 +33,17 @@ class DLCPayoutCurveTest extends BitcoinSUnitTest {
|
||||
val pointGen = for {
|
||||
outcome <- numGen
|
||||
value <- valueGen
|
||||
} yield OutcomePayoutEndpoint(outcome, Satoshis(value))
|
||||
} yield OutcomePayoutPoint(outcome, Satoshis(value))
|
||||
Gen
|
||||
.listOfN(n, pointGen)
|
||||
.suchThat(points =>
|
||||
points.map(_.outcome).distinct.length == points.length)
|
||||
.map(_.toVector.sortBy(_.outcome))
|
||||
.map { points =>
|
||||
points.head +: points.tail.init.map(_.toMidpoint) :+ points.last
|
||||
}
|
||||
}
|
||||
|
||||
it should "agree on lines and degree 1 polynomials" in {
|
||||
forAll(nPoints(2), Gen.listOfN(1000, numGen)) {
|
||||
case (Vector(point1: OutcomePayoutEndpoint,
|
||||
point2: OutcomePayoutEndpoint),
|
||||
case (Vector(point1: OutcomePayoutPoint, point2: OutcomePayoutPoint),
|
||||
outcomes) =>
|
||||
val line = OutcomePayoutLine(point1, point2)
|
||||
val polyDegOne = OutcomePayoutPolynomial(Vector(point1, point2))
|
||||
@ -78,8 +76,8 @@ class DLCPayoutCurveTest extends BitcoinSUnitTest {
|
||||
Satoshis(rounded)
|
||||
}
|
||||
|
||||
val point1 = OutcomePayoutEndpoint(x1, expectedPayout(x1))
|
||||
val point2 = OutcomePayoutEndpoint(x2, expectedPayout(x2))
|
||||
val point1 = OutcomePayoutPoint(x1, expectedPayout(x1))
|
||||
val point2 = OutcomePayoutPoint(x2, expectedPayout(x2))
|
||||
val line = OutcomePayoutLine(point1, point2)
|
||||
|
||||
outcomes.foreach { outcome =>
|
||||
@ -90,9 +88,9 @@ class DLCPayoutCurveTest extends BitcoinSUnitTest {
|
||||
|
||||
it should "agree on quadratics and degree 2 polynomials" in {
|
||||
forAll(nPoints(3), Gen.listOfN(1000, numGen)) {
|
||||
case (Vector(point1: OutcomePayoutEndpoint,
|
||||
point2: OutcomePayoutMidpoint,
|
||||
point3: OutcomePayoutEndpoint),
|
||||
case (Vector(point1: OutcomePayoutPoint,
|
||||
point2: OutcomePayoutPoint,
|
||||
point3: OutcomePayoutPoint),
|
||||
outcomes) =>
|
||||
val parabola = OutcomePayoutQuadratic(point1, point2, point3)
|
||||
val polyDegTwo =
|
||||
@ -124,9 +122,9 @@ class DLCPayoutCurveTest extends BitcoinSUnitTest {
|
||||
Satoshis(rounded)
|
||||
}
|
||||
|
||||
val point1 = OutcomePayoutEndpoint(x1, expectedPayout(x1))
|
||||
val point2 = OutcomePayoutMidpoint(x2, expectedPayout(x2))
|
||||
val point3 = OutcomePayoutEndpoint(x3, expectedPayout(x3))
|
||||
val point1 = OutcomePayoutPoint(x1, expectedPayout(x1))
|
||||
val point2 = OutcomePayoutPoint(x2, expectedPayout(x2))
|
||||
val point3 = OutcomePayoutPoint(x3, expectedPayout(x3))
|
||||
val parabola = OutcomePayoutQuadratic(point1, point2, point3)
|
||||
|
||||
outcomes.foreach { outcome =>
|
||||
@ -153,9 +151,9 @@ class DLCPayoutCurveTest extends BitcoinSUnitTest {
|
||||
Satoshis(rounded)
|
||||
}
|
||||
|
||||
val point1 = OutcomePayoutEndpoint(x1, expectedPayout(x1))
|
||||
val point2 = OutcomePayoutMidpoint(x2, expectedPayout(x2))
|
||||
val point3 = OutcomePayoutEndpoint(x3, expectedPayout(x3))
|
||||
val point1 = OutcomePayoutPoint(x1, expectedPayout(x1))
|
||||
val point2 = OutcomePayoutPoint(x2, expectedPayout(x2))
|
||||
val point3 = OutcomePayoutPoint(x3, expectedPayout(x3))
|
||||
val line = OutcomePayoutLine(point1, point3)
|
||||
val parabola = OutcomePayoutQuadratic(point1, point2, point3)
|
||||
|
||||
@ -167,10 +165,10 @@ class DLCPayoutCurveTest extends BitcoinSUnitTest {
|
||||
|
||||
it should "agree on cubics and degree 3 polynomials" in {
|
||||
forAll(nPoints(4), Gen.listOfN(1000, numGen)) {
|
||||
case (Vector(point1: OutcomePayoutEndpoint,
|
||||
point2: OutcomePayoutMidpoint,
|
||||
point3: OutcomePayoutMidpoint,
|
||||
point4: OutcomePayoutEndpoint),
|
||||
case (Vector(point1: OutcomePayoutPoint,
|
||||
point2: OutcomePayoutPoint,
|
||||
point3: OutcomePayoutPoint,
|
||||
point4: OutcomePayoutPoint),
|
||||
outcomes) =>
|
||||
val cubic = OutcomePayoutCubic(point1, point2, point3, point4)
|
||||
val polyDegThree =
|
||||
@ -208,10 +206,10 @@ class DLCPayoutCurveTest extends BitcoinSUnitTest {
|
||||
Satoshis(rounded)
|
||||
}
|
||||
|
||||
val point1 = OutcomePayoutEndpoint(x1, expectedPayout(x1))
|
||||
val point2 = OutcomePayoutMidpoint(x2, expectedPayout(x2))
|
||||
val point3 = OutcomePayoutMidpoint(x3, expectedPayout(x3))
|
||||
val point4 = OutcomePayoutEndpoint(x4, expectedPayout(x4))
|
||||
val point1 = OutcomePayoutPoint(x1, expectedPayout(x1))
|
||||
val point2 = OutcomePayoutPoint(x2, expectedPayout(x2))
|
||||
val point3 = OutcomePayoutPoint(x3, expectedPayout(x3))
|
||||
val point4 = OutcomePayoutPoint(x4, expectedPayout(x4))
|
||||
val cubic = OutcomePayoutCubic(point1, point2, point3, point4)
|
||||
|
||||
outcomes.foreach { outcome =>
|
||||
@ -239,10 +237,10 @@ class DLCPayoutCurveTest extends BitcoinSUnitTest {
|
||||
Satoshis(rounded)
|
||||
}
|
||||
|
||||
val point1 = OutcomePayoutEndpoint(x1, expectedPayout(x1))
|
||||
val point2 = OutcomePayoutMidpoint(x2, expectedPayout(x2))
|
||||
val point3 = OutcomePayoutMidpoint(x3, expectedPayout(x3))
|
||||
val point4 = OutcomePayoutEndpoint(x4, expectedPayout(x4))
|
||||
val point1 = OutcomePayoutPoint(x1, expectedPayout(x1))
|
||||
val point2 = OutcomePayoutPoint(x2, expectedPayout(x2))
|
||||
val point3 = OutcomePayoutPoint(x3, expectedPayout(x3))
|
||||
val point4 = OutcomePayoutPoint(x4, expectedPayout(x4))
|
||||
val line = OutcomePayoutLine(point1, point4)
|
||||
val cubic = OutcomePayoutCubic(point1, point2, point3, point4)
|
||||
|
||||
@ -271,10 +269,10 @@ class DLCPayoutCurveTest extends BitcoinSUnitTest {
|
||||
Satoshis(rounded)
|
||||
}
|
||||
|
||||
val point1 = OutcomePayoutEndpoint(x1, expectedPayout(x1))
|
||||
val point2 = OutcomePayoutMidpoint(x2, expectedPayout(x2))
|
||||
val point3 = OutcomePayoutMidpoint(x3, expectedPayout(x3))
|
||||
val point4 = OutcomePayoutEndpoint(x4, expectedPayout(x4))
|
||||
val point1 = OutcomePayoutPoint(x1, expectedPayout(x1))
|
||||
val point2 = OutcomePayoutPoint(x2, expectedPayout(x2))
|
||||
val point3 = OutcomePayoutPoint(x3, expectedPayout(x3))
|
||||
val point4 = OutcomePayoutPoint(x4, expectedPayout(x4))
|
||||
val quadratic = OutcomePayoutQuadratic(point1, point2, point4)
|
||||
val cubic = OutcomePayoutCubic(point1, point2, point3, point4)
|
||||
|
||||
@ -284,34 +282,80 @@ class DLCPayoutCurveTest extends BitcoinSUnitTest {
|
||||
}
|
||||
}
|
||||
|
||||
it should "agree on hyperbolas and y = d/x + translatePayout" in {
|
||||
forAll(intGen.suchThat(_ > 0),
|
||||
intGen,
|
||||
Gen.listOfN(1000, numGen.suchThat(_ > 0))) {
|
||||
case (d, translatePayout, outcomes) =>
|
||||
def expectedPayout(outcome: BigDecimal): Satoshis = {
|
||||
val value = d / outcome + translatePayout
|
||||
val rounded = value.setScale(0, RoundingMode.FLOOR).toLongExact
|
||||
Satoshis(rounded)
|
||||
}
|
||||
|
||||
val hyperbola =
|
||||
DLCHyperbolaPayoutCurvePiece(usePositivePiece = true,
|
||||
translateOutcome = 0,
|
||||
translatePayout,
|
||||
a = 1,
|
||||
b = 0,
|
||||
c = 0,
|
||||
d,
|
||||
OutcomePayoutPoint(0, 0),
|
||||
OutcomePayoutPoint(Long.MaxValue, 0))
|
||||
|
||||
outcomes.foreach { outcome =>
|
||||
assert(hyperbola(outcome) == expectedPayout(outcome))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it should "parse points into component functions correctly and compute outputs" in {
|
||||
val point0 = OutcomePayoutEndpoint(0, Satoshis.zero)
|
||||
val point1 = OutcomePayoutEndpoint(10, Satoshis(100))
|
||||
val point0 = PiecewisePolynomialEndpoint(0, Satoshis.zero)
|
||||
val point1 = PiecewisePolynomialEndpoint(10, Satoshis(100))
|
||||
|
||||
val line = DLCPayoutCurve(Vector(point0, point1))
|
||||
val lineFunc = line.functionComponents
|
||||
assert(lineFunc == Vector(OutcomePayoutLine(point0, point1)))
|
||||
val line = DLCPayoutCurve.polynomialInterpolate(
|
||||
Vector(point0, point1),
|
||||
serializationVersion = DLCSerializationVersion.Post144Pre163)
|
||||
val lineFunc = line.pieces
|
||||
assert(
|
||||
lineFunc == Vector(OutcomePayoutLine(point0.toOutcomePayoutPoint,
|
||||
point1.toOutcomePayoutPoint)))
|
||||
|
||||
val point2 = OutcomePayoutMidpoint(20, Satoshis.zero)
|
||||
val point3 = OutcomePayoutEndpoint(30, Satoshis(300))
|
||||
val point2 = PiecewisePolynomialMidpoint(20, Satoshis.zero)
|
||||
val point3 = PiecewisePolynomialEndpoint(30, Satoshis(300))
|
||||
|
||||
val quad = DLCPayoutCurve(Vector(point1, point2, point3))
|
||||
val quadFunc = quad.functionComponents
|
||||
assert(quadFunc == Vector(OutcomePayoutQuadratic(point1, point2, point3)))
|
||||
val quad =
|
||||
DLCPayoutCurve.polynomialInterpolate(
|
||||
Vector(point1, point2, point3),
|
||||
serializationVersion = DLCSerializationVersion.Post144Pre163)
|
||||
val quadFunc = quad.pieces
|
||||
assert(
|
||||
quadFunc == Vector(
|
||||
OutcomePayoutQuadratic(point1.toOutcomePayoutPoint,
|
||||
point2.toOutcomePayoutPoint,
|
||||
point3.toOutcomePayoutPoint)))
|
||||
|
||||
val point4 = OutcomePayoutMidpoint(40, Satoshis(600))
|
||||
val point5 = OutcomePayoutMidpoint(50, Satoshis(500))
|
||||
val point6 = OutcomePayoutEndpoint(60, Satoshis(700))
|
||||
val point4 = PiecewisePolynomialMidpoint(40, Satoshis(600))
|
||||
val point5 = PiecewisePolynomialMidpoint(50, Satoshis(500))
|
||||
val point6 = PiecewisePolynomialEndpoint(60, Satoshis(700))
|
||||
val cubicPoints = Vector(point3, point4, point5, point6)
|
||||
|
||||
val cubic = DLCPayoutCurve(cubicPoints)
|
||||
val cubicFunc = cubic.functionComponents
|
||||
val cubic = DLCPayoutCurve.polynomialInterpolate(
|
||||
cubicPoints,
|
||||
serializationVersion = DLCSerializationVersion.Post144Pre163)
|
||||
val cubicFunc = cubic.pieces
|
||||
assert(
|
||||
cubicFunc == Vector(OutcomePayoutCubic(point3, point4, point5, point6)))
|
||||
cubicFunc == Vector(
|
||||
OutcomePayoutCubic(point3.toOutcomePayoutPoint,
|
||||
point4.toOutcomePayoutPoint,
|
||||
point5.toOutcomePayoutPoint,
|
||||
point6.toOutcomePayoutPoint)))
|
||||
|
||||
val func = DLCPayoutCurve(
|
||||
Vector(point0, point1, point2, point3, point4, point5, point6))
|
||||
val allFuncs = func.functionComponents
|
||||
val func = DLCPayoutCurve.polynomialInterpolate(
|
||||
Vector(point0, point1, point2, point3, point4, point5, point6),
|
||||
serializationVersion = DLCSerializationVersion.Post144Pre163)
|
||||
val allFuncs = func.pieces
|
||||
assert(allFuncs == lineFunc ++ quadFunc ++ cubicFunc)
|
||||
|
||||
forAll(Gen.choose[Long](0, 60)) { outcome =>
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.bitcoins.core.protocol.tlv
|
||||
|
||||
import org.bitcoins.core.protocol.dlc.models.ContractInfo
|
||||
import org.bitcoins.testkitcore.gen.TLVGen
|
||||
import org.bitcoins.testkitcore.util.BitcoinSUnitTest
|
||||
|
||||
@ -184,4 +185,13 @@ class TLVTest extends BitcoinSUnitTest {
|
||||
assert(TLV(sign.bytes) == sign)
|
||||
}
|
||||
}
|
||||
|
||||
it must "parse a contract info pre 144" in {
|
||||
//this was a contract info created before we implemented support for
|
||||
//https://github.com/discreetlogcontracts/dlcspecs/pull/144
|
||||
val oldHex =
|
||||
"fdd82efd032500000000000186a0fda720540011fda72648000501000000000000000000000001fd9c400000000000000000000001fda604000000000000c350000001fdafc800000000000186a0000001fe0001ffff00000000000186a00000fda724020000fda712fd02bffdd824fd02b9659e890eef1b223ba45c9993f88c7997859302fd5510ac23f4cac0d4ee8232a77ecbdf50c07f093794370e6a506a836f6b0fb54b45f1fb662e1307166d2e57030574f77305826939fa9124d19bfa8a8b2f00f000586b8c58c79ee8b77969a949fdd822fd025300114762c188048a953803f0edeeeb68c69e6cdc1d371ba8d517003accfe05afc4d6588c3ea326512bc66c26a841adffa68330b8c723da442792e731fb19fda94274a7766bb48e520f118c100bbe62dc3806a8d05a63d92e23683a04b0b8c24148cd166585a6b33b995b3d6c083523a8435b156c05100d88f449f4754310d5574d5e88aad09af1b8ba942cfd305e728044ec6360d847254453ec05b1b518a36660e2238360e02f3a004663a7f3a3534973d8b66a2646c1386779aa820672b6361b88a8696395c0add87840b460dfd8a8c0d520017efc6bf58267d4c9d2a225c5d0e5719068a7dda5d630d7432239b6c9d921d5f3842b584503460ca52612ac2e64337d299513690372e8f4770eb8a28080e8d7c29920ca32af470d65d6f916ee81e3ac15ce02684ba6d2522a9ffea1de7e202b4b699ef7ec4f089dda07f3de5b7d1f853b2c56471999be4efca82674a651c80f047ba3a2b9e6f9999f0cd4062c533d1ae29cab2a5e33cbe98728b7b4271c67f7c5cd6e12e39128b9971e08496cbd84cfa99c77c88867d33e73acef37022ba4422a5221776991d45416db71fb54bc6c104f6a8e50e8905161709215104a7e7b97e866f32cf43233ffd615cab66699832ec607cf59c85a7f56fa957aa5f5d7ec9f46d84d5d4b777122d41ad76c6f4968aeedca243f2030d4f502e58f4181130e9afb75309ac21637bcfd0717528bfb82ffe1b6c9fadee6ba70357210990539184bcc913a0ec65837a736733a2fb6172d601b3900fdd80a11000200074254432f55534400000000001117626974636f696e2d732d70726963652d6578616d706c65"
|
||||
//https://test.oracle.suredbits.com/contract/numeric/d4d4df2892fb2cfd2e8f030f0e69a568e19668b5d355e7713f69853db09a4c33
|
||||
assert(ContractInfo.fromHexOpt(oldHex).isDefined)
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import org.bitcoins.core.currency.Satoshis
|
||||
import org.bitcoins.core.protocol.dlc.models._
|
||||
import org.bitcoins.core.protocol.tlv.{
|
||||
DLCOutcomeType,
|
||||
DLCSerializationVersion,
|
||||
EnumOutcome,
|
||||
SignedNumericOutcome,
|
||||
UnsignedNumericOutcome
|
||||
@ -82,7 +83,7 @@ object CETCalculator {
|
||||
|
||||
val (currentFunc, currentFuncIndex) =
|
||||
if (from + 1 == firstFunc.rightEndpoint.outcome && from + 1 != to) {
|
||||
(function.functionComponents(firstFuncIndex + 1), firstFuncIndex + 1)
|
||||
(function.pieces(firstFuncIndex + 1), firstFuncIndex + 1)
|
||||
} else {
|
||||
(firstFunc, firstFuncIndex)
|
||||
}
|
||||
@ -161,8 +162,7 @@ object CETCalculator {
|
||||
if (
|
||||
constantTo + 1 == currentFunc.rightEndpoint.outcome && constantTo + 1 != to
|
||||
) {
|
||||
(function.functionComponents(currentFuncIndex + 1),
|
||||
currentFuncIndex + 1)
|
||||
(function.pieces(currentFuncIndex + 1), currentFuncIndex + 1)
|
||||
} else {
|
||||
(currentFunc, currentFuncIndex)
|
||||
}
|
||||
@ -471,21 +471,21 @@ object CETCalculator {
|
||||
def payoutSample(
|
||||
func: Long => Long,
|
||||
numDigits: Int,
|
||||
numPoints: Long): Vector[OutcomePayoutEndpoint] = {
|
||||
numPoints: Long): Vector[PiecewisePolynomialEndpoint] = {
|
||||
val maxVal = (1L << numDigits) - 1
|
||||
0L.until(maxVal, maxVal / numPoints)
|
||||
.toVector
|
||||
.map { outcome =>
|
||||
val payout = func(outcome)
|
||||
OutcomePayoutEndpoint(outcome, payout)
|
||||
PiecewisePolynomialEndpoint(outcome, payout)
|
||||
}
|
||||
.:+(OutcomePayoutEndpoint(maxVal, func(maxVal)))
|
||||
.:+(PiecewisePolynomialEndpoint(maxVal, func(maxVal)))
|
||||
}
|
||||
|
||||
def payoutSampleByInterval(
|
||||
func: Long => Long,
|
||||
numDigits: Int,
|
||||
interval: Int): Vector[OutcomePayoutEndpoint] = {
|
||||
interval: Int): Vector[PiecewisePolynomialEndpoint] = {
|
||||
val maxVal = (1L << numDigits) - 1
|
||||
payoutSample(func, numDigits, maxVal / interval)
|
||||
}
|
||||
@ -494,7 +494,9 @@ object CETCalculator {
|
||||
func: Long => Long,
|
||||
numDigits: Int,
|
||||
interval: Int): DLCPayoutCurve = {
|
||||
DLCPayoutCurve(payoutSampleByInterval(func, numDigits, interval))
|
||||
DLCPayoutCurve.polynomialInterpolate(
|
||||
payoutSampleByInterval(func, numDigits, interval),
|
||||
serializationVersion = DLCSerializationVersion.Post144Pre163)
|
||||
}
|
||||
|
||||
/** Computes all combinations of threshold oracles, preserving order. */
|
||||
|
@ -102,31 +102,21 @@ case class NumericContractDescriptor(
|
||||
private val minValue: Long = 0L
|
||||
private val maxValue: Long = (Math.pow(2, numDigits) - 1).toLong
|
||||
|
||||
require(outcomeValueFunc.points.head.isEndpoint,
|
||||
"Payout curve must start with an end point")
|
||||
require(
|
||||
outcomeValueFunc.points.head.outcome == 0,
|
||||
s"Payout curve must start with its minimum value, $minValue, got ${outcomeValueFunc.points.head.outcome}. " +
|
||||
outcomeValueFunc.endpoints.head.outcome == 0,
|
||||
s"Payout curve must start with its minimum value, $minValue, got ${outcomeValueFunc.endpoints.head.outcome}. " +
|
||||
s"You must define the payout curve from $minValue - $maxValue"
|
||||
)
|
||||
|
||||
require(outcomeValueFunc.points.last.isEndpoint,
|
||||
"Payout curve must end with an end point")
|
||||
|
||||
require(
|
||||
outcomeValueFunc.points.last.outcome == maxValue,
|
||||
s"Payout curve must end with its maximum value, $maxValue, got ${outcomeValueFunc.points.last.outcome}. " +
|
||||
outcomeValueFunc.endpoints.last.outcome == maxValue,
|
||||
s"Payout curve must end with its maximum value, $maxValue, got ${outcomeValueFunc.endpoints.last.outcome}. " +
|
||||
s"You must define the payout curve from $minValue - $maxValue"
|
||||
)
|
||||
|
||||
override def flip(totalCollateral: Satoshis): NumericContractDescriptor = {
|
||||
|
||||
val flippedFunc = DLCPayoutCurve(outcomeValueFunc.points.map { point =>
|
||||
point.copy(payout = totalCollateral.toLong - point.payout)
|
||||
})
|
||||
|
||||
NumericContractDescriptor(
|
||||
flippedFunc,
|
||||
outcomeValueFunc.flip(totalCollateral),
|
||||
numDigits,
|
||||
roundingIntervals
|
||||
)
|
||||
|
@ -2,6 +2,7 @@ package org.bitcoins.core.protocol.dlc.models
|
||||
|
||||
import org.bitcoins.core.currency._
|
||||
import org.bitcoins.core.protocol.dlc.compute.CETCalculator
|
||||
import org.bitcoins.core.protocol.tlv.DLCSerializationVersion
|
||||
|
||||
sealed trait ContractDescriptorTemplate {
|
||||
def individualCollateral: CurrencyUnit
|
||||
@ -106,29 +107,32 @@ sealed trait OptionTemplate extends ContractDescriptorTemplate {
|
||||
|
||||
val curve = this match {
|
||||
case _: CallOption =>
|
||||
val pointA = OutcomePayoutEndpoint(
|
||||
val pointA = PiecewisePolynomialEndpoint(
|
||||
0L,
|
||||
individualCollateral.satoshis - premium.satoshis)
|
||||
(individualCollateral - premium).satoshis)
|
||||
|
||||
val pointB = OutcomePayoutEndpoint(
|
||||
val pointB = PiecewisePolynomialEndpoint(
|
||||
strikePrice,
|
||||
individualCollateral.satoshis - premium.satoshis)
|
||||
(individualCollateral - premium).satoshis)
|
||||
|
||||
val pointC =
|
||||
OutcomePayoutEndpoint(maxNum, totalCollateral)
|
||||
DLCPayoutCurve(Vector(pointA, pointB, pointC))
|
||||
PiecewisePolynomialEndpoint(maxNum, totalCollateral.satoshis)
|
||||
DLCPayoutCurve.polynomialInterpolate(
|
||||
Vector(pointA, pointB, pointC),
|
||||
serializationVersion = DLCSerializationVersion.Post144Pre163)
|
||||
case _: PutOption =>
|
||||
val pointA = OutcomePayoutEndpoint(0L, totalCollateral)
|
||||
val pointA = PiecewisePolynomialEndpoint(0L, totalCollateral.satoshis)
|
||||
|
||||
val pointB = OutcomePayoutEndpoint(
|
||||
val pointB = PiecewisePolynomialEndpoint(
|
||||
strikePrice,
|
||||
individualCollateral.satoshis - premium.satoshis)
|
||||
(individualCollateral - premium).satoshis)
|
||||
|
||||
val pointC =
|
||||
OutcomePayoutEndpoint(
|
||||
maxNum,
|
||||
individualCollateral.satoshis - premium.satoshis)
|
||||
DLCPayoutCurve(Vector(pointA, pointB, pointC))
|
||||
PiecewisePolynomialEndpoint(maxNum,
|
||||
(individualCollateral - premium).satoshis)
|
||||
DLCPayoutCurve.polynomialInterpolate(
|
||||
Vector(pointA, pointB, pointC),
|
||||
serializationVersion = DLCSerializationVersion.Post144Pre163)
|
||||
}
|
||||
|
||||
NumericContractDescriptor(curve, numDigits = numDigits, roundingIntervals)
|
||||
|
@ -1,57 +1,45 @@
|
||||
package org.bitcoins.core.protocol.dlc.models
|
||||
|
||||
import org.bitcoins.core.currency.{CurrencyUnit, Satoshis}
|
||||
import org.bitcoins.core.protocol.tlv.{PayoutFunctionV0TLV, TLVPoint}
|
||||
import org.bitcoins.core.protocol.tlv._
|
||||
import org.bitcoins.core.util.{Indexed, NumberUtil}
|
||||
|
||||
import scala.math.BigDecimal.RoundingMode
|
||||
import scala.util.{Failure, Success, Try}
|
||||
|
||||
/** A DLC payout curve defined by piecewise interpolating points */
|
||||
case class DLCPayoutCurve(points: Vector[OutcomePayoutPoint]) {
|
||||
require(points.init.zip(points.tail).forall { case (p1, p2) =>
|
||||
p1.outcome < p2.outcome
|
||||
},
|
||||
s"Points must be ascending: $points")
|
||||
case class DLCPayoutCurve(
|
||||
pieces: Vector[DLCPayoutCurvePiece],
|
||||
serializationVersion: DLCSerializationVersion)
|
||||
extends TLVSerializable[PayoutFunctionV0TLV] {
|
||||
|
||||
def toTLV: PayoutFunctionV0TLV = {
|
||||
PayoutFunctionV0TLV(points.map { point =>
|
||||
TLVPoint(point.outcome,
|
||||
point.roundedPayout,
|
||||
point.extraPrecision,
|
||||
point.isEndpoint)
|
||||
})
|
||||
val endpoints: Vector[OutcomePayoutPoint] = {
|
||||
pieces.map(_.leftEndpoint).:+(pieces.last.rightEndpoint)
|
||||
}
|
||||
|
||||
/** 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.
|
||||
*/
|
||||
lazy val endpoints: Vector[Indexed[OutcomePayoutPoint]] =
|
||||
Indexed(points).filter(_.element.isEndpoint)
|
||||
require(pieces.map(_.rightEndpoint) == endpoints.tail,
|
||||
s"Endpoints must line up: $this")
|
||||
|
||||
/** This Vector contains the function pieces between the endpoints */
|
||||
lazy val functionComponents: Vector[DLCPayoutCurvePiece] = {
|
||||
endpoints.init.zip(endpoints.tail).map { // All pairs of adjacent endpoints
|
||||
case (Indexed(_, index), Indexed(_, nextIndex)) =>
|
||||
DLCPayoutCurvePiece(points.slice(index, nextIndex + 1))
|
||||
}
|
||||
override def toTLV: PayoutFunctionV0TLV = {
|
||||
val tlvEndpoints = endpoints.map(_.toTLVPoint)
|
||||
val tlvPieces = pieces.map(_.toTLV)
|
||||
|
||||
PayoutFunctionV0TLV(tlvEndpoints, tlvPieces, serializationVersion)
|
||||
}
|
||||
|
||||
private lazy val outcomes = endpoints.map(_.element.outcome)
|
||||
private lazy val endpointOutcomes = endpoints.map(_.outcome)
|
||||
|
||||
/** Returns the function component on which the given oracle outcome is
|
||||
* defined, along with its index
|
||||
*/
|
||||
def componentFor(outcome: Long): Indexed[DLCPayoutCurvePiece] = {
|
||||
val endpointIndex = NumberUtil.search(outcomes, outcome)
|
||||
val Indexed(endpoint, _) = endpoints(endpointIndex)
|
||||
val endpointIndex = NumberUtil.search(endpointOutcomes, outcome)
|
||||
val endpoint = endpoints(endpointIndex)
|
||||
|
||||
if (
|
||||
endpoint.outcome == outcome && endpointIndex != functionComponents.length
|
||||
) {
|
||||
Indexed(functionComponents(endpointIndex), endpointIndex)
|
||||
if (endpoint.outcome == outcome && endpointIndex != pieces.length) {
|
||||
Indexed(pieces(endpointIndex), endpointIndex)
|
||||
} else {
|
||||
Indexed(functionComponents(endpointIndex - 1), endpointIndex - 1)
|
||||
Indexed(pieces(endpointIndex - 1), endpointIndex - 1)
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,29 +71,76 @@ case class DLCPayoutCurve(points: Vector[OutcomePayoutPoint]) {
|
||||
rounding: RoundingIntervals,
|
||||
totalCollateral: Satoshis): Satoshis =
|
||||
getPayout(outcome, rounding, totalCollateral)
|
||||
}
|
||||
|
||||
object DLCPayoutCurve {
|
||||
|
||||
def fromTLV(tlv: PayoutFunctionV0TLV): DLCPayoutCurve = {
|
||||
DLCPayoutCurve(tlv.points.map { point =>
|
||||
val payoutWithPrecision =
|
||||
point.value.toLong + (BigDecimal(point.extraPrecision) / (1 << 16))
|
||||
OutcomePayoutPoint(point.outcome, payoutWithPrecision, point.isEndpoint)
|
||||
})
|
||||
def flip(totalCollateral: Satoshis): DLCPayoutCurve = {
|
||||
DLCPayoutCurve(pieces.map(_.flip(totalCollateral)),
|
||||
serializationVersion = serializationVersion)
|
||||
}
|
||||
}
|
||||
|
||||
/** A point on a DLC payout curve to be used for interpolation
|
||||
*
|
||||
* outcome: An element of the domain of possible events signed by the oracle
|
||||
* payout: The payout to the local party corresponding to outcome
|
||||
* isEndpoint: True if this point defines a boundary between pieces in the curve
|
||||
*/
|
||||
sealed trait OutcomePayoutPoint {
|
||||
object DLCPayoutCurve
|
||||
extends TLVDeserializable[PayoutFunctionV0TLV, DLCPayoutCurve](
|
||||
PayoutFunctionV0TLV) {
|
||||
|
||||
override def fromTLV(tlv: PayoutFunctionV0TLV): DLCPayoutCurve = {
|
||||
val pieces =
|
||||
tlv.endpoints.init.zip(tlv.endpoints.tail).zip(tlv.pieces).map {
|
||||
case ((leftEndpoint, rightEndpoint), tlvPiece) =>
|
||||
DLCPayoutCurvePiece.fromTLV(leftEndpoint, tlvPiece, rightEndpoint)
|
||||
}
|
||||
|
||||
DLCPayoutCurve(pieces, tlv.serializationVersion)
|
||||
}
|
||||
|
||||
def polynomialInterpolate(
|
||||
points: Vector[PiecewisePolynomialPoint],
|
||||
serializationVersion: DLCSerializationVersion): DLCPayoutCurve = {
|
||||
require(points.head.isEndpoint && points.last.isEndpoint,
|
||||
s"First and last points must be endpoints: $points")
|
||||
|
||||
val initMidpoints = Vector.empty[PiecewisePolynomialMidpoint]
|
||||
val initCurvePieces = Vector.empty[DLCPolynomialPayoutCurvePiece]
|
||||
val (_, _, pieces) =
|
||||
points.tail.foldLeft((points.head, initMidpoints, initCurvePieces)) {
|
||||
case ((lastEndpoint, midpointsSoFar, piecesSoFar), point) =>
|
||||
point match {
|
||||
case midpoint: PiecewisePolynomialMidpoint =>
|
||||
(lastEndpoint, midpointsSoFar.:+(midpoint), piecesSoFar)
|
||||
case endpoint: PiecewisePolynomialEndpoint =>
|
||||
val all = midpointsSoFar
|
||||
.+:(lastEndpoint)
|
||||
.:+(endpoint)
|
||||
val points = all.map(_.toOutcomePayoutPoint)
|
||||
(endpoint,
|
||||
Vector.empty,
|
||||
piecesSoFar.:+(DLCPolynomialPayoutCurvePiece(points)))
|
||||
}
|
||||
}
|
||||
DLCPayoutCurve(pieces, serializationVersion)
|
||||
}
|
||||
|
||||
def fromPoints(
|
||||
points: Vector[TLVPoint],
|
||||
serializationVersion: DLCSerializationVersion): DLCPayoutCurve = {
|
||||
|
||||
val pieceEndpoints = points.map { p =>
|
||||
PiecewisePolynomialEndpoint(p.outcome, p.value)
|
||||
}
|
||||
|
||||
DLCPayoutCurve.polynomialInterpolate(pieceEndpoints, serializationVersion)
|
||||
}
|
||||
|
||||
def fromPointsPre144(points: Vector[OldTLVPoint]): DLCPayoutCurve = {
|
||||
val newPoints =
|
||||
points.map(p => TLVPoint(p.outcome, p.value, p.extraPrecision))
|
||||
fromPoints(newPoints,
|
||||
serializationVersion = DLCSerializationVersion.PrePR144)
|
||||
}
|
||||
}
|
||||
|
||||
trait DLCPoint {
|
||||
def outcome: Long
|
||||
def payout: BigDecimal
|
||||
def isEndpoint: Boolean
|
||||
|
||||
def roundedPayout: Satoshis = {
|
||||
Satoshis(payout.setScale(0, RoundingMode.FLOOR).toLongExact)
|
||||
@ -116,85 +151,96 @@ sealed trait OutcomePayoutPoint {
|
||||
shifted.setScale(0, RoundingMode.FLOOR).toIntExact
|
||||
}
|
||||
|
||||
def copy(
|
||||
outcome: Long = this.outcome,
|
||||
payout: BigDecimal = this.payout): OutcomePayoutPoint = {
|
||||
this match {
|
||||
case OutcomePayoutEndpoint(_, _) => OutcomePayoutEndpoint(outcome, payout)
|
||||
case OutcomePayoutMidpoint(_, _) => OutcomePayoutMidpoint(outcome, payout)
|
||||
}
|
||||
def toTLVPoint: TLVPoint = {
|
||||
TLVPoint(outcome, roundedPayout, extraPrecision)
|
||||
}
|
||||
|
||||
def toOutcomePayoutPoint: OutcomePayoutPoint = {
|
||||
OutcomePayoutPoint(outcome = outcome, payout = payout)
|
||||
}
|
||||
}
|
||||
|
||||
/** A point on a DLC payout curve to be used for interpolation
|
||||
*
|
||||
* outcome: An element of the domain of possible events signed by the oracle
|
||||
* payout: The payout to the local party corresponding to outcome
|
||||
*/
|
||||
case class OutcomePayoutPoint(outcome: Long, payout: BigDecimal)
|
||||
extends DLCPoint {
|
||||
|
||||
override def toString: String = {
|
||||
s"OutcomePayoutPoint(outcome=$outcome,payout=$payout)"
|
||||
}
|
||||
}
|
||||
|
||||
object OutcomePayoutPoint {
|
||||
|
||||
def apply(outcome: Long, payout: Satoshis): OutcomePayoutPoint = {
|
||||
OutcomePayoutPoint(outcome, payout.toLong)
|
||||
}
|
||||
|
||||
def fromTLVPoint(point: TLVPoint): OutcomePayoutPoint = {
|
||||
OutcomePayoutPoint(point.outcome, point.bigDecimalPayout)
|
||||
}
|
||||
}
|
||||
|
||||
sealed trait PiecewisePolynomialPoint extends DLCPoint {
|
||||
|
||||
/** True if this point defines a boundary between pieces in the curve */
|
||||
def isEndpoint: Boolean
|
||||
}
|
||||
|
||||
object PiecewisePolynomialPoint {
|
||||
|
||||
def apply(
|
||||
outcome: Long,
|
||||
payout: BigDecimal,
|
||||
isEndpoint: Boolean): OutcomePayoutPoint = {
|
||||
isEndpoint: Boolean): PiecewisePolynomialPoint = {
|
||||
if (isEndpoint) {
|
||||
OutcomePayoutEndpoint(outcome, payout)
|
||||
PiecewisePolynomialEndpoint(outcome, payout)
|
||||
} else {
|
||||
OutcomePayoutMidpoint(outcome, payout)
|
||||
PiecewisePolynomialMidpoint(outcome, payout)
|
||||
}
|
||||
}
|
||||
|
||||
def apply(
|
||||
outcome: Long,
|
||||
payout: Satoshis,
|
||||
isEndpoint: Boolean): OutcomePayoutPoint = {
|
||||
OutcomePayoutPoint(outcome, payout.toLong, isEndpoint)
|
||||
payout: CurrencyUnit,
|
||||
isEndpoint: Boolean): PiecewisePolynomialPoint = {
|
||||
PiecewisePolynomialPoint(outcome, payout.toBigDecimal, isEndpoint)
|
||||
}
|
||||
}
|
||||
|
||||
case class OutcomePayoutEndpoint(outcome: Long, payout: BigDecimal)
|
||||
extends OutcomePayoutPoint {
|
||||
override val isEndpoint: Boolean = true
|
||||
|
||||
def toMidpoint: OutcomePayoutMidpoint = OutcomePayoutMidpoint(outcome, payout)
|
||||
case class PiecewisePolynomialEndpoint(outcome: Long, payout: BigDecimal)
|
||||
extends PiecewisePolynomialPoint {
|
||||
override def isEndpoint: Boolean = true
|
||||
}
|
||||
|
||||
object OutcomePayoutEndpoint {
|
||||
object PiecewisePolynomialEndpoint {
|
||||
|
||||
def apply(outcome: Long, payout: CurrencyUnit): OutcomePayoutEndpoint = {
|
||||
OutcomePayoutEndpoint(outcome, payout.satoshis.toLong)
|
||||
def apply(outcome: Long, payout: Satoshis): PiecewisePolynomialEndpoint = {
|
||||
PiecewisePolynomialEndpoint(outcome, payout.toBigDecimal)
|
||||
}
|
||||
}
|
||||
|
||||
case class OutcomePayoutMidpoint(outcome: Long, payout: BigDecimal)
|
||||
extends OutcomePayoutPoint {
|
||||
override val isEndpoint: Boolean = false
|
||||
|
||||
def toEndpoint: OutcomePayoutEndpoint = OutcomePayoutEndpoint(outcome, payout)
|
||||
case class PiecewisePolynomialMidpoint(outcome: Long, payout: BigDecimal)
|
||||
extends PiecewisePolynomialPoint {
|
||||
override def isEndpoint: Boolean = false
|
||||
}
|
||||
|
||||
object OutcomePayoutMidpoint {
|
||||
object PiecewisePolynomialMidpoint {
|
||||
|
||||
def apply(outcome: Long, payout: Satoshis): OutcomePayoutMidpoint = {
|
||||
OutcomePayoutMidpoint(outcome, payout.toLong)
|
||||
def apply(outcome: Long, payout: Satoshis): PiecewisePolynomialMidpoint = {
|
||||
PiecewisePolynomialMidpoint(outcome, payout.toBigDecimal)
|
||||
}
|
||||
}
|
||||
|
||||
/** A single piece of a larger piecewise function defined between left and right endpoints */
|
||||
sealed trait DLCPayoutCurvePiece {
|
||||
def leftEndpoint: OutcomePayoutEndpoint
|
||||
def midpoints: Vector[OutcomePayoutMidpoint]
|
||||
def rightEndpoint: OutcomePayoutEndpoint
|
||||
sealed trait DLCPayoutCurvePiece extends TLVSerializable[PayoutCurvePieceTLV] {
|
||||
def leftEndpoint: OutcomePayoutPoint
|
||||
def rightEndpoint: OutcomePayoutPoint
|
||||
|
||||
midpoints.headOption match {
|
||||
case Some(firstMidpoint) =>
|
||||
require(leftEndpoint.outcome < firstMidpoint.outcome,
|
||||
s"Points must be ascending: $this")
|
||||
require(midpoints.init.zip(midpoints.tail).forall { case (m1, m2) =>
|
||||
m1.outcome < m2.outcome
|
||||
},
|
||||
s"Points must be ascending: $this")
|
||||
require(rightEndpoint.outcome > midpoints.last.outcome,
|
||||
s"Points must be ascending: $this")
|
||||
case None =>
|
||||
require(leftEndpoint.outcome < rightEndpoint.outcome,
|
||||
s"Points must be ascending: $this")
|
||||
}
|
||||
require(leftEndpoint.outcome < rightEndpoint.outcome,
|
||||
s"Points must be ascending: $this")
|
||||
|
||||
def apply(outcome: Long): Satoshis
|
||||
|
||||
@ -219,45 +265,194 @@ sealed trait DLCPayoutCurvePiece {
|
||||
.setScale(0, RoundingMode.FLOOR)
|
||||
.toLongExact)
|
||||
}
|
||||
|
||||
def flip(totalCollateral: Satoshis): DLCPayoutCurvePiece
|
||||
}
|
||||
|
||||
object DLCPayoutCurvePiece {
|
||||
|
||||
def apply(points: Vector[OutcomePayoutPoint]): DLCPayoutCurvePiece = {
|
||||
require(points.head.isEndpoint && points.last.isEndpoint,
|
||||
s"First and last points must be endpoints, $points")
|
||||
require(points.tail.init.forall(!_.isEndpoint),
|
||||
s"Endpoint detected in middle, $points")
|
||||
def fromTLV(
|
||||
leftEndpoint: TLVPoint,
|
||||
curvePiece: PayoutCurvePieceTLV,
|
||||
rightEndpoint: TLVPoint): DLCPayoutCurvePiece = {
|
||||
curvePiece match {
|
||||
case polynomial: PolynomialPayoutCurvePieceTLV =>
|
||||
DLCPolynomialPayoutCurvePiece.fromTLV(leftEndpoint,
|
||||
polynomial,
|
||||
rightEndpoint)
|
||||
case hyperbola: HyperbolaPayoutCurvePieceTLV =>
|
||||
DLCHyperbolaPayoutCurvePiece.fromTLV(leftEndpoint,
|
||||
hyperbola,
|
||||
rightEndpoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case class DLCHyperbolaPayoutCurvePiece(
|
||||
usePositivePiece: Boolean,
|
||||
translateOutcome: BigDecimal,
|
||||
translatePayout: BigDecimal,
|
||||
a: BigDecimal,
|
||||
b: BigDecimal,
|
||||
c: BigDecimal,
|
||||
d: BigDecimal,
|
||||
leftEndpoint: OutcomePayoutPoint,
|
||||
rightEndpoint: OutcomePayoutPoint)
|
||||
extends DLCPayoutCurvePiece
|
||||
with TLVSerializable[HyperbolaPayoutCurvePieceTLV] {
|
||||
require(a * d != b * c, s"ad cannot equal bc: $this")
|
||||
|
||||
override def apply(outcome: Long): Satoshis = {
|
||||
val resultT = Try {
|
||||
val translatedOutcome: BigDecimal = outcome - translateOutcome
|
||||
|
||||
val sqrtTermAbsVal: BigDecimal =
|
||||
BigDecimal(math.sqrt((translatedOutcome.pow(2) - 4 * a * b).toDouble))
|
||||
|
||||
val sqrtTerm: BigDecimal =
|
||||
if (usePositivePiece) sqrtTermAbsVal else -sqrtTermAbsVal
|
||||
|
||||
val firstTerm = c * (translatedOutcome + sqrtTerm) / (2 * a)
|
||||
val secondTerm = 2 * a * d / (translatedOutcome + sqrtTerm)
|
||||
|
||||
val value = firstTerm + secondTerm + translatePayout
|
||||
|
||||
bigDecimalSats(value)
|
||||
}
|
||||
|
||||
resultT match {
|
||||
case Success(result) => result
|
||||
case Failure(err) =>
|
||||
throw new IllegalArgumentException(s"Illegal input outcome $outcome.",
|
||||
err)
|
||||
}
|
||||
}
|
||||
|
||||
override def toTLV: HyperbolaPayoutCurvePieceTLV = {
|
||||
HyperbolaPayoutCurvePieceTLV(
|
||||
usePositivePiece,
|
||||
Signed16PTLVNumber.fromBigDecimal(translateOutcome),
|
||||
Signed16PTLVNumber.fromBigDecimal(translatePayout),
|
||||
Signed16PTLVNumber.fromBigDecimal(a),
|
||||
Signed16PTLVNumber.fromBigDecimal(b),
|
||||
Signed16PTLVNumber.fromBigDecimal(c),
|
||||
Signed16PTLVNumber.fromBigDecimal(d)
|
||||
)
|
||||
}
|
||||
|
||||
override def flip(totalCollateral: Satoshis): DLCHyperbolaPayoutCurvePiece = {
|
||||
DLCHyperbolaPayoutCurvePiece(
|
||||
usePositivePiece,
|
||||
translateOutcome,
|
||||
totalCollateral.toLong - translatePayout,
|
||||
a,
|
||||
b,
|
||||
-c,
|
||||
-d,
|
||||
leftEndpoint.copy(payout = totalCollateral.toLong - leftEndpoint.payout),
|
||||
rightEndpoint.copy(payout = totalCollateral.toLong - rightEndpoint.payout)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object DLCHyperbolaPayoutCurvePiece {
|
||||
|
||||
def fromTLV(
|
||||
leftEndpoint: TLVPoint,
|
||||
curvePiece: HyperbolaPayoutCurvePieceTLV,
|
||||
rightEndpoint: TLVPoint): DLCHyperbolaPayoutCurvePiece = {
|
||||
DLCHyperbolaPayoutCurvePiece(
|
||||
curvePiece.usePositivePiece,
|
||||
curvePiece.translateOutcome.toBigDecimal,
|
||||
curvePiece.translatePayout.toBigDecimal,
|
||||
curvePiece.a.toBigDecimal,
|
||||
curvePiece.b.toBigDecimal,
|
||||
curvePiece.c.toBigDecimal,
|
||||
curvePiece.d.toBigDecimal,
|
||||
OutcomePayoutPoint.fromTLVPoint(leftEndpoint),
|
||||
OutcomePayoutPoint.fromTLVPoint(rightEndpoint)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** A single piece of a larger piecewise function defined between left and right endpoints */
|
||||
sealed trait DLCPolynomialPayoutCurvePiece
|
||||
extends DLCPayoutCurvePiece
|
||||
with TLVSerializable[PolynomialPayoutCurvePieceTLV] {
|
||||
def midpoints: Vector[OutcomePayoutPoint]
|
||||
|
||||
def points: Vector[OutcomePayoutPoint] = {
|
||||
midpoints.+:(leftEndpoint).:+(rightEndpoint)
|
||||
}
|
||||
|
||||
midpoints.headOption.foreach { firstMidpoint =>
|
||||
require(leftEndpoint.outcome < firstMidpoint.outcome,
|
||||
s"Points must be ascending: $this")
|
||||
require(midpoints.init.zip(midpoints.tail).forall { case (m1, m2) =>
|
||||
m1.outcome < m2.outcome
|
||||
},
|
||||
s"Points must be ascending: $this")
|
||||
require(rightEndpoint.outcome > midpoints.last.outcome,
|
||||
s"Points must be ascending: $this")
|
||||
}
|
||||
|
||||
override def toTLV: PolynomialPayoutCurvePieceTLV = {
|
||||
PolynomialPayoutCurvePieceTLV(midpoints.map(_.toTLVPoint))
|
||||
}
|
||||
|
||||
override def flip(
|
||||
totalCollateral: Satoshis): DLCPolynomialPayoutCurvePiece = {
|
||||
val flippedPoints = points.map { point =>
|
||||
point.copy(payout = totalCollateral.toLong - point.payout)
|
||||
}
|
||||
|
||||
DLCPolynomialPayoutCurvePiece(flippedPoints)
|
||||
}
|
||||
}
|
||||
|
||||
object DLCPolynomialPayoutCurvePiece {
|
||||
|
||||
def apply(
|
||||
points: Vector[OutcomePayoutPoint]): DLCPolynomialPayoutCurvePiece = {
|
||||
points match {
|
||||
case Vector(left: OutcomePayoutEndpoint, right: OutcomePayoutEndpoint) =>
|
||||
case Vector(left: OutcomePayoutPoint, right: OutcomePayoutPoint) =>
|
||||
if (left.payout == right.payout) {
|
||||
OutcomePayoutConstant(left, right)
|
||||
} else {
|
||||
OutcomePayoutLine(left, right)
|
||||
}
|
||||
case Vector(left: OutcomePayoutEndpoint,
|
||||
mid: OutcomePayoutMidpoint,
|
||||
right: OutcomePayoutEndpoint) =>
|
||||
case Vector(left: OutcomePayoutPoint,
|
||||
mid: OutcomePayoutPoint,
|
||||
right: OutcomePayoutPoint) =>
|
||||
OutcomePayoutQuadratic(left, mid, right)
|
||||
case Vector(left: OutcomePayoutEndpoint,
|
||||
mid1: OutcomePayoutMidpoint,
|
||||
mid2: OutcomePayoutMidpoint,
|
||||
right: OutcomePayoutEndpoint) =>
|
||||
case Vector(left: OutcomePayoutPoint,
|
||||
mid1: OutcomePayoutPoint,
|
||||
mid2: OutcomePayoutPoint,
|
||||
right: OutcomePayoutPoint) =>
|
||||
OutcomePayoutCubic(left, mid1, mid2, right)
|
||||
case _ => OutcomePayoutPolynomial(points)
|
||||
}
|
||||
}
|
||||
|
||||
def fromTLV(
|
||||
leftEndpoint: TLVPoint,
|
||||
curvePiece: PolynomialPayoutCurvePieceTLV,
|
||||
rightEndpoint: TLVPoint): DLCPolynomialPayoutCurvePiece = {
|
||||
val tlvPoints = curvePiece.midpoints.+:(leftEndpoint).:+(rightEndpoint)
|
||||
val points = tlvPoints.map(OutcomePayoutPoint.fromTLVPoint)
|
||||
|
||||
DLCPolynomialPayoutCurvePiece(points)
|
||||
}
|
||||
}
|
||||
|
||||
case class OutcomePayoutConstant(
|
||||
leftEndpoint: OutcomePayoutEndpoint,
|
||||
rightEndpoint: OutcomePayoutEndpoint)
|
||||
extends DLCPayoutCurvePiece {
|
||||
leftEndpoint: OutcomePayoutPoint,
|
||||
rightEndpoint: OutcomePayoutPoint)
|
||||
extends DLCPolynomialPayoutCurvePiece {
|
||||
require(leftEndpoint.payout == rightEndpoint.payout,
|
||||
"Constant function must have same values on endpoints")
|
||||
|
||||
override lazy val midpoints: Vector[OutcomePayoutMidpoint] = Vector.empty
|
||||
override lazy val midpoints: Vector[OutcomePayoutPoint] = Vector.empty
|
||||
|
||||
override def apply(outcome: Long): Satoshis =
|
||||
bigDecimalSats(leftEndpoint.payout)
|
||||
@ -265,10 +460,10 @@ case class OutcomePayoutConstant(
|
||||
|
||||
/** A Line between left and right endpoints defining a piece of a larger payout curve */
|
||||
case class OutcomePayoutLine(
|
||||
leftEndpoint: OutcomePayoutEndpoint,
|
||||
rightEndpoint: OutcomePayoutEndpoint)
|
||||
extends DLCPayoutCurvePiece {
|
||||
override lazy val midpoints: Vector[OutcomePayoutMidpoint] = Vector.empty
|
||||
leftEndpoint: OutcomePayoutPoint,
|
||||
rightEndpoint: OutcomePayoutPoint)
|
||||
extends DLCPolynomialPayoutCurvePiece {
|
||||
override lazy val midpoints: Vector[OutcomePayoutPoint] = Vector.empty
|
||||
|
||||
lazy val slope: BigDecimal = {
|
||||
(rightEndpoint.payout - leftEndpoint.payout) / (rightEndpoint.outcome - leftEndpoint.outcome)
|
||||
@ -286,11 +481,11 @@ case class OutcomePayoutLine(
|
||||
* A quadratic equation defines a parabola: https://en.wikipedia.org/wiki/Quadratic_function
|
||||
*/
|
||||
case class OutcomePayoutQuadratic(
|
||||
leftEndpoint: OutcomePayoutEndpoint,
|
||||
midpoint: OutcomePayoutMidpoint,
|
||||
rightEndpoint: OutcomePayoutEndpoint)
|
||||
extends DLCPayoutCurvePiece {
|
||||
override lazy val midpoints: Vector[OutcomePayoutMidpoint] = Vector(midpoint)
|
||||
leftEndpoint: OutcomePayoutPoint,
|
||||
midpoint: OutcomePayoutPoint,
|
||||
rightEndpoint: OutcomePayoutPoint)
|
||||
extends DLCPolynomialPayoutCurvePiece {
|
||||
override lazy val midpoints: Vector[OutcomePayoutPoint] = Vector(midpoint)
|
||||
|
||||
private lazy val (x01, x02, x12) =
|
||||
(leftEndpoint.outcome - midpoint.outcome,
|
||||
@ -318,13 +513,13 @@ case class OutcomePayoutQuadratic(
|
||||
|
||||
/** A cubic between left and right endpoints defining a piece of a larger payout curve */
|
||||
case class OutcomePayoutCubic(
|
||||
leftEndpoint: OutcomePayoutEndpoint,
|
||||
leftMidpoint: OutcomePayoutMidpoint,
|
||||
rightMidpoint: OutcomePayoutMidpoint,
|
||||
rightEndpoint: OutcomePayoutEndpoint)
|
||||
extends DLCPayoutCurvePiece {
|
||||
leftEndpoint: OutcomePayoutPoint,
|
||||
leftMidpoint: OutcomePayoutPoint,
|
||||
rightMidpoint: OutcomePayoutPoint,
|
||||
rightEndpoint: OutcomePayoutPoint)
|
||||
extends DLCPolynomialPayoutCurvePiece {
|
||||
|
||||
override lazy val midpoints: Vector[OutcomePayoutMidpoint] =
|
||||
override lazy val midpoints: Vector[OutcomePayoutPoint] =
|
||||
Vector(leftMidpoint, rightMidpoint)
|
||||
|
||||
private lazy val (x01, x02, x03, x12, x13, x23) =
|
||||
@ -363,21 +558,18 @@ case class OutcomePayoutCubic(
|
||||
}
|
||||
|
||||
/** A polynomial interpolating points and defining a piece of a larger payout curve */
|
||||
case class OutcomePayoutPolynomial(points: Vector[OutcomePayoutPoint])
|
||||
extends DLCPayoutCurvePiece {
|
||||
require(points.head.isEndpoint && points.last.isEndpoint,
|
||||
s"First and last points must be endpoints, $points")
|
||||
require(points.tail.init.forall(!_.isEndpoint),
|
||||
s"Endpoint detected in middle, $points")
|
||||
case class OutcomePayoutPolynomial(
|
||||
override val points: Vector[OutcomePayoutPoint])
|
||||
extends DLCPolynomialPayoutCurvePiece {
|
||||
|
||||
override lazy val leftEndpoint: OutcomePayoutEndpoint =
|
||||
points.head.asInstanceOf[OutcomePayoutEndpoint]
|
||||
override lazy val leftEndpoint: OutcomePayoutPoint =
|
||||
points.head
|
||||
|
||||
override lazy val rightEndpoint: OutcomePayoutEndpoint =
|
||||
points.last.asInstanceOf[OutcomePayoutEndpoint]
|
||||
override lazy val rightEndpoint: OutcomePayoutPoint =
|
||||
points.last
|
||||
|
||||
override lazy val midpoints: Vector[OutcomePayoutMidpoint] =
|
||||
points.tail.init.asInstanceOf[Vector[OutcomePayoutMidpoint]]
|
||||
override lazy val midpoints: Vector[OutcomePayoutPoint] =
|
||||
points.tail.init
|
||||
|
||||
lazy val coefficients: Vector[BigDecimal] = {
|
||||
points.map { point =>
|
||||
|
@ -0,0 +1,23 @@
|
||||
package org.bitcoins.core.protocol.tlv
|
||||
|
||||
/** We have various binary serializations in our codebase currently.
|
||||
* This is a product of trying to release a DLC wallet before the
|
||||
* spec was finalized. Some of the binary level serialization for DLCs
|
||||
* has changed since we initiallly deployed wallets.
|
||||
*/
|
||||
sealed trait DLCSerializationVersion
|
||||
|
||||
object DLCSerializationVersion {
|
||||
|
||||
/** This format existed in our wallet before we merged support for this PR
|
||||
* on the DLC spec repo. See the diff below
|
||||
* @see [[https://github.com/discreetlogcontracts/dlcspecs/pull/144]]
|
||||
*/
|
||||
case object PrePR144 extends DLCSerializationVersion
|
||||
|
||||
/** This represents binary serialization for the case where we have
|
||||
* included support for 144, but not included support for 163 yet
|
||||
* @see [[https://github.com/discreetlogcontracts/dlcspecs/pull/144]]
|
||||
*/
|
||||
case object Post144Pre163 extends DLCSerializationVersion
|
||||
}
|
@ -43,7 +43,7 @@ object LnMessage extends Factory[LnMessage[TLV]] {
|
||||
throw new IllegalArgumentException(s"Parsed unknown TLV $unknown")
|
||||
case _: DLCSetupTLV | _: DLCSetupPieceTLV | _: InitTLV | _: DLCOracleTLV |
|
||||
_: ErrorTLV | _: PingTLV | _: PongTLV | _: ContractInfoV0TLV |
|
||||
_: ContractInfoV1TLV =>
|
||||
_: ContractInfoV1TLV | _: PayoutCurvePieceTLV =>
|
||||
()
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,11 @@ import org.bitcoins.core.currency.Satoshis
|
||||
import org.bitcoins.core.number._
|
||||
import org.bitcoins.core.protocol.dlc.compute.SigningVersion
|
||||
import org.bitcoins.core.protocol.dlc.compute.SigningVersion.DLCOracleV0SigningVersion
|
||||
import org.bitcoins.core.protocol.dlc.models.{
|
||||
DLCPayoutCurve,
|
||||
OutcomePayoutPoint,
|
||||
PiecewisePolynomialEndpoint
|
||||
}
|
||||
import org.bitcoins.core.protocol.script._
|
||||
import org.bitcoins.core.protocol.tlv.TLV.{
|
||||
DecodeTLVResult,
|
||||
@ -20,6 +25,8 @@ import scodec.bits.ByteVector
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.time.Instant
|
||||
import scala.annotation.tailrec
|
||||
import scala.math.BigDecimal.RoundingMode
|
||||
import scala.util.Try
|
||||
|
||||
sealed trait TLV extends NetworkElement with TLVUtil {
|
||||
def tpe: BigSizeUInt
|
||||
@ -146,7 +153,6 @@ object TLV extends TLVParentFactory[TLV] {
|
||||
s"Length specified was $length but not enough bytes in ${bytes.drop(prefixSize)}")
|
||||
|
||||
val value = bytes.drop(prefixSize).take(length.num.toLong)
|
||||
|
||||
DecodeTLVResult(tpe, length, value)
|
||||
}
|
||||
|
||||
@ -169,6 +175,7 @@ object TLV extends TLVParentFactory[TLV] {
|
||||
DLCAcceptTLV,
|
||||
DLCSignTLV
|
||||
) ++ EventDescriptorTLV.allFactories ++
|
||||
PayoutCurvePieceTLV.allFactories ++
|
||||
ContractDescriptorTLV.allFactories ++
|
||||
OracleInfoTLV.allFactories ++
|
||||
ContractInfoTLV.allFactories ++
|
||||
@ -965,7 +972,40 @@ object RoundingIntervalsV0TLV extends TLVFactory[RoundingIntervalsV0TLV] {
|
||||
override val typeName: String = "RoundingIntervalsV0TLV"
|
||||
}
|
||||
|
||||
case class TLVPoint(
|
||||
case class TLVPoint(outcome: Long, value: Satoshis, extraPrecision: Int)
|
||||
extends NetworkElement {
|
||||
|
||||
lazy val bigDecimalPayout: BigDecimal = {
|
||||
value.toLong + (BigDecimal(extraPrecision) / (1 << 16))
|
||||
}
|
||||
|
||||
override def bytes: ByteVector = {
|
||||
BigSizeUInt(outcome).bytes ++
|
||||
BigSizeUInt(value.toLong).bytes ++
|
||||
UInt16(extraPrecision).bytes
|
||||
}
|
||||
|
||||
def outcomePayoutPoint: OutcomePayoutPoint = {
|
||||
OutcomePayoutPoint(outcome, value.toLong)
|
||||
}
|
||||
}
|
||||
|
||||
object TLVPoint extends Factory[TLVPoint] {
|
||||
|
||||
override def fromBytes(bytes: ByteVector): TLVPoint = {
|
||||
|
||||
val outcome = BigSizeUInt(bytes)
|
||||
val value = BigSizeUInt(bytes.drop(outcome.byteSize))
|
||||
val extraPrecision = UInt16(
|
||||
bytes.drop(outcome.byteSize + value.byteSize).take(2)).toInt
|
||||
|
||||
TLVPoint(outcome = outcome.toLong,
|
||||
value = Satoshis(value.toLong),
|
||||
extraPrecision = extraPrecision)
|
||||
}
|
||||
}
|
||||
|
||||
case class OldTLVPoint(
|
||||
outcome: Long,
|
||||
value: Satoshis,
|
||||
extraPrecision: Int,
|
||||
@ -986,9 +1026,9 @@ case class TLVPoint(
|
||||
}
|
||||
}
|
||||
|
||||
object TLVPoint extends Factory[TLVPoint] {
|
||||
object OldTLVPoint extends Factory[OldTLVPoint] {
|
||||
|
||||
override def fromBytes(bytes: ByteVector): TLVPoint = {
|
||||
override def fromBytes(bytes: ByteVector): OldTLVPoint = {
|
||||
val isEndpoint = bytes.head match {
|
||||
case 0 => false
|
||||
case 1 => true
|
||||
@ -1001,20 +1041,192 @@ object TLVPoint extends Factory[TLVPoint] {
|
||||
val value = UInt64(bytes.drop(1 + outcome.byteSize).take(8))
|
||||
val extraPrecision = UInt16(bytes.drop(9 + outcome.byteSize).take(2)).toInt
|
||||
|
||||
TLVPoint(outcome = outcome.toLong,
|
||||
value = Satoshis(value.toLong),
|
||||
extraPrecision = extraPrecision,
|
||||
isEndpoint = isEndpoint)
|
||||
OldTLVPoint(outcome = outcome.toLong,
|
||||
value = Satoshis(value.toLong),
|
||||
extraPrecision = extraPrecision,
|
||||
isEndpoint = isEndpoint)
|
||||
}
|
||||
}
|
||||
|
||||
/** @see https://github.com/discreetlogcontracts/dlcspecs/blob/8ee4bbe816c9881c832b1ce320b9f14c72e3506f/NumericOutcome.md#curve-serialization */
|
||||
case class PayoutFunctionV0TLV(points: Vector[TLVPoint])
|
||||
sealed trait PayoutCurvePieceTLV extends DLCSetupPieceTLV
|
||||
|
||||
object PayoutCurvePieceTLV extends TLVParentFactory[PayoutCurvePieceTLV] {
|
||||
|
||||
override val allFactories: Vector[TLVFactory[PayoutCurvePieceTLV]] =
|
||||
Vector(PolynomialPayoutCurvePieceTLV, HyperbolaPayoutCurvePieceTLV)
|
||||
|
||||
override val typeName: String = "PayoutCurvePieceTLV"
|
||||
}
|
||||
|
||||
case class PolynomialPayoutCurvePieceTLV(midpoints: Vector[TLVPoint])
|
||||
extends PayoutCurvePieceTLV {
|
||||
override val tpe: BigSizeUInt = PolynomialPayoutCurvePieceTLV.tpe
|
||||
|
||||
override val value: ByteVector = {
|
||||
u16PrefixedList(midpoints)
|
||||
}
|
||||
}
|
||||
|
||||
object PolynomialPayoutCurvePieceTLV
|
||||
extends TLVFactory[PolynomialPayoutCurvePieceTLV] {
|
||||
override val tpe: BigSizeUInt = BigSizeUInt(42792)
|
||||
|
||||
override def fromTLVValue(
|
||||
value: ByteVector): PolynomialPayoutCurvePieceTLV = {
|
||||
val iter = ValueIterator(value)
|
||||
|
||||
val points = iter.takeU16PrefixedList(() => iter.take(TLVPoint))
|
||||
|
||||
PolynomialPayoutCurvePieceTLV(points)
|
||||
}
|
||||
|
||||
override val typeName: String = "PolynomialPayoutCurvePieceTLV"
|
||||
}
|
||||
|
||||
case class Signed16PTLVNumber(
|
||||
sign: Boolean,
|
||||
withoutPrecision: Long,
|
||||
extraPrecision: Int)
|
||||
extends NetworkElement {
|
||||
|
||||
lazy val toBigDecimal: BigDecimal = {
|
||||
val absVal = withoutPrecision + (BigDecimal(extraPrecision) / (1 << 16))
|
||||
|
||||
if (sign) absVal else -absVal
|
||||
}
|
||||
|
||||
lazy val signByte: Byte = if (sign) {
|
||||
1.toByte
|
||||
} else {
|
||||
0.toByte
|
||||
}
|
||||
|
||||
override def bytes: ByteVector = {
|
||||
ByteVector(signByte) ++
|
||||
BigSizeUInt(withoutPrecision).bytes ++
|
||||
UInt16(extraPrecision).bytes
|
||||
}
|
||||
}
|
||||
|
||||
object Signed16PTLVNumber extends Factory[Signed16PTLVNumber] {
|
||||
|
||||
override def fromBytes(bytes: ByteVector): Signed16PTLVNumber = {
|
||||
val sign = bytes.head match {
|
||||
case 0 => false
|
||||
case 1 => true
|
||||
case b: Byte =>
|
||||
throw new IllegalArgumentException(
|
||||
s"Did not recognize leading byte: $b")
|
||||
}
|
||||
|
||||
val withoutPrecision = BigSizeUInt(bytes.tail)
|
||||
val extraPrecision = UInt16(
|
||||
bytes.drop(1 + withoutPrecision.byteSize).take(2))
|
||||
|
||||
Signed16PTLVNumber(sign, withoutPrecision.toLong, extraPrecision.toInt)
|
||||
}
|
||||
|
||||
def fromBigDecimal(number: BigDecimal): Signed16PTLVNumber = {
|
||||
val sign = number >= 0
|
||||
val withoutPrecision =
|
||||
number.abs.setScale(0, RoundingMode.FLOOR).toLongExact
|
||||
val extraPrecisionBD = (number.abs - withoutPrecision) * (1 << 16)
|
||||
val extraPrecision =
|
||||
extraPrecisionBD.setScale(0, RoundingMode.FLOOR).toIntExact
|
||||
|
||||
Signed16PTLVNumber(sign, withoutPrecision, extraPrecision)
|
||||
}
|
||||
}
|
||||
|
||||
case class HyperbolaPayoutCurvePieceTLV(
|
||||
usePositivePiece: Boolean,
|
||||
translateOutcome: Signed16PTLVNumber,
|
||||
translatePayout: Signed16PTLVNumber,
|
||||
a: Signed16PTLVNumber,
|
||||
b: Signed16PTLVNumber,
|
||||
c: Signed16PTLVNumber,
|
||||
d: Signed16PTLVNumber)
|
||||
extends PayoutCurvePieceTLV {
|
||||
override val tpe: BigSizeUInt = HyperbolaPayoutCurvePieceTLV.tpe
|
||||
|
||||
override val value: ByteVector = {
|
||||
boolBytes(usePositivePiece) ++
|
||||
translateOutcome.bytes ++
|
||||
translatePayout.bytes ++
|
||||
a.bytes ++
|
||||
b.bytes ++
|
||||
c.bytes ++
|
||||
d.bytes
|
||||
}
|
||||
}
|
||||
|
||||
object HyperbolaPayoutCurvePieceTLV
|
||||
extends TLVFactory[HyperbolaPayoutCurvePieceTLV] {
|
||||
override val tpe: BigSizeUInt = BigSizeUInt(42794)
|
||||
|
||||
override def fromTLVValue(value: ByteVector): HyperbolaPayoutCurvePieceTLV = {
|
||||
val iter = ValueIterator(value)
|
||||
|
||||
val usePositivePiece = iter.takeBoolean()
|
||||
val translateOutcome = iter.take(Signed16PTLVNumber)
|
||||
val translatePayout = iter.take(Signed16PTLVNumber)
|
||||
val a = iter.take(Signed16PTLVNumber)
|
||||
val b = iter.take(Signed16PTLVNumber)
|
||||
val c = iter.take(Signed16PTLVNumber)
|
||||
val d = iter.take(Signed16PTLVNumber)
|
||||
|
||||
HyperbolaPayoutCurvePieceTLV(usePositivePiece,
|
||||
translateOutcome,
|
||||
translatePayout,
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
d)
|
||||
}
|
||||
|
||||
override def typeName: String = "HyperbolaPayoutCurvePieceTLV"
|
||||
}
|
||||
|
||||
case class OldPayoutFunctionV0TLV(points: Vector[OldTLVPoint])
|
||||
extends DLCSetupPieceTLV {
|
||||
override val tpe: BigSizeUInt = PayoutFunctionV0TLV.tpe
|
||||
|
||||
override val value: ByteVector = u16PrefixedList(points)
|
||||
}
|
||||
|
||||
/** @see https://github.com/discreetlogcontracts/dlcspecs/blob/8ee4bbe816c9881c832b1ce320b9f14c72e3506f/NumericOutcome.md#curve-serialization */
|
||||
case class PayoutFunctionV0TLV(
|
||||
endpoints: Vector[TLVPoint],
|
||||
pieces: Vector[PayoutCurvePieceTLV],
|
||||
serializationVersion: DLCSerializationVersion)
|
||||
extends DLCSetupPieceTLV {
|
||||
require(
|
||||
endpoints.length == pieces.length + 1,
|
||||
s"Number of endpoints (${endpoints.length}) does not match number of pieces (${pieces.length}).")
|
||||
|
||||
override val tpe: BigSizeUInt = PayoutFunctionV0TLV.tpe
|
||||
|
||||
override val value: ByteVector = {
|
||||
u16PrefixedList(points)
|
||||
u16PrefixedList[(TLVPoint, PayoutCurvePieceTLV)](
|
||||
endpoints.init.zip(pieces),
|
||||
{ case (leftEndpoint: TLVPoint, piece: PayoutCurvePieceTLV) =>
|
||||
leftEndpoint.bytes ++ piece.bytes
|
||||
}) ++ endpoints.last.bytes
|
||||
}
|
||||
|
||||
def piecewisePolynomialEndpoints: Vector[PiecewisePolynomialEndpoint] = {
|
||||
endpoints.map(e => PiecewisePolynomialEndpoint(e.outcome, e.value))
|
||||
}
|
||||
|
||||
override val byteSize: Long = {
|
||||
serializationVersion match {
|
||||
case DLCSerializationVersion.PrePR144 =>
|
||||
val old = OldPayoutFunctionV0TLV(endpoints.map(p =>
|
||||
OldTLVPoint(p.outcome, p.value, p.extraPrecision, true)))
|
||||
old.byteSize
|
||||
case DLCSerializationVersion.Post144Pre163 =>
|
||||
super.byteSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1022,11 +1234,31 @@ object PayoutFunctionV0TLV extends TLVFactory[PayoutFunctionV0TLV] {
|
||||
override val tpe: BigSizeUInt = BigSizeUInt(42790)
|
||||
|
||||
override def fromTLVValue(value: ByteVector): PayoutFunctionV0TLV = {
|
||||
val t = Try {
|
||||
val iter = ValueIterator(value)
|
||||
|
||||
val endpointsAndPieces = iter.takeU16PrefixedList { () =>
|
||||
val leftEndpoint = iter.take(TLVPoint)
|
||||
val piece = iter.take(PayoutCurvePieceTLV)
|
||||
(leftEndpoint, piece)
|
||||
}
|
||||
val rightEndpoint = iter.take(TLVPoint)
|
||||
val endpoints = endpointsAndPieces.map(_._1).:+(rightEndpoint)
|
||||
val pieces = endpointsAndPieces.map(_._2)
|
||||
|
||||
PayoutFunctionV0TLV(endpoints,
|
||||
pieces,
|
||||
serializationVersion =
|
||||
DLCSerializationVersion.Post144Pre163)
|
||||
}
|
||||
|
||||
t.getOrElse(oldfromTLVValue(value))
|
||||
}
|
||||
|
||||
private def oldfromTLVValue(value: ByteVector): PayoutFunctionV0TLV = {
|
||||
val iter = ValueIterator(value)
|
||||
|
||||
val points = iter.takeU16PrefixedList(() => iter.take(TLVPoint))
|
||||
|
||||
PayoutFunctionV0TLV(points)
|
||||
val points = iter.takeU16PrefixedList(() => iter.take(OldTLVPoint))
|
||||
DLCPayoutCurve.fromPointsPre144(points).toTLV
|
||||
}
|
||||
|
||||
override val typeName: String = "PayoutFunctionV0TLV"
|
||||
@ -1039,11 +1271,25 @@ case class ContractDescriptorV1TLV(
|
||||
extends ContractDescriptorTLV {
|
||||
override val tpe: BigSizeUInt = ContractDescriptorV1TLV.tpe
|
||||
|
||||
val numDigitsU16: UInt16 = UInt16(numDigits)
|
||||
|
||||
override val value: ByteVector = {
|
||||
UInt16(numDigits).bytes ++
|
||||
numDigitsU16.bytes ++
|
||||
payoutFunction.bytes ++
|
||||
roundingIntervals.bytes
|
||||
}
|
||||
|
||||
override val byteSize: Long = {
|
||||
payoutFunction.serializationVersion match {
|
||||
case DLCSerializationVersion.Post144Pre163 => super.byteSize
|
||||
case DLCSerializationVersion.PrePR144 =>
|
||||
val payloadSize =
|
||||
numDigitsU16.byteSize + payoutFunction.byteSize + roundingIntervals.byteSize
|
||||
val total =
|
||||
tpe.byteSize + BigSizeUInt(payloadSize).byteSize + payloadSize
|
||||
total
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object ContractDescriptorV1TLV extends TLVFactory[ContractDescriptorV1TLV] {
|
||||
@ -1239,7 +1485,9 @@ object ContractInfoV0TLV extends TLVFactory[ContractInfoV0TLV] {
|
||||
val iter = ValueIterator(value)
|
||||
|
||||
val totalCollateral = iter.takeSats()
|
||||
|
||||
val contractDescriptor = iter.take(ContractDescriptorTLV)
|
||||
|
||||
val oracleInfo = iter.take(OracleInfoTLV)
|
||||
|
||||
ContractInfoV0TLV(totalCollateral, contractDescriptor, oracleInfo)
|
||||
|
@ -24,6 +24,7 @@ case class ValueIterator(value: ByteVector) {
|
||||
|
||||
def skip(bytes: NetworkElement): Unit = {
|
||||
skip(bytes.byteSize)
|
||||
()
|
||||
}
|
||||
|
||||
def take(numBytes: Int): ByteVector = {
|
||||
|
@ -1,6 +1,6 @@
|
||||
package org.bitcoins.dlc.testgen
|
||||
|
||||
import org.bitcoins.core.number.{UInt16, UInt64}
|
||||
import org.bitcoins.core.number.UInt16
|
||||
import org.bitcoins.core.protocol.BigSizeUInt
|
||||
import org.bitcoins.core.protocol.script.EmptyScriptPubKey
|
||||
import org.bitcoins.core.protocol.tlv._
|
||||
@ -141,20 +141,84 @@ object DLCParsingTestVector extends TestVectorParser[DLCParsingTestVector] {
|
||||
|
||||
def apply(tlv: TLV): DLCParsingTestVector = {
|
||||
tlv match {
|
||||
case PayoutFunctionV0TLV(points) =>
|
||||
case old: OldPayoutFunctionV0TLV =>
|
||||
sys.error(s"Should have old payout function here=$old")
|
||||
case PayoutFunctionV0TLV(endpoints, pieces, _) =>
|
||||
val fields = Vector(
|
||||
"tpe" -> Element(PayoutFunctionV0TLV.tpe),
|
||||
"length" -> Element(tlv.length),
|
||||
"numPoints" -> Element(UInt16(points.length)),
|
||||
"points" -> MultiElement(points.map { point =>
|
||||
"numPieces" -> Element(UInt16(pieces.length)),
|
||||
"endpointsAndPieces" -> MultiElement(
|
||||
endpoints
|
||||
.zip(pieces)
|
||||
.flatMap { case (leftEndpoint, piece) =>
|
||||
Vector(leftEndpoint, piece)
|
||||
}
|
||||
.:+(endpoints.last)
|
||||
.map {
|
||||
case point: TLVPoint =>
|
||||
NamedMultiElement(
|
||||
"outcome" -> Element(BigSizeUInt(point.outcome)),
|
||||
"value" -> Element(BigSizeUInt(point.value.toLong)),
|
||||
"extraPrecision" -> Element(UInt16(point.extraPrecision))
|
||||
)
|
||||
case piece => Element(piece)
|
||||
})
|
||||
)
|
||||
DLCTLVTestVector(tlv, "payout_function_v0", fields)
|
||||
case PolynomialPayoutCurvePieceTLV(midpoints) =>
|
||||
val fields = Vector(
|
||||
"tpe" -> Element(PolynomialPayoutCurvePieceTLV.tpe),
|
||||
"length" -> Element(tlv.length),
|
||||
"numMidpoints" -> Element(UInt16(midpoints.length)),
|
||||
"midpoints" -> MultiElement(midpoints.map { point =>
|
||||
NamedMultiElement(
|
||||
"isEndpoint" -> Element(ByteVector(point.leadingByte)),
|
||||
"outcome" -> Element(BigSizeUInt(point.outcome)),
|
||||
"value" -> Element(UInt64(point.value.toLong))
|
||||
"value" -> Element(BigSizeUInt(point.value.toLong)),
|
||||
"extraPrecision" -> Element(UInt16(point.extraPrecision))
|
||||
)
|
||||
})
|
||||
)
|
||||
DLCTLVTestVector(tlv, "payout_function_v0", fields)
|
||||
DLCTLVTestVector(tlv, "polynomial_payout_curve_piece", fields)
|
||||
case HyperbolaPayoutCurvePieceTLV(usePositivePiece,
|
||||
translateOutcome,
|
||||
translatePayout,
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
d) =>
|
||||
def boolToElement(bool: Boolean): Element = {
|
||||
Element(ByteVector(if (bool) 1.toByte else 0.toByte))
|
||||
}
|
||||
|
||||
val fields = Vector(
|
||||
"tpe" -> Element(HyperbolaPayoutCurvePieceTLV.tpe),
|
||||
"length" -> Element(tlv.length),
|
||||
"usePositivePiece" -> boolToElement(usePositivePiece),
|
||||
"translateOutcomeSign" -> boolToElement(translateOutcome.sign),
|
||||
"translateOutcome" -> Element(
|
||||
BigSizeUInt(translateOutcome.withoutPrecision)),
|
||||
"translateOutcomeExtraPrecision" -> Element(
|
||||
UInt16(translateOutcome.extraPrecision)),
|
||||
"translatePayoutSign" -> boolToElement(translatePayout.sign),
|
||||
"translatePayout" -> Element(
|
||||
BigSizeUInt(translatePayout.withoutPrecision)),
|
||||
"translatePayoutExtraPrecision" -> Element(
|
||||
UInt16(translatePayout.extraPrecision)),
|
||||
"aSign" -> boolToElement(a.sign),
|
||||
"a" -> Element(BigSizeUInt(a.withoutPrecision)),
|
||||
"aExtraPrecision" -> Element(UInt16(a.extraPrecision)),
|
||||
"bSign" -> boolToElement(b.sign),
|
||||
"b" -> Element(BigSizeUInt(b.withoutPrecision)),
|
||||
"bExtraPrecision" -> Element(UInt16(b.extraPrecision)),
|
||||
"cSign" -> boolToElement(c.sign),
|
||||
"c" -> Element(BigSizeUInt(c.withoutPrecision)),
|
||||
"cExtraPrecision" -> Element(UInt16(c.extraPrecision)),
|
||||
"dSign" -> boolToElement(d.sign),
|
||||
"d" -> Element(BigSizeUInt(d.withoutPrecision)),
|
||||
"dExtraPrecision" -> Element(UInt16(d.extraPrecision))
|
||||
)
|
||||
DLCTLVTestVector(tlv, "hyperbola_payout_curve_piece", fields)
|
||||
case RoundingIntervalsV0TLV(intervalStarts) =>
|
||||
val fields = Vector(
|
||||
"tpe" -> Element(RoundingIntervalsV0TLV.tpe),
|
||||
|
@ -28,7 +28,7 @@ import org.bitcoins.core.util.sorted._
|
||||
|
||||
[DLCPayoutCurve.scala](https://github.com/bitcoin-s/bitcoin-s/blob/master/core/src/main/scala/org/bitcoins/core/protocol/dlc/DLCPayoutCurve.scala) provides an interface for serializing and evaluating payout curves for DLCs as specified in the [Payout Curve Specification](https://github.com/discreetlogcontracts/dlcspecs/blob/c4fb12d95a4255eabb873611437d05b740bbeccc/PayoutCurve.md). This file supports arbitrary polynomial interpolation.
|
||||
|
||||
To approximate a payout curve that is not a piecewise polynomial function, one may either propose a new kind of curve to the specification, or use approximation. For example by feeding `DLCPayoutCurve` a list of `OutcomePayoutEndpoint`s, one receives a linear approximation of their payout curve which takes the sampled points and "connects the dots" with straight lines. Alternatively one can use spline interpolation and sample two midpoints of every spline to get a piecewise cubic interpolation.
|
||||
To approximate a payout curve that is not a piecewise polynomial function, one may either propose a new kind of curve to the specification, or use approximation. For example by feeding `DLCPayoutCurve` a list of `PiecewisePolynomialEndpoint`s, one receives a linear approximation of their payout curve which takes the sampled points and "connects the dots" with straight lines. Alternatively one can use spline interpolation and sample two midpoints of every spline to get a piecewise cubic interpolation.
|
||||
|
||||
```scala mdoc:to-string
|
||||
// Constructing a forward contract's payout curve (going long) that looks like this:
|
||||
@ -40,12 +40,12 @@ To approximate a payout curve that is not a piecewise polynomial function, one m
|
||||
// Assume a 15 binary digit oracle
|
||||
val maxVal = (1L << 15) - 1
|
||||
val pts = Vector(
|
||||
OutcomePayoutEndpoint(0, 0),
|
||||
OutcomePayoutEndpoint(1000, 0),
|
||||
OutcomePayoutEndpoint(2000, 1000),
|
||||
OutcomePayoutEndpoint(maxVal, 1000)
|
||||
PiecewisePolynomialEndpoint(0, 0),
|
||||
PiecewisePolynomialEndpoint(1000, 0),
|
||||
PiecewisePolynomialEndpoint(2000, 1000),
|
||||
PiecewisePolynomialEndpoint(maxVal, 1000)
|
||||
)
|
||||
val curve = DLCPayoutCurve(pts)
|
||||
val curve = DLCPayoutCurve.polynomialInterpolate(pts,DLCSerializationVersion.Post144Pre163)
|
||||
|
||||
// Let's evalute the curve's values at varios points
|
||||
curve(500)
|
||||
@ -58,7 +58,7 @@ val roundTo100 = RoundingIntervals(Vector(IntervalStart(0, 100)))
|
||||
curve(1667, roundTo100)
|
||||
|
||||
// Let's take a look at the pieces in this piece-wise polynomial
|
||||
curve.functionComponents
|
||||
curve.pieces
|
||||
|
||||
// And we can even see which component is used on a given outcome
|
||||
val Indexed(line1, _) = curve.componentFor(500)
|
||||
|
@ -789,7 +789,7 @@ trait DLCTest {
|
||||
outcomes: Vector[DLCOutcomeType],
|
||||
outcomeIndex: Long): Vector[Int] = {
|
||||
val points =
|
||||
desc.outcomeValueFunc.points
|
||||
desc.outcomeValueFunc.endpoints
|
||||
val left = points(1).outcome
|
||||
val right = points(2).outcome
|
||||
// Somewhere in the middle third of the interesting values
|
||||
|
@ -5,10 +5,10 @@ import org.bitcoins.core.protocol.dlc.models.{
|
||||
DLCPayoutCurve,
|
||||
EnumContractDescriptor,
|
||||
NumericContractDescriptor,
|
||||
OutcomePayoutEndpoint,
|
||||
PiecewisePolynomialEndpoint,
|
||||
RoundingIntervals
|
||||
}
|
||||
import org.bitcoins.core.protocol.tlv.EnumOutcome
|
||||
import org.bitcoins.core.protocol.tlv.{DLCSerializationVersion, EnumOutcome}
|
||||
import org.bitcoins.core.util.NumberUtil
|
||||
|
||||
object DLCTestUtil {
|
||||
@ -76,13 +76,15 @@ object DLCTestUtil {
|
||||
val (leftVal, rightVal) =
|
||||
if (isGoingLong) (Satoshis.zero, totalCollateral.satoshis)
|
||||
else (totalCollateral.satoshis, Satoshis.zero)
|
||||
val func = DLCPayoutCurve(
|
||||
val func = DLCPayoutCurve.polynomialInterpolate(
|
||||
Vector(
|
||||
OutcomePayoutEndpoint(0, leftVal),
|
||||
OutcomePayoutEndpoint(botCollar + 1, leftVal),
|
||||
OutcomePayoutEndpoint(topCollar, rightVal),
|
||||
OutcomePayoutEndpoint(overMaxValue - 1, rightVal)
|
||||
))
|
||||
PiecewisePolynomialEndpoint(0, leftVal),
|
||||
PiecewisePolynomialEndpoint(botCollar + 1, leftVal),
|
||||
PiecewisePolynomialEndpoint(topCollar, rightVal),
|
||||
PiecewisePolynomialEndpoint(overMaxValue - 1, rightVal)
|
||||
),
|
||||
serializationVersion = DLCSerializationVersion.Post144Pre163
|
||||
)
|
||||
val roundingIntervalsToUse =
|
||||
if (numRounds > 0 && roundingIntervals == RoundingIntervals.noRounding) {
|
||||
val intervalStarts = 0.until(numRounds).toVector.map { num =>
|
||||
|
Loading…
Reference in New Issue
Block a user