mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-07-28 17:53:23 +00:00
fix(amm): address Copilot review — exact BigInt min_out, fail on oversized pool fee, sync flake run docs
This commit is contained in:
parent
9b7a3dcaac
commit
fe41baf210
@ -18,8 +18,10 @@
|
|||||||
# fails flake evaluation because the app directory is copied into the Nix
|
# fails flake evaluation because the app directory is copied into the Nix
|
||||||
# store as its own flake root, so `../..` can't escape it there. Instead,
|
# store as its own flake root, so `../..` can't escape it there. Instead,
|
||||||
# the repo-root flake.nix builds this module directly (src = ./apps/amm)
|
# the repo-root flake.nix builds this module directly (src = ./apps/amm)
|
||||||
# and resolves amm_client_ffi via `self`. Build/run the app from the repo
|
# and resolves amm_client_ffi via `self`. The repo-root flake exposes the UI
|
||||||
# root, e.g. `nix build .#packages.<system>.default` or `nix run .`.
|
# as a named attribute (there is no bare `default`): run it from the repo root
|
||||||
|
# with `nix run .#amm-ui`, and build just the FFI crate with
|
||||||
|
# `nix build .#amm_client_ffi`.
|
||||||
outputs = inputs@{ logos-module-builder, ... }:
|
outputs = inputs@{ logos-module-builder, ... }:
|
||||||
logos-module-builder.lib.mkLogosQmlModule {
|
logos-module-builder.lib.mkLogosQmlModule {
|
||||||
src = ./.;
|
src = ./.;
|
||||||
|
|||||||
@ -239,7 +239,15 @@ Rectangle {
|
|||||||
root.swapInProgress = true
|
root.swapInProgress = true
|
||||||
root.swapError = ""
|
root.swapError = ""
|
||||||
|
|
||||||
var minOutStr = root.formatBaseUnits(root.minReceivedAmount)
|
// Compute the submitted slippage floor with exact integer (BigInt) math
|
||||||
|
// rather than the double-based preview: base-unit values for 18-decimal
|
||||||
|
// tokens exceed 2^53, where doubles would understate min_out and weaken
|
||||||
|
// price protection. Sell/buy reserves follow the pool's canonical order.
|
||||||
|
var minOutStr = swapState.minOutBaseUnits(
|
||||||
|
root.sellInput,
|
||||||
|
root.sellIsPoolA ? root.poolReserveA : root.poolReserveB,
|
||||||
|
root.sellIsPoolA ? root.poolReserveB : root.poolReserveA,
|
||||||
|
root.slippageTolerancePercent)
|
||||||
// Max u64 sentinel: "ignore deadline", per AmmUiBackend.rep.
|
// Max u64 sentinel: "ignore deadline", per AmmUiBackend.rep.
|
||||||
var deadline = "18446744073709551615"
|
var deadline = "18446744073709551615"
|
||||||
|
|
||||||
|
|||||||
@ -73,6 +73,44 @@ QtObject {
|
|||||||
return safeAmount * (1 - safeSlippage / 100);
|
return safeAmount * (1 - safeSlippage / 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Exact-integer minimum-received (base units) for a SwapExactInput, used as
|
||||||
|
// the on-chain slippage floor that is actually submitted. Computed in BigInt
|
||||||
|
// (arbitrary precision, mirroring the on-chain u256 math): base units for
|
||||||
|
// 18-decimal tokens exceed 2^53 and even overflow u128 intermediates, so JS
|
||||||
|
// doubles silently lose precision and would understate min_out — weakening
|
||||||
|
// the user's price protection. amountIn/reserveIn/reserveOut are base-unit
|
||||||
|
// integer strings; returns a decimal string. Falls back to the double
|
||||||
|
// estimate only if BigInt is unavailable in this Qt build.
|
||||||
|
function minOutBaseUnits(amountIn, reserveIn, reserveOut, slippagePercent) {
|
||||||
|
if (typeof BigInt !== "undefined") {
|
||||||
|
var toBig = function (x) {
|
||||||
|
var s = String(x).trim();
|
||||||
|
return /^[0-9]+$/.test(s) ? BigInt(s) : BigInt(0);
|
||||||
|
};
|
||||||
|
var zero = BigInt(0);
|
||||||
|
var denom = BigInt(10000);
|
||||||
|
var amtIn = toBig(amountIn);
|
||||||
|
var resIn = toBig(reserveIn);
|
||||||
|
var resOut = toBig(reserveOut);
|
||||||
|
if (amtIn <= zero || resIn <= zero || resOut <= zero)
|
||||||
|
return "0";
|
||||||
|
|
||||||
|
var feeBps = BigInt(Math.round(Math.min(10000, Math.max(0, Number(root.feeBps) || 0))));
|
||||||
|
var amtInAfterFee = amtIn * (denom - feeBps) / denom; // floor
|
||||||
|
if (amtInAfterFee <= zero)
|
||||||
|
return "0";
|
||||||
|
var out = resOut * amtInAfterFee / (resIn + amtInAfterFee); // floor
|
||||||
|
var slipBps = BigInt(Math.round(clampSlippagePercent(slippagePercent) * 100));
|
||||||
|
if (slipBps < zero) slipBps = zero;
|
||||||
|
if (slipBps > denom) slipBps = denom;
|
||||||
|
var minOut = out * (denom - slipBps) / denom; // floor
|
||||||
|
return minOut.toString();
|
||||||
|
}
|
||||||
|
// Legacy double fallback (no worse than before if BigInt is missing).
|
||||||
|
var estOut = amountOutFor(amountIn, reserveIn, reserveOut);
|
||||||
|
return String(Math.floor(Math.max(0, minReceived(estOut, slippagePercent))));
|
||||||
|
}
|
||||||
|
|
||||||
function maxSent(amountIn, slippagePercent) {
|
function maxSent(amountIn, slippagePercent) {
|
||||||
const safeAmount = parseAmount(amountIn);
|
const safeAmount = parseAmount(amountIn);
|
||||||
const safeSlippage = clampSlippagePercent(slippagePercent);
|
const safeSlippage = clampSlippagePercent(slippagePercent);
|
||||||
|
|||||||
@ -27,7 +27,8 @@ pub fn decode_pool(bytes: &[u8]) -> Result<PoolView, String> {
|
|||||||
reserve_a: p.reserve_a,
|
reserve_a: p.reserve_a,
|
||||||
reserve_b: p.reserve_b,
|
reserve_b: p.reserve_b,
|
||||||
liquidity_supply: p.liquidity_pool_supply,
|
liquidity_supply: p.liquidity_pool_supply,
|
||||||
fees: u32::try_from(p.fees).unwrap_or(u32::MAX),
|
fees: u32::try_from(p.fees)
|
||||||
|
.map_err(|_| format!("pool fee {} exceeds u32::MAX", p.fees))?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user