mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-26 21:42:48 +01:00
Add invariant and better error message for invalid numeric contract descriptor (#3338)
* Add invariant and better error message for invalid numeric contract descriptor * Better error message * Fix test name
This commit is contained in:
parent
af9bf21058
commit
d7b753a869
5 changed files with 172 additions and 48 deletions
|
@ -18,25 +18,25 @@ import java.nio.file.{Path, Paths}
|
|||
|
||||
object BundleGUI extends WalletGUI with JFXApp3 {
|
||||
|
||||
// Catch unhandled exceptions on FX Application thread
|
||||
Thread
|
||||
.currentThread()
|
||||
.setUncaughtExceptionHandler((_: Thread, ex: Throwable) => {
|
||||
ex.printStackTrace()
|
||||
lazy val _ = new Alert(AlertType.Error) {
|
||||
initOwner(owner)
|
||||
title = "Unhandled exception"
|
||||
headerText = "Exception: " + ex.getClass + ""
|
||||
contentText = Option(ex.getMessage).getOrElse("")
|
||||
}.showAndWait()
|
||||
})
|
||||
|
||||
implicit override lazy val system: ActorSystem = ActorSystem(
|
||||
s"bitcoin-s-gui-${System.currentTimeMillis()}")
|
||||
|
||||
lazy val args = parameters.raw
|
||||
|
||||
override def start(): Unit = {
|
||||
// Catch unhandled exceptions on FX Application thread
|
||||
Thread
|
||||
.currentThread()
|
||||
.setUncaughtExceptionHandler((_: Thread, ex: Throwable) => {
|
||||
ex.printStackTrace()
|
||||
lazy val _ = new Alert(AlertType.Error) {
|
||||
initOwner(owner)
|
||||
title = "Unhandled exception"
|
||||
headerText = "Exception: " + ex.getClass + ""
|
||||
contentText = Option(ex.getMessage).getOrElse("")
|
||||
}.showAndWait()
|
||||
})
|
||||
|
||||
// Set log location
|
||||
val baseConfig: Config = AppConfig
|
||||
.getBaseConfig(DEFAULT_BITCOIN_S_DATADIR)
|
||||
|
|
|
@ -21,19 +21,6 @@ object GUI extends WalletGUI with JFXApp3 {
|
|||
implicit override lazy val system: ActorSystem = ActorSystem(
|
||||
s"bitcoin-s-gui-${System.currentTimeMillis()}")
|
||||
|
||||
// Catch unhandled exceptions on FX Application thread
|
||||
Thread
|
||||
.currentThread()
|
||||
.setUncaughtExceptionHandler((_: Thread, ex: Throwable) => {
|
||||
ex.printStackTrace()
|
||||
lazy val _ = new Alert(AlertType.Error) {
|
||||
initOwner(owner)
|
||||
title = "Unhandled exception"
|
||||
headerText = "Exception: " + ex.getClass + ""
|
||||
contentText = Option(ex.getMessage).getOrElse("")
|
||||
}.showAndWait()
|
||||
})
|
||||
|
||||
override lazy val glassPane: VBox = new VBox {
|
||||
children = new ProgressIndicator {
|
||||
progress = ProgressIndicator.IndeterminateProgress
|
||||
|
@ -44,6 +31,19 @@ object GUI extends WalletGUI with JFXApp3 {
|
|||
}
|
||||
|
||||
override def start(): Unit = {
|
||||
// Catch unhandled exceptions on FX Application thread
|
||||
Thread
|
||||
.currentThread()
|
||||
.setUncaughtExceptionHandler((_: Thread, ex: Throwable) => {
|
||||
ex.printStackTrace()
|
||||
lazy val _ = new Alert(AlertType.Error) {
|
||||
initOwner(owner)
|
||||
title = "Unhandled exception"
|
||||
headerText = "Exception: " + ex.getClass + ""
|
||||
contentText = Option(ex.getMessage).getOrElse("")
|
||||
}.showAndWait()
|
||||
})
|
||||
|
||||
lazy val argsWithIndex: Vector[(String, Int)] =
|
||||
parameters.raw.zipWithIndex.toVector
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import org.bitcoins.gui.dlc.dialog.CreateDLCOfferDialog.getNumericContractInfo
|
|||
import org.bitcoins.gui.util.GUIUtil._
|
||||
import scalafx.Includes._
|
||||
import scalafx.geometry.{Insets, Pos}
|
||||
import scalafx.scene.control.Alert.AlertType
|
||||
import scalafx.scene.control._
|
||||
import scalafx.scene.layout._
|
||||
import scalafx.stage.Window
|
||||
|
@ -250,19 +251,17 @@ class CreateDLCOfferDialog extends Logging {
|
|||
|
||||
val previewGraphButton: Button = new Button("Preview Graph") {
|
||||
onAction = _ => {
|
||||
getNumericContractInfo(
|
||||
val (totalCollateral, descriptor) = getNumericContractInfo(
|
||||
decompOpt,
|
||||
pointMap.toVector.sortBy(_._1).map(_._2),
|
||||
roundingMap.toVector.sortBy(_._1).map(_._2)) match {
|
||||
case Failure(_) => ()
|
||||
case Success((totalCollateral, descriptor)) =>
|
||||
DLCPlotUtil.plotCETsWithOriginalCurve(base = 2,
|
||||
descriptor.numDigits,
|
||||
descriptor.outcomeValueFunc,
|
||||
totalCollateral,
|
||||
descriptor.roundingIntervals)
|
||||
()
|
||||
}
|
||||
roundingMap.toVector.sortBy(_._1).map(_._2))
|
||||
|
||||
DLCPlotUtil.plotCETsWithOriginalCurve(base = 2,
|
||||
descriptor.numDigits,
|
||||
descriptor.outcomeValueFunc,
|
||||
totalCollateral,
|
||||
descriptor.roundingIntervals)
|
||||
()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -505,14 +504,12 @@ class CreateDLCOfferDialog extends Logging {
|
|||
|
||||
ContractInfo(descriptor, oracleInfo).toTLV
|
||||
case oracleInfo: NumericOracleInfo =>
|
||||
getNumericContractInfo(
|
||||
val (totalCol, numeric) = getNumericContractInfo(
|
||||
decompOpt,
|
||||
pointMap.toVector.sortBy(_._1).map(_._2),
|
||||
roundingMap.toVector.sortBy(_._1).map(_._2)) match {
|
||||
case Failure(exception) => throw exception
|
||||
case Success((totalCol, numeric)) =>
|
||||
ContractInfo(totalCol, numeric, oracleInfo).toTLV
|
||||
}
|
||||
roundingMap.toVector.sortBy(_._1).map(_._2))
|
||||
|
||||
ContractInfo(totalCol, numeric, oracleInfo).toTLV
|
||||
}
|
||||
|
||||
Some(
|
||||
|
@ -539,11 +536,12 @@ object CreateDLCOfferDialog {
|
|||
def getNumericContractInfo(
|
||||
decompOpt: Option[DigitDecompositionEventDescriptorV0TLV],
|
||||
pointVec: Vector[(TextField, TextField, CheckBox)],
|
||||
roundingVec: Vector[(TextField, TextField)]): Try[
|
||||
(Satoshis, NumericContractDescriptor)] = {
|
||||
roundingVec: Vector[(TextField, TextField)]): (
|
||||
Satoshis,
|
||||
NumericContractDescriptor) = {
|
||||
decompOpt match {
|
||||
case Some(decomp) =>
|
||||
Try {
|
||||
val contractInfoT = Try {
|
||||
val numDigits = decomp.numDigits.toInt
|
||||
|
||||
val outcomesValuePoints = pointVec.flatMap {
|
||||
|
@ -585,8 +583,23 @@ object CreateDLCOfferDialog {
|
|||
numDigits,
|
||||
RoundingIntervals(roundingIntervalsStarts)))
|
||||
}
|
||||
case None => Failure(new RuntimeException("No announcement"))
|
||||
contractInfoT match {
|
||||
case Success(contractInfo) => contractInfo
|
||||
case Failure(err) =>
|
||||
// Do some basic processing of the message so it's prettier
|
||||
val errorMsg = err.getMessage
|
||||
.replace("requirement failed: ", "")
|
||||
.replace(". You must define", ".\nYou must define")
|
||||
|
||||
new Alert(AlertType.Error) {
|
||||
initOwner(owner)
|
||||
title = "Error construction Contract Info"
|
||||
headerText = errorMsg
|
||||
dialogPane().stylesheets = GlobalData.currentStyleSheets
|
||||
}.showAndWait()
|
||||
throw err
|
||||
}
|
||||
case None => throw new RuntimeException("No announcement")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
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.testkitcore.util.BitcoinSUnitTest
|
||||
|
||||
class ContractDescriptorTest extends BitcoinSUnitTest {
|
||||
|
||||
behavior of "ContractDescriptor"
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
it should "fail for starting below the minimum" in {
|
||||
val func = DLCPayoutCurve(
|
||||
Vector(
|
||||
OutcomePayoutPoint(-1, Satoshis(0), isEndpoint = true),
|
||||
OutcomePayoutPoint(3, Satoshis(100), isEndpoint = true)
|
||||
))
|
||||
assertThrows[IllegalArgumentException](
|
||||
NumericContractDescriptor(func, 2, RoundingIntervals.noRounding))
|
||||
}
|
||||
|
||||
it should "fail for starting above the minimum" in {
|
||||
val func = DLCPayoutCurve(
|
||||
Vector(
|
||||
OutcomePayoutPoint(1, Satoshis(0), isEndpoint = true),
|
||||
OutcomePayoutPoint(3, Satoshis(100), isEndpoint = true)
|
||||
))
|
||||
assertThrows[IllegalArgumentException](
|
||||
NumericContractDescriptor(func, 2, RoundingIntervals.noRounding))
|
||||
}
|
||||
|
||||
it should "fail for ending below the maximum" in {
|
||||
val func = DLCPayoutCurve(
|
||||
Vector(
|
||||
OutcomePayoutPoint(0, Satoshis(0), isEndpoint = true),
|
||||
OutcomePayoutPoint(2, Satoshis(100), isEndpoint = true)
|
||||
))
|
||||
assertThrows[IllegalArgumentException](
|
||||
NumericContractDescriptor(func, 2, RoundingIntervals.noRounding))
|
||||
}
|
||||
|
||||
it should "fail for ending above the maximum" in {
|
||||
val func = DLCPayoutCurve(
|
||||
Vector(
|
||||
OutcomePayoutPoint(0, Satoshis(0), isEndpoint = true),
|
||||
OutcomePayoutPoint(4, Satoshis(100), isEndpoint = true)
|
||||
))
|
||||
assertThrows[IllegalArgumentException](
|
||||
NumericContractDescriptor(func, 2, RoundingIntervals.noRounding))
|
||||
}
|
||||
|
||||
it should "correctly create a NumericContractDescriptor" in {
|
||||
val func = DLCPayoutCurve(
|
||||
Vector(
|
||||
OutcomePayoutPoint(0, Satoshis(0), isEndpoint = true),
|
||||
OutcomePayoutPoint(3, Satoshis(100), isEndpoint = true)
|
||||
))
|
||||
|
||||
val descriptor =
|
||||
NumericContractDescriptor(func, 2, RoundingIntervals.noRounding)
|
||||
|
||||
assert(descriptor.toTLV == ContractDescriptorV1TLV(
|
||||
"fda720260002fda7261a0002010000000000000000000000010300000000000000640000fda724020000"))
|
||||
}
|
||||
}
|
|
@ -52,6 +52,8 @@ case class EnumContractDescriptor(
|
|||
with TLVSerializable[ContractDescriptorV0TLV]
|
||||
with SeqWrapper[(EnumOutcome, Satoshis)] {
|
||||
|
||||
require(outcomeValueMap.nonEmpty, "Cannot give an empty set of outcomes")
|
||||
|
||||
override def wrapped: Vector[(EnumOutcome, Satoshis)] = outcomeValueMap
|
||||
|
||||
def keys: Vector[EnumOutcome] = outcomeValueMap.map(_._1)
|
||||
|
@ -97,6 +99,26 @@ case class NumericContractDescriptor(
|
|||
extends ContractDescriptor
|
||||
with TLVSerializable[ContractDescriptorV1TLV] {
|
||||
|
||||
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}. " +
|
||||
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}. " +
|
||||
s"You must define the payout curve from $minValue - $maxValue"
|
||||
)
|
||||
|
||||
override def flip(totalCollateral: Satoshis): NumericContractDescriptor = {
|
||||
|
||||
val flippedFunc = DLCPayoutCurve(outcomeValueFunc.points.map { point =>
|
||||
|
|
Loading…
Add table
Reference in a new issue