diff --git a/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/pegged-swap-args.test.ts b/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/pegged-swap-args.test.ts index 2997bcd..d03b75f 100644 --- a/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/pegged-swap-args.test.ts +++ b/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/pegged-swap-args.test.ts @@ -31,7 +31,7 @@ describe('PeggedSwapArgs', () => { it('should handle maximum uint256 values for reserves and rates', () => { const maxUint256 = (1n << 256n) - 1n - const maxLinearWidth = 2n * 10n ** 27n + const maxLinearWidth = 5000n * 10n ** 27n const args = new PeggedSwapArgs(maxUint256, maxUint256, maxLinearWidth, maxUint256, maxUint256) const encoded = PeggedSwapArgs.CODER.encode(args) @@ -54,8 +54,8 @@ describe('PeggedSwapArgs', () => { expect(() => new PeggedSwapArgs(1n, 1n, 1n, 1n, 0n)).toThrow(/Invalid rateGt/) }) - it('should throw when linearWidth exceeds 2e27', () => { - const tooLarge = 2n * 10n ** 27n + 1n + it('should throw when linearWidth exceeds 5000e27', () => { + const tooLarge = 5000n * 10n ** 27n + 1n expect(() => new PeggedSwapArgs(1n, 1n, tooLarge, 1n, 1n)).toThrow(/Invalid linearWidth/) }) diff --git a/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/pegged-swap-args.ts b/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/pegged-swap-args.ts index 4667899..b00831c 100644 --- a/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/pegged-swap-args.ts +++ b/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/pegged-swap-args.ts @@ -4,6 +4,7 @@ import type { HexString } from '@1inch/sdk-core' import { UINT_256_MAX } from '@1inch/byte-utils' import assert from 'assert' import { PeggedSwapArgsCoder } from './pegged-swap-args-coder' +import { MAX_LINEAR_WIDTH } from './pegged-swap-math/pegged-swap-math' import type { PeggedTokenInfo } from './types' import { resolveRate } from './rate-resolver' import type { IArgsCoder, IArgsData } from '../types' @@ -19,7 +20,7 @@ export class PeggedSwapArgs implements IArgsData { /** * x0 - Initial X reserve (normalization factor) = initial_balance_X * rateLt (or rateGt) * y0 - Initial Y reserve (normalization factor) = initial_balance_Y * rateGt (or rateLt) - * linearWidth - Linear component coefficient A scaled by 1e27 (e.g., 0.8e27 for A=0.8) + * linearWidth - Linear component coefficient A scaled by 1e27 (e.g., 0.8e27 for A=0.8); must be ≤ MAX_LINEAR_WIDTH (A ≤ 5000) * rateLt - Rate multiplier for token with LOWER address * rateGt - Rate multiplier for token with GREATER address * > For equal decimals (e.g., both 18): rateLt = rateGt = 1 @@ -35,7 +36,7 @@ export class PeggedSwapArgs implements IArgsData { assert(x0 > 0n && y0 > 0n, 'Reserves cannot be zero') assert(x0 <= UINT_256_MAX, `Invalid x0: ${x0}`) assert(y0 <= UINT_256_MAX, `Invalid y0: ${y0}`) - assert(linearWidth <= 2n * 10n ** 27n, `Invalid linearWidth: ${linearWidth}`) + assert(linearWidth <= MAX_LINEAR_WIDTH, `Invalid linearWidth: ${linearWidth}`) assert( rateLt > 0n && rateLt <= UINT_256_MAX, `Invalid rateLt: ${rateLt}. Must be positive and <= UINT_256_MAX`, diff --git a/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/pegged-swap-math/pegged-swap-math.test.ts b/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/pegged-swap-math/pegged-swap-math.test.ts index 654abe2..3a7bf7e 100644 --- a/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/pegged-swap-math/pegged-swap-math.test.ts +++ b/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/pegged-swap-math/pegged-swap-math.test.ts @@ -25,8 +25,13 @@ describe('linearWidthFromSymmetricRangePercent', () => { expect(linearWidth).toBe(1460784313725490196078431372n) }) - it('allows linearWidth at the on-chain maximum (20% symmetric range)', () => { - expect(linearWidthFromSymmetricRangePercent(20)).toBe(MAX_LINEAR_WIDTH) + it('maps a 20% symmetric range to A = 2', () => { + expect(linearWidthFromSymmetricRangePercent(20)).toBe(2n * PEGGED_SWAP_ONE) + }) + + it('allows narrow peg bands up to the on-chain maximum (A ≈ 5000 at the 0.01% floor)', () => { + // X = 0.0001 → A = (1 - X) / (2X) = 4999.5 → 4999.5e27 ≤ MAX_LINEAR_WIDTH (5000e27). + expect(linearWidthFromSymmetricRangePercent(0.01)).toBe(49995n * 10n ** 26n) }) it('rejects zero and 100% symmetric range', () => { @@ -34,8 +39,8 @@ describe('linearWidthFromSymmetricRangePercent', () => { expect(() => linearWidthFromSymmetricRangePercent(100)).toThrow(/less than 100%/) }) - it('rejects narrow peg bands whose A exceeds 2', () => { - expect(() => linearWidthFromSymmetricRangePercent(0.2)).toThrow(/exceeds maximum/) + it('rejects peg bands tighter than the A ≤ 5000 floor', () => { + expect(() => linearWidthFromSymmetricRangePercent(0.001)).toThrow(/exceeds maximum/) }) }) @@ -44,8 +49,12 @@ describe('symmetricRangePercentFromLinearWidth', () => { expect(symmetricRangePercentFromLinearWidth(15n * 10n ** 26n)).toBe(25) }) - it('returns 20% at the on-chain maximum linearWidth', () => { - expect(symmetricRangePercentFromLinearWidth(MAX_LINEAR_WIDTH)).toBe(20) + it('returns 20% at A = 2', () => { + expect(symmetricRangePercentFromLinearWidth(2n * PEGGED_SWAP_ONE)).toBe(20) + }) + + it('returns ≈0.009999% at the on-chain maximum linearWidth (A = 5000)', () => { + expect(symmetricRangePercentFromLinearWidth(MAX_LINEAR_WIDTH)).toBeCloseTo(100 / 10001, 6) }) it('returns 100% when linearWidth is zero', () => { @@ -63,7 +72,7 @@ describe('symmetricRangePercentFromLinearWidth', () => { }) it('round-trips percent -> linearWidth -> percent', () => { - for (const percent of [20, 25, 25.5, 33.33, 50, 99]) { + for (const percent of [0.01, 0.1, 1, 20, 25, 25.5, 33.33, 50, 99]) { const linearWidth = linearWidthFromSymmetricRangePercent(percent) expect(symmetricRangePercentFromLinearWidth(linearWidth)).toBeCloseTo(percent, 10) } @@ -75,7 +84,7 @@ describe('symmetricRangePercentFromLinearWidth', () => { 5n * 10n ** 26n, 10n ** 27n, 15n * 10n ** 26n, - MAX_LINEAR_WIDTH, + 2n * PEGGED_SWAP_ONE, ]) { const percent = symmetricRangePercentFromLinearWidth(linearWidth) const recovered = linearWidthFromSymmetricRangePercent(percent) @@ -86,7 +95,7 @@ describe('symmetricRangePercentFromLinearWidth', () => { }) it('round-trips exactly for clean percents', () => { - for (const percent of [20, 25, 50]) { + for (const percent of [0.01, 0.1, 20, 25, 50]) { const linearWidth = linearWidthFromSymmetricRangePercent(percent) expect(symmetricRangePercentFromLinearWidth(linearWidth)).toBe(percent) } diff --git a/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/pegged-swap-math/pegged-swap-math.ts b/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/pegged-swap-math/pegged-swap-math.ts index db98ad4..2c70e7d 100644 --- a/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/pegged-swap-math/pegged-swap-math.ts +++ b/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/pegged-swap-math/pegged-swap-math.ts @@ -6,8 +6,8 @@ import { bigintSqrt } from '../../utils' /** Matches `PeggedSwapMath.ONE` in swap-vm. */ export const PEGGED_SWAP_ONE: bigint = 10n ** 27n -/** Maximum on-chain `linearWidth` (`A` ≤ 2). */ -export const MAX_LINEAR_WIDTH: bigint = 2n * PEGGED_SWAP_ONE +/** Maximum on-chain `linearWidth` (`A` ≤ 5000). */ +export const MAX_LINEAR_WIDTH: bigint = 5000n * PEGGED_SWAP_ONE const MARGINAL_PRICE_ONE = 10n ** 18n