Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix various conversion functions from string to Decimal (#439)
* fix various conversion functions from string to Decimal Fixes #399. This fixes a critical bug -- since: * The DAML-LF spec specifies that the scale of `Decimal` is 10 -- that is, there are at most 10 digits past the decimal point: <https://github.com/digital-asset/daml/blob/79bbf5c794530ed7e29e61fcc2d98221cdb221e3/daml-lf/spec/value.rst#field-decimal>. * However, the code converting from the string representation that we get on the wire was _not_ performing this check. This is due to two reasons: - `Decimal.check` is a function that checks whether a given `Decimal` is within the upper and lower bounds of what the DAML-LF spec specifies, but crucially it does _not_ check that the scale is not exceeded: <https://github.com/digital-asset/daml/blob/79bbf5c794530ed7e29e61fcc2d98221cdb221e3/daml-lf/data/src/main/scala/com/digitalasset/daml/lf/data/Decimal.scala#L31>. This behavior is correct in some cases (more on that later), but not in others. Crucially, `Decimal.fromString`, which was supposed to check if a decimal string literal is valid, used this function, which means that it accepted string literals containing numbers out of the required scale, rounding them to make them fit within the scale. This function has been renamed to `Decimal.checkWithinBoundsAndRound`, and a new function `Decimal.checkWithinBoundsAndWithinScale` has been added, which fails if the number provided has data not within the scale. `Decimal.fromString` now uses `Decimal.checkWithinBoundsAndWithinScale`. - `ApiToLfEngine.parseDecimal` assumed that `Decimal.fromString` _did_ fail when passed numbers that were in any way invalid, and moreover did _not_ use the result of `Decimal.fromString`, but rather re-parsed the string into an unconstrained `BigDecimal`: <https://github.com/digital-asset/daml/blob/79bbf5c794530ed7e29e61fcc2d98221cdb221e3/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/participant/util/ApiToLfEngine.scala#L96>. The reason for the code to work this way is that in the past it was responsible for converting decimal strings both for the current engine but also for an older version of the engine which handled decimals of a different type. Both issues have been fixed. * Therefore, `Decimal`s with scale exceeding the specified scale made it into the engine, and contracts could be created containing this invalid value. * Once on the ledger, these bad numbers can be used to produce extremely surprising results, due to how `Decimal` operations are implemented. Generally speaking, all operations on `Decimal` first compute the result and then run the output through `Decimal.checkWithinBoundsAndRound`. The reason for this behavior is that we specify multiplication and division as rounding their output. Consider the case where the bad value 0.00000000005 made it to the engine, and is then added to 100. The full-precision result will be 100.00000000005, which after rounding becomes 100. Therefore, on a ledger where such invalid values exist, it is not the case that `y > 0 ==> x + y != x`, and so on. Thanks a bunch to @briandbecker for the excellent bug report. * fix failing test using to much precision
- Loading branch information
1 parent
a3ff379
commit 6af8405
Showing
9 changed files
with
86 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters