diff --git a/apps/amm/VALIDATION.md b/apps/amm/VALIDATION.md index af13c1d..8663bfa 100644 --- a/apps/amm/VALIDATION.md +++ b/apps/amm/VALIDATION.md @@ -46,9 +46,6 @@ transaction ID is displayed. scales either deposit from the minimum that mints more than `MINIMUM_LIQUIDITY`. - Active Pool flow keeps the reserve ratio and previews expected LP output. -- Decimal inference keeps small supplies at 0 decimals, medium supplies at 6, - large supplies at 9, and massive supplies at 18. -- Human amount parse/format preserves 0, 6, 9, and 18 decimal values. - `new_definition` and `add_liquidity` account lists match the committed AMM IDL order. - Submit re-quotes and surfaces `quote_changed` when the request no longer diff --git a/apps/amm/qml/components/liquidity/AmountMath.js b/apps/amm/qml/components/liquidity/AmountMath.js index 2779ef6..1130983 100644 --- a/apps/amm/qml/components/liquidity/AmountMath.js +++ b/apps/amm/qml/components/liquidity/AmountMath.js @@ -133,19 +133,6 @@ function pow10(exponent) { return value } -function implyDecimals(totalSupplyRaw) { - if (!isUnsigned(totalSupplyRaw)) - return 0 - var digits = normalize(totalSupplyRaw).length - if (digits <= 9) - return 0 - if (digits <= 14) - return 6 - if (digits <= 21) - return 9 - return 18 -} - function parseHuman(text, decimals) { var value = String(text) if (value.length === 0) @@ -163,17 +150,6 @@ function parseHuman(text, decimals) { return { "ok": true, "code": "", "raw": raw } } -function trimHumanPrecision(text, decimals) { - var value = String(text) - var match = /^(0|[1-9][0-9]*)(?:\.([0-9]*))?$/.exec(value) - if (!match) - return value - var fraction = match[2] || "" - if (fraction.length <= decimals) - return value - return decimals > 0 ? match[1] + "." + fraction.slice(0, decimals) : match[1] -} - function formatRaw(rawValue, decimals) { if (!isUnsigned(rawValue)) return "" diff --git a/apps/amm/qml/components/liquidity/NewPositionForm.qml b/apps/amm/qml/components/liquidity/NewPositionForm.qml index fef642f..c70000f 100644 --- a/apps/amm/qml/components/liquidity/NewPositionForm.qml +++ b/apps/amm/qml/components/liquidity/NewPositionForm.qml @@ -65,8 +65,8 @@ AmmActionCard { ? root.newPositionContext.feeTiers : [] readonly property var tokenA: root.tokenById(root.selectedTokenAId) readonly property var tokenB: root.tokenById(root.selectedTokenBId) - readonly property int decimalsA: AmountMath.implyDecimals(root.tokenA.totalSupplyRaw || "0") - readonly property int decimalsB: AmountMath.implyDecimals(root.tokenB.totalSupplyRaw || "0") + readonly property int decimalsA: 0 + readonly property int decimalsB: 0 readonly property bool displayIsCanonical: root.selectedTokenAId.length > 0 && AmountMath.compareBase58Ids(root.selectedTokenAId, root.selectedTokenBId) > 0 @@ -770,11 +770,9 @@ AmmActionCard { return } - var decimals = AmountMath.implyDecimals(token.totalSupplyRaw || "0") - root.tokenResolutionMessage = qsTr("%1 - supply %2 (%3 implied decimals)") + root.tokenResolutionMessage = qsTr("%1 - raw supply %2") .arg(token.name || root.shortId(token.definitionId)) - .arg(AmountMath.formatRaw(token.totalSupplyRaw || "0", decimals)) - .arg(decimals) + .arg(AmountMath.formatRaw(token.totalSupplyRaw || "0", 0)) root.selectToken(side, token.definitionId) } @@ -1193,7 +1191,7 @@ AmmActionCard { "amount_required": qsTr("Enter a value."), "amount_must_be_positive": qsTr("Value must be greater than zero."), "invalid_amount_format": qsTr("Use plain dot-decimal format."), - "invalid_amount_precision": qsTr("Too many decimal places for this token."), + "invalid_amount_precision": qsTr("Token amounts must use whole raw units."), "invalid_raw_amount": qsTr("Value is outside the supported range."), "amount_exceeds_balance": qsTr("Amount exceeds the selected holding balance."), "amount_too_low": qsTr("Value is too low for this pool."), @@ -1239,7 +1237,6 @@ AmmActionCard { function finishActiveAmount(side, value) { var decimals = side === "A" ? root.decimalsA : root.decimalsB - value = AmountMath.trimHumanPrecision(value, decimals) if (side === "A") root.amountA = value else @@ -1302,7 +1299,6 @@ AmmActionCard { function finishMissingAmount(side, value) { var decimals = side === "A" ? root.decimalsA : root.decimalsB - value = AmountMath.trimHumanPrecision(value, decimals) if (side === "A") root.amountA = value else @@ -1483,8 +1479,7 @@ AmmActionCard { } function tokenBalanceDetail(token) { - var decimals = AmountMath.implyDecimals(token.totalSupplyRaw || "0") - return qsTr("Available %1").arg(root.balanceText(token, decimals)) + return qsTr("Available %1").arg(root.balanceText(token, 0)) } function shortId(value) { diff --git a/apps/amm/tests/qml/tst_NewPositionForm.qml b/apps/amm/tests/qml/tst_NewPositionForm.qml index a8cc48d..083be1f 100644 --- a/apps/amm/tests/qml/tst_NewPositionForm.qml +++ b/apps/amm/tests/qml/tst_NewPositionForm.qml @@ -59,7 +59,7 @@ TestCase { } } - function equalDecimalsContext() { + function rawAmountsContext() { return { "status": "ready", "tokens": [ @@ -118,15 +118,23 @@ TestCase { compare(built.request.tokenBId, tokenLow) } + function test_tokenAmountsUseRawUnits() { + var form = createForm() + compare(form.decimalsA, 0) + compare(form.decimalsB, 0) + compare(form.balanceText(form.tokenA, form.decimalsA), "1000") + compare(form.balanceText(form.tokenB, form.decimalsB), "5000000000") + } + function test_missingPoolKeepsMinimumRatioWhileEditingDeposit() { var quote = { "status": "ok", "tokenAId": tokenHigh, "tokenBId": tokenLow, "poolStatus": "missing_pool", - "minimumAmountARaw": "2000000", + "minimumAmountARaw": "2", "minimumAmountBRaw": "3", - "actualAmountARaw": "2000000", + "actualAmountARaw": "2", "actualAmountBRaw": "3" } var form = createForm() @@ -146,14 +154,14 @@ TestCase { var built = form.buildQuoteRequest() verify(built.ok) - compare(built.request.amountARaw, "4000000") + compare(built.request.amountARaw, "4") compare(built.request.amountBRaw, "6") } function test_missingPoolAcceptsLargeDirectAmountsFromEitherSide() { var form = createTemporaryObject(formComponent, testCase, { "flowState": flowState(({})), - "newPositionContext": equalDecimalsContext() + "newPositionContext": rawAmountsContext() }) verify(form) wait(0) @@ -176,8 +184,8 @@ TestCase { compare(form.amountB, "100") var built = form.buildQuoteRequest() verify(built.ok) - compare(built.request.amountARaw, "100000000") - compare(built.request.amountBRaw, "150000000") + compare(built.request.amountARaw, "100") + compare(built.request.amountBRaw, "150") compare(built.request.initialPriceRealRaw, "27670116110564327424") verify(!built.request.hasOwnProperty("depositScaleBps")) @@ -186,14 +194,14 @@ TestCase { compare(form.amountB, "200") built = form.buildQuoteRequest() verify(built.ok) - compare(built.request.amountARaw, "200000000") - compare(built.request.amountBRaw, "300000000") + compare(built.request.amountARaw, "200") + compare(built.request.amountBRaw, "300") } - function test_missingPoolRoundsPairedAmountAndTrimsInputPrecision() { + function test_missingPoolRoundsPairedRawAmounts() { var form = createTemporaryObject(formComponent, testCase, { "flowState": flowState(({})), - "newPositionContext": equalDecimalsContext() + "newPositionContext": rawAmountsContext() }) verify(form) wait(0) @@ -204,22 +212,18 @@ TestCase { "tokenAId": tokenHigh, "tokenBId": tokenLow, "poolStatus": "missing_pool", - "minimumAmountARaw": "26", - "minimumAmountBRaw": "39", - "actualAmountARaw": "26", - "actualAmountBRaw": "39" + "minimumAmountARaw": "1", + "minimumAmountBRaw": "1", + "actualAmountARaw": "1", + "actualAmountBRaw": "1" }) wait(0) var cases = [ - { "input": "1", "paired": "0.666667", "rawA": "666667", "rawB": "1000000", - "price": "27670102275513189667" }, - { "input": "2", "paired": "1.333333", "rawA": "1333333", "rawB": "2000000", - "price": "27670123028095084447" }, - { "input": "3", "paired": "2", "rawA": "2000000", "rawB": "3000000", - "price": "27670116110564327424" }, - { "input": "4", "paired": "2.666667", "rawA": "2666667", "rawB": "4000000", - "price": "27670112651800245948" } + { "input": "1", "paired": "1", "rawA": "1", "rawB": "1" }, + { "input": "2", "paired": "1", "rawA": "1", "rawB": "2" }, + { "input": "3", "paired": "2", "rawA": "2", "rawB": "3" }, + { "input": "4", "paired": "3", "rawA": "3", "rawB": "4" } ] for (var i = 0; i < cases.length; ++i) { form.finishMissingAmount("A", cases[i].input) @@ -228,13 +232,12 @@ TestCase { verify(built.ok) compare(built.request.amountARaw, cases[i].rawA) compare(built.request.amountBRaw, cases[i].rawB) - compare(built.request.initialPriceRealRaw, cases[i].price) } form.finishMissingAmount("A", "1.1234567") - compare(form.amountA, "1.123456") - compare(form.amountB, "0.748971") - verify(form.buildQuoteRequest().ok) + compare(form.amountA, "1.1234567") + verify(!form.buildQuoteRequest().ok) + compare(form.fieldError("amountA"), form.issueText("invalid_amount_precision")) } function test_missingPoolUsesTradeStyleInputsWithInlinePrice() { @@ -374,9 +377,9 @@ TestCase { "tokenAId": tokenHigh, "tokenBId": tokenLow, "poolStatus": "active_pool", - "reserveARaw": "2000000", + "reserveARaw": "2", "reserveBRaw": "10", - "maxAmountARaw": "4000000", + "maxAmountARaw": "4", "maxAmountBRaw": "20", "errors": [{ "code": "amount_exceeds_balance", @@ -404,9 +407,9 @@ TestCase { "tokenAId": tokenHigh, "tokenBId": tokenLow, "poolStatus": "active_pool", - "reserveARaw": "2000000", + "reserveARaw": "2", "reserveBRaw": "10", - "maxAmountARaw": "4000000", + "maxAmountARaw": "4", "maxAmountBRaw": "20" } var form = createForm() @@ -428,11 +431,12 @@ TestCase { var built = form.buildQuoteRequest() verify(built.ok) - compare(built.request.maxAmountARaw, "1000000") + compare(built.request.maxAmountARaw, "1") compare(built.request.maxAmountBRaw, "5") form.finishActiveAmount("B", "1.1234567") - compare(form.amountB, "1.123456") + compare(form.amountB, "1.1234567") + verify(!form.buildQuoteRequest().ok) } function test_activePoolEditRecoversAfterInvalidQuote() { @@ -443,9 +447,9 @@ TestCase { "tokenBId": tokenLow, "poolStatus": "active_pool", "poolFeeBps": 30, - "reserveARaw": "2000000", + "reserveARaw": "2", "reserveBRaw": "10", - "maxAmountARaw": "4000000", + "maxAmountARaw": "4", "maxAmountBRaw": "20" }) wait(0) @@ -466,7 +470,7 @@ TestCase { var built = form.buildQuoteRequest() verify(built.ok) - compare(built.request.maxAmountARaw, "1000000") + compare(built.request.maxAmountARaw, "1") compare(built.request.maxAmountBRaw, "5") }