diff --git a/typescript/swap-vm/package.json b/typescript/swap-vm/package.json index 2ed473b..760ac92 100644 --- a/typescript/swap-vm/package.json +++ b/typescript/swap-vm/package.json @@ -1,6 +1,6 @@ { "name": "@1inch/swap-vm-sdk", - "version": "0.1.2-rc.7", + "version": "0.1.3", "description": "1inch Swap VM SDK", "author": "@1inch", "license": "LicenseRef-Degensoft-SwapVM-1.1", diff --git a/typescript/swap-vm/src/swap-vm/instructions/concentrate/concentrate-grow-liquidity-2d-args.ts b/typescript/swap-vm/src/swap-vm/instructions/concentrate/concentrate-grow-liquidity-2d-args.ts index bb8826e..b97a390 100644 --- a/typescript/swap-vm/src/swap-vm/instructions/concentrate/concentrate-grow-liquidity-2d-args.ts +++ b/typescript/swap-vm/src/swap-vm/instructions/concentrate/concentrate-grow-liquidity-2d-args.ts @@ -4,7 +4,7 @@ import type { HexString } from '@1inch/sdk-core' import { UINT_256_MAX } from '@1inch/byte-utils' import assert from 'assert' import { ConcentrateGrowLiquidity2DArgsCoder } from './concentrate-grow-liquidity-2d-args-coder' -import { bigintSqrt } from './bigint-sqrt' +import { bigintSqrt } from '../utils/bigint-sqrt' import type { IArgsCoder, IArgsData } from '../types' export const ONE_E18: bigint = 10n ** 18n diff --git a/typescript/swap-vm/src/swap-vm/instructions/concentrate/concentrate-liquidity-math/concentrate-liquidity-math.test.ts b/typescript/swap-vm/src/swap-vm/instructions/concentrate/concentrate-liquidity-math/concentrate-liquidity-math.test.ts index bb36c06..947cf39 100644 --- a/typescript/swap-vm/src/swap-vm/instructions/concentrate/concentrate-liquidity-math/concentrate-liquidity-math.test.ts +++ b/typescript/swap-vm/src/swap-vm/instructions/concentrate/concentrate-liquidity-math/concentrate-liquidity-math.test.ts @@ -9,7 +9,7 @@ import { computeLiquidityFromAmounts, } from './concentrate-liquidity-math' import { ONE_E18 } from '../concentrate-grow-liquidity-2d-args' -import { bigintSqrt } from '../bigint-sqrt' +import { bigintSqrt } from '../../utils/bigint-sqrt' describe('concentrate-liquidity-math', () => { describe('computeLiquidityAndPrice', () => { diff --git a/typescript/swap-vm/src/swap-vm/instructions/concentrate/concentrate-liquidity-math/concentrate-liquidity-math.ts b/typescript/swap-vm/src/swap-vm/instructions/concentrate/concentrate-liquidity-math/concentrate-liquidity-math.ts index 45e4f26..00b8e22 100644 --- a/typescript/swap-vm/src/swap-vm/instructions/concentrate/concentrate-liquidity-math/concentrate-liquidity-math.ts +++ b/typescript/swap-vm/src/swap-vm/instructions/concentrate/concentrate-liquidity-math/concentrate-liquidity-math.ts @@ -1,7 +1,7 @@ // SPDX-License-Identifier: LicenseRef-Degensoft-SwapVM-1.1 import { UINT_256_MAX } from '@1inch/byte-utils' -import { bigintSqrt } from '../bigint-sqrt' +import { bigintSqrt } from '../../utils/bigint-sqrt' const ONE = 10n ** 18n diff --git a/typescript/swap-vm/src/swap-vm/instructions/concentrate/index.ts b/typescript/swap-vm/src/swap-vm/instructions/concentrate/index.ts index 2175139..94c334f 100644 --- a/typescript/swap-vm/src/swap-vm/instructions/concentrate/index.ts +++ b/typescript/swap-vm/src/swap-vm/instructions/concentrate/index.ts @@ -20,4 +20,4 @@ export type { PriceBounds, ConcentratedLiquidityInfo, } from './concentrate-liquidity-calculator/types' -export * from './bigint-sqrt' +export { bigintSqrt } from '../utils/bigint-sqrt' diff --git a/typescript/swap-vm/src/swap-vm/instructions/concentrate/price/index.ts b/typescript/swap-vm/src/swap-vm/instructions/concentrate/price/index.ts index 1e62e06..4107fb9 100644 --- a/typescript/swap-vm/src/swap-vm/instructions/concentrate/price/index.ts +++ b/typescript/swap-vm/src/swap-vm/instructions/concentrate/price/index.ts @@ -1,5 +1,5 @@ // SPDX-License-Identifier: LicenseRef-Degensoft-SwapVM-1.1 export { Price } from './price' -export { truncateHumanDecimalString } from './truncate-human-decimal-string' +export { truncateHumanDecimalString } from '../../utils/truncate-human-decimal-string' export type { PriceJSON, PricePair, PriceToken } from './types' diff --git a/typescript/swap-vm/src/swap-vm/instructions/concentrate/price/price.ts b/typescript/swap-vm/src/swap-vm/instructions/concentrate/price/price.ts index bb5741b..de47680 100644 --- a/typescript/swap-vm/src/swap-vm/instructions/concentrate/price/price.ts +++ b/typescript/swap-vm/src/swap-vm/instructions/concentrate/price/price.ts @@ -4,8 +4,8 @@ import { Address } from '@1inch/sdk-core' import { formatUnits, parseUnits } from 'viem' import assert from 'assert' import type { PriceJSON, PricePair, PriceToken } from './types' -import { truncateHumanDecimalString } from './truncate-human-decimal-string' -import { bigintSqrt } from '../bigint-sqrt' +import { bigintSqrt } from '../../utils/bigint-sqrt' +import { truncateHumanDecimalString } from '../../utils/truncate-human-decimal-string' const ONE_E18 = 10n ** 18n diff --git a/typescript/swap-vm/src/swap-vm/instructions/index.ts b/typescript/swap-vm/src/swap-vm/instructions/index.ts index 3360fd3..d36f8da 100644 --- a/typescript/swap-vm/src/swap-vm/instructions/index.ts +++ b/typescript/swap-vm/src/swap-vm/instructions/index.ts @@ -20,6 +20,7 @@ import type { IArgsData } from './types' import * as peggedSwap from './pegged-swap' export * from './types' +export { bigintSqrt, truncateHumanDecimalString } from './utils' export { EMPTY_OPCODE } from './empty' export * as balances from './balances' export * as controls from './controls' diff --git a/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/index.ts b/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/index.ts index 83bc09c..bc616dd 100644 --- a/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/index.ts +++ b/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/index.ts @@ -3,3 +3,13 @@ export * from './opcodes' export { PeggedSwapArgs } from './pegged-swap-args' export type { PeggedTokenInfo } from './types' +export { PeggedSwapCalculator } from './pegged-swap-calculator' +export { PeggedPrice } from './price' +export type { PeggedInitialBalances, PeggedSwapCalculatorArgs } from './pegged-swap-calculator' +export type { + PeggedPriceJSON, + PeggedPricePair, + PeggedReservesInput, + PeggedTokenRef, + PeggedTokenReserve, +} from './price' diff --git a/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/pegged-swap-calculator/PEGGED-SWAP-CALCULATOR.md b/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/pegged-swap-calculator/PEGGED-SWAP-CALCULATOR.md new file mode 100644 index 0000000..6f99d71 --- /dev/null +++ b/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/pegged-swap-calculator/PEGGED-SWAP-CALCULATOR.md @@ -0,0 +1,146 @@ +# PeggedSwapCalculator + +Helper for **initial pool balances before deployment** of a PeggedSwap strategy (`peggedSwapGrowPriceRange2D`). It answers: “I know the target spot price and how much of one token I want to deposit — how much of the other token do I need so the pool starts on-peg?” + +This calculator only covers the **pre-launch** case where `currentReserve = initialReserve` (normalized coordinates `u = v = 1` on the curve). It does not model reserves after trading or off-center spot. + +## Overview + +- **Token ordering**: Lt = lower address, Gt = higher address (same as on-chain `PeggedSwapMath`). `tokenA` / `tokenB` order in the constructor does not matter; use `tokenLt` / `tokenGt` getters if needed. +- **Price convention**: Use `PeggedPrice` — human string is **quote per 1 base** (same as `Price` in concentrate). Internally the marginal rate is **tokenGt per tokenLt** in 1e18 fixed-point. +- **At deployment center**: Spot does not depend on `linearWidth`; with equal normalization, `reserveGt ≈ reserveLt × spot` (in raw units, via `toGtPerLtE18()`). `linearWidth` still goes into `PeggedSwapArgs` and affects swaps after launch. + +## On-chain curve (context) + +PeggedSwap invariant (see [PeggedSwap.sol](https://github.com/1inch/swap-vm/blob/main/src/instructions/PeggedSwap.sol)): + +``` +√(x/X₀) + √(y/Y₀) + A(x/X₀ + y/Y₀) = 1 + A +``` + +`X₀` / `Y₀` in args are **rate-scaled** initial balances: `x0 = initialLt × rateLt`, `y0 = initialGt × rateGt` (see `PeggedSwapArgs.fromTokens`). + +## Types + +### `PeggedSwapCalculatorArgs` + +| Field | Type | Description | +|-------|------|-------------| +| `tokenA` | `PeggedTokenRef` | First token (`address`, `decimals`). | +| `tokenB` | `PeggedTokenRef` | Second token. | + +### `PeggedInitialBalances` + +| Field | Type | Description | +|-------|------|-------------| +| `reserveLt` | `bigint` | Raw initial balance for Lt (lower address). | +| `reserveGt` | `bigint` | Raw initial balance for Gt (higher address). | + +## API + +Exported from `instructions.peggedSwap` on `@1inch/swap-vm-sdk`: + +```ts +import { instructions } from '@1inch/swap-vm-sdk' +import { parseUnits } from 'viem' + +const { PeggedSwapCalculator, PeggedPrice, PeggedSwapArgs } = instructions.peggedSwap +``` + +### Creating a calculator + +```ts +const USDC = { address: usdcAddress, decimals: 6 } +const USDT = { address: usdtAddress, decimals: 6 } + +const calculator = PeggedSwapCalculator.new({ tokenA: USDC, tokenB: USDT }) +// calculator.tokenLt → USDC (lower address) +// calculator.tokenGt → USDT +``` + +### `computeFixedAllocation(spotPrice, fixedReserveForToken, fixedReserve)` + +Given a target **`PeggedPrice`** and a **raw** amount for one token, returns both initial reserves in Lt/Gt order. + +**Use case**: “Deposit 1M USDC; how much USDT at 0.998 USDT/USDC before the strategy ships?” + +```ts +const spot = PeggedPrice.fromHuman('0.998', { + quoteToken: { address: usdtAddress, decimals: 6 }, + baseToken: { address: usdcAddress, decimals: 6 }, +}) + +const fixedUsdc = parseUnits('1000000', 6) +const balances = calculator.computeFixedAllocation(spot, usdcAddress, fixedUsdc) + +// balances.reserveLt — USDC amount (raw) +// balances.reserveGt — USDT amount (raw) +``` + +Fix the other side instead: + +```ts +const balances = calculator.computeFixedAllocation(spot, usdtAddress, parseUnits('998000', 6)) +``` + +### Building `PeggedSwapArgs` + +Pass the computed raw reserves into `PeggedSwapArgs.fromTokens` (rates and Lt/Gt ordering are applied inside): + +```ts +const linearWidth = 8n * 10n ** 26n // A = 0.8 + +const args = PeggedSwapArgs.fromTokens( + { address: usdcAddress, decimals: 6, reserve: balances.reserveLt }, + { address: usdtAddress, decimals: 6, reserve: balances.reserveGt }, + linearWidth, +) +``` + +Use `args` with `peggedSwapGrowPriceRange2D` in your program builder (e.g. `AquaPeggedAmmStrategy`). + +## Spot price helpers (`PeggedPrice`) + +The calculator does not compute spot from reserves; use **`PeggedPrice`**: + +| Method | Purpose | +|--------|---------| +| `PeggedPrice.fromHuman(price, pair)` | Parse target quote-per-base (e.g. `'0.998'`, `'1.002'`). | +| `PeggedPrice.fromReserves({ reserveA, reserveB, linearWidth })` | Marginal spot from initial/current reserves; set `currentReserve = initialReserve` before launch. | +| `price.toHuman(quoteToken)` | Format for display. | + +**Verify** allocation (optional): + +```ts +const check = PeggedPrice.fromReserves({ + linearWidth, + reserveA: { ...usdc, initialReserve: balances.reserveLt, currentReserve: balances.reserveLt }, + reserveB: { ...usdt, initialReserve: balances.reserveGt, currentReserve: balances.reserveGt }, +}) + +expect(check.toHuman(usdtAddress)).toBe('0.998') +``` + +## Examples + +### USDC / USDT @ 0.998 + +Lt = USDC, Gt = USDT. Quote = USDT, base = USDC → 0.998 USDT per 1 USDC. + +1M USDC fixed → 998_000 USDT (6 decimals each). + +### DAI / USDC @ 1.002 + +Lt = DAI (18 dec), Gt = USDC (6 dec). Quote = USDC, base = DAI → 1.002 USDC per 1 DAI. + +1M DAI fixed → 1_002_000 USDC (raw `6` decimals). + +## Relation to SwapVM + +| Piece | Role | +|-------|------| +| `PeggedSwapCalculator` | Initial Lt/Gt raw balances from spot + one fixed leg. | +| `PeggedSwapArgs` | Encoded `x0`, `y0`, `linearWidth`, `rateLt`, `rateGt` for the instruction. | +| `PeggedPrice` | Human and reserve-based marginal price (gt per lt). | + +After deployment, spot moves with reserves; use `PeggedPrice.fromReserves` with **current** balances, not this calculator. diff --git a/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/pegged-swap-calculator/index.ts b/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/pegged-swap-calculator/index.ts new file mode 100644 index 0000000..080d331 --- /dev/null +++ b/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/pegged-swap-calculator/index.ts @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: LicenseRef-Degensoft-SwapVM-1.1 + +export { PeggedSwapCalculator } from './pegged-swap-calculator' +export type { PeggedInitialBalances, PeggedSwapCalculatorArgs } from './types' diff --git a/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/pegged-swap-calculator/pegged-swap-calculator.test.ts b/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/pegged-swap-calculator/pegged-swap-calculator.test.ts new file mode 100644 index 0000000..fe6d3df --- /dev/null +++ b/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/pegged-swap-calculator/pegged-swap-calculator.test.ts @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: LicenseRef-Degensoft-SwapVM-1.1 + +import { describe, expect, it } from 'vitest' +import { Address } from '@1inch/sdk-core' +import { PeggedSwapCalculator } from './pegged-swap-calculator' +import { PeggedPrice } from '../price/pegged-price' +import type { PeggedPricePair } from '../price/types' + +const TOKEN_A = new Address('0x0000000000000000000000000000000000000001') +const TOKEN_B = new Address('0x0000000000000000000000000000000000000002') +const LINEAR_WIDTH = 8n * 10n ** 26n + +const USDC = new Address('0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48') +const USDT = new Address('0xdAC17F958D2ee523a2206206994597C13D831ec7') +const DAI = new Address('0x6B175474E89094C44Da98b954EedeAC495271d0F') + +const pairGtQuoteLtBase: PeggedPricePair = { + quoteToken: { address: TOKEN_B, decimals: 18 }, + baseToken: { address: TOKEN_A, decimals: 18 }, +} + +describe('PeggedSwapCalculator', () => { + const calculator = PeggedSwapCalculator.new({ + tokenA: { address: TOKEN_A, decimals: 18 }, + tokenB: { address: TOKEN_B, decimals: 18 }, + }) + + it('computeFixedAllocation derives initial Gt from fixed Lt', () => { + const spot = PeggedPrice.fromHuman('1.5', pairGtQuoteLtBase) + const fixedLt = 10n ** 18n + + const balances = calculator.computeFixedAllocation(spot, TOKEN_A, fixedLt) + + expect(balances.reserveLt).toBe(fixedLt) + expect(balances.reserveGt).toBe(15n * 10n ** 17n) + + const price = PeggedPrice.fromReserves({ + linearWidth: LINEAR_WIDTH, + reserveA: { + address: TOKEN_A, + decimals: 18, + initialReserve: balances.reserveLt, + currentReserve: balances.reserveLt, + }, + reserveB: { + address: TOKEN_B, + decimals: 18, + initialReserve: balances.reserveGt, + currentReserve: balances.reserveGt, + }, + }) + + expect(price.toHuman(TOKEN_B)).toBe('1.5') + }) + + it('computeFixedAllocation derives initial Lt from fixed Gt (6 / 18 decimals)', () => { + const calc = PeggedSwapCalculator.new({ + tokenA: { address: TOKEN_A, decimals: 6 }, + tokenB: { address: TOKEN_B, decimals: 18 }, + }) + + const spot = PeggedPrice.fromHuman('2', { + quoteToken: { address: TOKEN_B, decimals: 18 }, + baseToken: { address: TOKEN_A, decimals: 6 }, + }) + + const balances = calc.computeFixedAllocation(spot, TOKEN_B, 2n * 10n ** 18n) + + expect(balances.reserveGt).toBe(2n * 10n ** 18n) + expect(balances.reserveLt).toBe(1n * 10n ** 6n) + + const price = PeggedPrice.fromReserves({ + linearWidth: LINEAR_WIDTH, + reserveA: { + address: TOKEN_A, + decimals: 6, + initialReserve: balances.reserveLt, + currentReserve: balances.reserveLt, + }, + reserveB: { + address: TOKEN_B, + decimals: 18, + initialReserve: balances.reserveGt, + currentReserve: balances.reserveGt, + }, + }) + + expect(price.toHuman(TOKEN_B)).toBe('2') + }) + + describe('stablecoin pairs', () => { + it('USDC/USDT: 0.998 USDT per 1 USDC with 1M USDC fixed', () => { + const calculator = PeggedSwapCalculator.new({ + tokenA: { address: USDC, decimals: 6 }, + tokenB: { address: USDT, decimals: 6 }, + }) + + const spot = PeggedPrice.fromHuman('0.998', { + quoteToken: { address: USDT, decimals: 6 }, + baseToken: { address: USDC, decimals: 6 }, + }) + + const fixedUsdc = 1_000_000n * 10n ** 6n + const balances = calculator.computeFixedAllocation(spot, USDC, fixedUsdc) + + expect(balances.reserveLt).toBe(fixedUsdc) + expect(balances.reserveGt).toBe(998_000n * 10n ** 6n) + + const price = PeggedPrice.fromReserves({ + linearWidth: LINEAR_WIDTH, + reserveA: { + address: USDC, + decimals: 6, + initialReserve: balances.reserveLt, + currentReserve: balances.reserveLt, + }, + reserveB: { + address: USDT, + decimals: 6, + initialReserve: balances.reserveGt, + currentReserve: balances.reserveGt, + }, + }) + + expect(price.toHuman(USDT)).toBe('0.998') + }) + + it('DAI/USDC: 1.002 USDC per 1 DAI with 1M DAI fixed', () => { + const calculator = PeggedSwapCalculator.new({ + tokenA: { address: DAI, decimals: 18 }, + tokenB: { address: USDC, decimals: 6 }, + }) + + const spot = PeggedPrice.fromHuman('1.002', { + quoteToken: { address: USDC, decimals: 6 }, + baseToken: { address: DAI, decimals: 18 }, + }) + + const fixedDai = 1_000_000n * 10n ** 18n + const balances = calculator.computeFixedAllocation(spot, DAI, fixedDai) + + expect(balances.reserveLt).toBe(fixedDai) + expect(balances.reserveGt).toBe(1_002_000n * 10n ** 6n) + + const price = PeggedPrice.fromReserves({ + linearWidth: LINEAR_WIDTH, + reserveA: { + address: DAI, + decimals: 18, + initialReserve: balances.reserveLt, + currentReserve: balances.reserveLt, + }, + reserveB: { + address: USDC, + decimals: 6, + initialReserve: balances.reserveGt, + currentReserve: balances.reserveGt, + }, + }) + + expect(price.toHuman(USDC)).toBe('1.002') + }) + }) +}) diff --git a/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/pegged-swap-calculator/pegged-swap-calculator.ts b/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/pegged-swap-calculator/pegged-swap-calculator.ts new file mode 100644 index 0000000..1cd72db --- /dev/null +++ b/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/pegged-swap-calculator/pegged-swap-calculator.ts @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: LicenseRef-Degensoft-SwapVM-1.1 + +import type { Address } from '@1inch/sdk-core' +import assert from 'assert' +import type { PeggedInitialBalances, PeggedSwapCalculatorArgs } from './types' +import type { PeggedPrice, PeggedTokenRef } from '../price' + +const MARGINAL_PRICE_ONE = 10n ** 18n + +export class PeggedSwapCalculator { + constructor( + private readonly tokenA: PeggedTokenRef, + private readonly tokenB: PeggedTokenRef, + ) { + assert(!tokenA.address.equal(tokenB.address), 'tokens must be different') + } + + get tokenLt(): PeggedTokenRef { + return this.tokenA.address.lt(this.tokenB.address) ? this.tokenA : this.tokenB + } + + get tokenGt(): PeggedTokenRef { + return this.tokenA.address.lt(this.tokenB.address) ? this.tokenB : this.tokenA + } + + static new(args: PeggedSwapCalculatorArgs): PeggedSwapCalculator { + return new PeggedSwapCalculator(args.tokenA, args.tokenB) + } + + /** + * Initial balances before deployment (`currentReserve = initialReserve`, u = v = 1). + * Given spot price and one raw initial reserve, returns the other. + */ + computeFixedAllocation( + spotPrice: PeggedPrice, + fixedReserveForToken: Address, + fixedReserve: bigint, + ): PeggedInitialBalances { + assert(fixedReserve > 0n, 'fixed reserve must be positive') + assert( + spotPrice.matchesTokens(this.tokenLt.address, this.tokenGt.address), + 'spot price must match calculator token pair', + ) + + const marginalE18 = spotPrice.toGtPerLtE18() + + if (fixedReserveForToken.equal(this.tokenLt.address)) { + return { + reserveLt: fixedReserve, + reserveGt: (fixedReserve * marginalE18) / MARGINAL_PRICE_ONE, + } + } + + if (fixedReserveForToken.equal(this.tokenGt.address)) { + return { + reserveLt: (fixedReserve * MARGINAL_PRICE_ONE) / marginalE18, + reserveGt: fixedReserve, + } + } + + throw new Error('fixedReserveForToken token must be one of the two pair tokens') + } +} diff --git a/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/pegged-swap-calculator/types.ts b/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/pegged-swap-calculator/types.ts new file mode 100644 index 0000000..5cb2e8c --- /dev/null +++ b/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/pegged-swap-calculator/types.ts @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LicenseRef-Degensoft-SwapVM-1.1 + +import type { PeggedTokenRef } from '../price/types' + +export type PeggedSwapCalculatorArgs = { + tokenA: PeggedTokenRef + tokenB: PeggedTokenRef +} + +export type PeggedInitialBalances = { + reserveLt: bigint + reserveGt: bigint +} 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 new file mode 100644 index 0000000..ae225ac --- /dev/null +++ b/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/pegged-swap-math/pegged-swap-math.test.ts @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: LicenseRef-Degensoft-SwapVM-1.1 + +import { describe, expect, it } from 'vitest' +import { + normalizeReserve, + peggedSwapMarginalGtPerLtE18, + peggedSwapMarginalWeight, + PEGGED_SWAP_ONE, +} from './pegged-swap-math' +import { bigintSqrt } from '../../utils' + +const LINEAR_WIDTH = 8n * 10n ** 26n + +describe('peggedSwapMath', () => { + it('normalizeReserve is ONE when current equals initial', () => { + const initial = 10n ** 18n + expect(normalizeReserve(initial, initial)).toBe(PEGGED_SWAP_ONE) + }) + + it('marginal weight at u = ONE', () => { + const sqrtCoord = bigintSqrt(PEGGED_SWAP_ONE * PEGGED_SWAP_ONE) + const weight = peggedSwapMarginalWeight(sqrtCoord, LINEAR_WIDTH) + expect(weight).toBe((PEGGED_SWAP_ONE * PEGGED_SWAP_ONE) / (2n * sqrtCoord) + LINEAR_WIDTH) + }) + + it('marginal price at center equals y0·rateLt/(x0·rateGt) in 1e18', () => { + const x0 = 10n ** 18n + const y0 = 2n * 10n ** 18n + const rateLt = 1n + const rateGt = 1n + const marginal = peggedSwapMarginalGtPerLtE18(x0, y0, x0, y0, LINEAR_WIDTH, rateLt, rateGt) + expect(marginal).toBe((y0 * rateLt * 10n ** 18n) / (x0 * rateGt)) + }) + + it('marginal price at center with lt 6 / gt 18 decimals', () => { + const x0 = 10n ** 18n + const y0 = 2n * 10n ** 18n + const rateLt = 10n ** 12n + const rateGt = 1n + const marginal = peggedSwapMarginalGtPerLtE18(x0, y0, x0, y0, LINEAR_WIDTH, rateLt, rateGt) + expect(marginal).toBe(2n * 10n ** 30n) + }) + + it('marginal price differs when current reserves differ from initial', () => { + const x0 = 10n ** 18n + const y0 = 10n ** 18n + const atCenter = peggedSwapMarginalGtPerLtE18(x0, y0, x0, y0, LINEAR_WIDTH, 1n, 1n) + const offCenter = peggedSwapMarginalGtPerLtE18( + 993_000_000_000_000_000n, + 999_360_128_962_949_073n, + x0, + y0, + LINEAR_WIDTH, + 1n, + 1n, + ) + expect(offCenter).not.toBe(atCenter) + expect(offCenter).toBeGreaterThan(atCenter) + }) +}) 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 new file mode 100644 index 0000000..cff8a89 --- /dev/null +++ b/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/pegged-swap-math/pegged-swap-math.ts @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: LicenseRef-Degensoft-SwapVM-1.1 + +import assert from 'assert' +import { bigintSqrt } from '../../utils' + +/** Matches `PeggedSwapMath.ONE` in swap-vm. */ +export const PEGGED_SWAP_ONE: bigint = 10n ** 27n + +const MARGINAL_PRICE_ONE = 10n ** 18n + +/** + * Spot price tokenGt per tokenLt (raw) in 1e18 fixed-point. + * + * P = (Y₀/X₀) · (1/(2√u) + A) / (1/(2√v) + A) · (rateLt/rateGt) + * + * where u = x·ONE/X₀, v = y·ONE/Y₀, x/y are rate-adjusted Lt/Gt balances, A = `linearWidth`. + */ +export function peggedSwapMarginalGtPerLtE18( + balanceLtNorm: bigint, + balanceGtNorm: bigint, + x0: bigint, + y0: bigint, + linearWidth: bigint, + rateLt: bigint, + rateGt: bigint, +): bigint { + const u = normalizeReserve(balanceLtNorm, x0) + const v = normalizeReserve(balanceGtNorm, y0) + + assert(u !== 0n && v !== 0n, 'PeggedSwapMath: reserves cannot be zero') + + const slopeLt = peggedSwapMarginalWeight(bigintSqrt(u * PEGGED_SWAP_ONE), linearWidth) + const slopeGt = peggedSwapMarginalWeight(bigintSqrt(v * PEGGED_SWAP_ONE), linearWidth) + + return (y0 * slopeLt * rateLt * MARGINAL_PRICE_ONE) / (x0 * slopeGt * rateGt) +} + +/** + * u = x·ONE/X₀, v = y·ONE/Y₀ (x, y are rate-adjusted reserves). + */ +export function normalizeReserve(currentReserve: bigint, initialReserve: bigint): bigint { + return mulDiv(currentReserve, PEGGED_SWAP_ONE, initialReserve) +} + +/** + * Marginal weight `1/(2√u) + A` (Lt side) or `1/(2√v) + A` (Gt side), A = `linearWidth`. + */ +export function peggedSwapMarginalWeight(sqrtCoord: bigint, linearWidth: bigint): bigint { + return mulDiv(PEGGED_SWAP_ONE, PEGGED_SWAP_ONE, 2n * sqrtCoord) + linearWidth +} + +function mulDiv(a: bigint, b: bigint, c: bigint): bigint { + if (c === 0n) { + throw new Error('mulDiv: division by zero') + } + + return (a * b) / c +} diff --git a/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/price/index.ts b/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/price/index.ts new file mode 100644 index 0000000..8f46e1c --- /dev/null +++ b/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/price/index.ts @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: LicenseRef-Degensoft-SwapVM-1.1 + +export { PeggedPrice } from './pegged-price' +export type { + PeggedPriceJSON, + PeggedPricePair, + PeggedReservesInput, + PeggedTokenRef, + PeggedTokenReserve, +} from './types' diff --git a/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/price/pegged-price.test.ts b/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/price/pegged-price.test.ts new file mode 100644 index 0000000..bf63462 --- /dev/null +++ b/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/price/pegged-price.test.ts @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: LicenseRef-Degensoft-SwapVM-1.1 + +import { describe, expect, it } from 'vitest' +import { Address } from '@1inch/sdk-core' +import { PeggedPrice } from './pegged-price' +import type { PeggedPricePair, PeggedTokenReserve } from './types' + +const TOKEN_A = new Address('0x0000000000000000000000000000000000000001') +const TOKEN_B = new Address('0x0000000000000000000000000000000000000002') +const LINEAR_WIDTH = 8n * 10n ** 26n + +const reserveA: PeggedTokenReserve = { + address: TOKEN_A, + decimals: 18, + initialReserve: 1000n * 10n ** 18n, + currentReserve: 1000n * 10n ** 18n, +} + +const reserveB: PeggedTokenReserve = { + address: TOKEN_B, + decimals: 18, + initialReserve: 999n * 10n ** 18n - 1n, + currentReserve: 999n * 10n ** 18n - 1n, +} + +const pairGtQuoteLtBase: PeggedPricePair = { + quoteToken: { address: TOKEN_B, decimals: 18 }, + baseToken: { address: TOKEN_A, decimals: 18 }, +} + +const pairLtQuoteGtBase: PeggedPricePair = { + quoteToken: { address: TOKEN_A, decimals: 18 }, + baseToken: { address: TOKEN_B, decimals: 18 }, +} + +describe('PeggedPrice', () => { + it('fromReserves derives lt/gt from addresses', () => { + const price = PeggedPrice.fromReserves({ + linearWidth: LINEAR_WIDTH, + reserveA, + reserveB, + }) + expect(price.toHuman(TOKEN_B)).toBe('0.998999999999999999') + }) + + it('fromReserves accepts tokens in either order', () => { + const forward = PeggedPrice.fromReserves({ + linearWidth: LINEAR_WIDTH, + reserveA, + reserveB, + }) + const reversed = PeggedPrice.fromReserves({ + linearWidth: LINEAR_WIDTH, + reserveA: reserveB, + reserveB: reserveA, + }) + expect(reversed.equals(forward)).toBe(true) + }) + + it('toJSON should yield bigint-safe JSON', () => { + const price = PeggedPrice.fromHuman('1.5', pairGtQuoteLtBase) + const json = price.toJSON() + + expect(json.gtPerLtRaw).toBe('1500000000000000000000000000000000000') + expect(json.tokenLt.decimals).toBe('18') + expect(json.tokenGt.decimals).toBe('18') + expect(JSON.stringify(price)).toBe(JSON.stringify(json)) + }) + + it('fromJSON should round-trip toJSON', () => { + const price = PeggedPrice.fromHuman('1.5', pairGtQuoteLtBase) + + expect(PeggedPrice.fromJSON(price.toJSON()).equals(price)).toBe(true) + }) + + it('fromJSON should require canonical tokenLt < tokenGt order', () => { + const price = PeggedPrice.fromHuman('1.5', pairGtQuoteLtBase) + const json = price.toJSON() + const bad = { + ...json, + tokenLt: json.tokenGt, + tokenGt: json.tokenLt, + } + + expect(() => PeggedPrice.fromJSON(bad)).toThrow('tokenLt address must be less than tokenGt') + }) + + it('fromHuman round-trips gt quote', () => { + const price = PeggedPrice.fromHuman('1.5', pairGtQuoteLtBase) + expect(price.toHuman(TOKEN_B)).toBe('1.5') + }) + + it('fromHuman round-trips lt quote', () => { + const price = PeggedPrice.fromHuman('0.5', pairLtQuoteGtBase) + expect(price.toHuman(TOKEN_A)).toBe('0.5') + }) + + describe('mixed decimals (lt 6, gt 18)', () => { + const pairGtQuoteLtBase: PeggedPricePair = { + quoteToken: { address: TOKEN_B, decimals: 18 }, + baseToken: { address: TOKEN_A, decimals: 6 }, + } + + const pairLtQuoteGtBase: PeggedPricePair = { + quoteToken: { address: TOKEN_A, decimals: 6 }, + baseToken: { address: TOKEN_B, decimals: 18 }, + } + + const reserveLt = { + address: TOKEN_A, + decimals: 6, + initialReserve: 1000n * 10n ** 6n, + currentReserve: 1000n * 10n ** 6n, + } + + const reserveGt = { + address: TOKEN_B, + decimals: 18, + initialReserve: 999n * 10n ** 18n - 1n, + currentReserve: 999n * 10n ** 18n - 1n, + } + + it('fromReserves at center is 2 gt per 1 lt', () => { + const price = PeggedPrice.fromReserves({ + linearWidth: LINEAR_WIDTH, + reserveA: reserveLt, + reserveB: reserveGt, + }) + expect(price.toHuman(TOKEN_B)).toBe('0.998999999999999999') + }) + + it('fromHuman round-trips gt quote', () => { + const price = PeggedPrice.fromHuman('2', pairGtQuoteLtBase) + expect(price.toHuman(TOKEN_B)).toBe('2') + }) + + it('fromHuman round-trips lt quote', () => { + const price = PeggedPrice.fromHuman('0.5', pairLtQuoteGtBase) + expect(price.toHuman(TOKEN_A)).toBe('0.5') + }) + }) + + describe('mixed decimals (lt 18, gt 6)', () => { + const pairGtQuoteLtBase: PeggedPricePair = { + quoteToken: { address: TOKEN_B, decimals: 6 }, + baseToken: { address: TOKEN_A, decimals: 18 }, + } + + const pairLtQuoteGtBase: PeggedPricePair = { + quoteToken: { address: TOKEN_A, decimals: 18 }, + baseToken: { address: TOKEN_B, decimals: 6 }, + } + + const reserveLt = { + address: TOKEN_A, + decimals: 18, + initialReserve: 1000n * 10n ** 18n, + currentReserve: 1000n * 10n ** 18n, + } + + const reserveGt = { + address: TOKEN_B, + decimals: 6, + initialReserve: 999n * 10n ** 6n - 1n, + currentReserve: 999n * 10n ** 6n - 1n, + } + + it('fromReserves at center is 2 gt per 1 lt', () => { + const price = PeggedPrice.fromReserves({ + linearWidth: LINEAR_WIDTH, + reserveA: reserveLt, + reserveB: reserveGt, + }) + expect(price.toHuman(TOKEN_B)).toBe('0.998999') + }) + + it('fromHuman round-trips gt quote', () => { + const price = PeggedPrice.fromHuman('2', pairGtQuoteLtBase) + expect(price.toHuman(TOKEN_B)).toBe('2') + }) + + it('fromHuman round-trips lt quote', () => { + const price = PeggedPrice.fromHuman('0.5', pairLtQuoteGtBase) + expect(price.toHuman(TOKEN_A)).toBe('0.5') + }) + }) + + describe('off-center reserves (current ≠ initial)', () => { + it('equal 18/18 decimals', () => { + const initial = 10n ** 18n + const reserveAOff = { + address: TOKEN_A, + decimals: 18, + initialReserve: initial, + currentReserve: 993_000_000_000_000_000n, + } + const reserveBOff = { + address: TOKEN_B, + decimals: 18, + initialReserve: initial, + currentReserve: 999_360_128_962_949_073n, + } + + const price = PeggedPrice.fromReserves({ + linearWidth: LINEAR_WIDTH, + reserveA: reserveAOff, + reserveB: reserveBOff, + }) + expect(price.toHuman(TOKEN_B)).toBe('1.001229999999999999') + }) + + it('lt 6 / gt 18 decimals', () => { + const reserveAOff = { + address: TOKEN_A, + decimals: 6, + initialReserve: 1_000_000n, + currentReserve: 993_000n, + } + const reserveBOff = { + address: TOKEN_B, + decimals: 18, + initialReserve: 10n ** 18n, + currentReserve: 999_360_128_962_949_073n, + } + + const price = PeggedPrice.fromReserves({ + linearWidth: LINEAR_WIDTH, + reserveA: reserveAOff, + reserveB: reserveBOff, + }) + expect(price.toHuman(TOKEN_B)).toBe('1.001229999999999999') + }) + + it('lt 18 / gt 6 decimals', () => { + const reserveAOff = { + address: TOKEN_A, + decimals: 18, + initialReserve: 10n ** 18n, + currentReserve: 993_000_000_000_000_000n, + } + const reserveBOff = { + address: TOKEN_B, + decimals: 6, + initialReserve: 1_000_000n, + currentReserve: 999_360n, + } + + const price = PeggedPrice.fromReserves({ + linearWidth: LINEAR_WIDTH, + reserveA: reserveAOff, + reserveB: reserveBOff, + }) + expect(price.toHuman(TOKEN_B)).toBe('1.001229') + }) + + it('fromHuman round-trips gt quote (18/18)', () => { + const price = PeggedPrice.fromHuman('1.00123', pairGtQuoteLtBase) + expect(price.toHuman(TOKEN_B)).toBe('1.00123') + }) + }) +}) diff --git a/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/price/pegged-price.ts b/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/price/pegged-price.ts new file mode 100644 index 0000000..d78f92b --- /dev/null +++ b/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/price/pegged-price.ts @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: LicenseRef-Degensoft-SwapVM-1.1 + +import { Address } from '@1inch/sdk-core' +import { formatUnits, parseUnits } from 'viem' +import assert from 'assert' +import type { PeggedPriceJSON, PeggedPricePair, PeggedReservesInput, PeggedTokenRef } from './types' +import { peggedSwapMarginalGtPerLtE18 } from '../pegged-swap-math/pegged-swap-math' +import { truncateHumanDecimalString } from '../../utils' +import { resolveRate } from '../rate-resolver' + +const ONE_E18 = 10n ** 18n + +export class PeggedPrice { + private constructor( + private readonly gtPerLtRaw: bigint, + public readonly tokenLt: PeggedTokenRef, + public readonly tokenGt: PeggedTokenRef, + ) { + assert(gtPerLtRaw > 0n, 'price must be positive') + assert(tokenLt.address.lt(tokenGt.address), 'internal pair order violated') + } + + /** + * Spot price from per-token `initialReserve` / `currentReserve` (raw, not rate-scaled) and `linearWidth`. + * Use currentReserve = initialReserve to calculate the spot price before the strategy was deployed + */ + static fromReserves(input: PeggedReservesInput): PeggedPrice { + assert( + input.reserveA.currentReserve > 0n && input.reserveB.currentReserve > 0n, + 'current reserves should be positive', + ) + + assert( + input.reserveA.initialReserve > 0n && input.reserveB.initialReserve > 0n, + 'initial reserves should be positive', + ) + + const zeroForOne = input.reserveA.address.lt(input.reserveB.address) + const reserveLt = zeroForOne ? input.reserveA : input.reserveB + const reserveGt = zeroForOne ? input.reserveB : input.reserveA + + const rateLt = resolveRate(reserveLt.decimals, reserveGt.decimals) + const rateGt = resolveRate(reserveGt.decimals, reserveLt.decimals) + + const initialLtNorm = reserveLt.initialReserve * rateLt + const initialGtNorm = reserveGt.initialReserve * rateGt + + const marginalE18 = peggedSwapMarginalGtPerLtE18( + reserveLt.currentReserve * rateLt, + reserveGt.currentReserve * rateGt, + initialLtNorm, + initialGtNorm, + input.linearWidth, + rateLt, + rateGt, + ) + + return PeggedPrice.fromGtPerLtE18(marginalE18, reserveLt, reserveGt) + } + + /** + * Human decimal string for **quote per 1 base**. + */ + static fromHuman(price: string, pair: PeggedPricePair): PeggedPrice { + assert( + !pair.quoteToken.address.equal(pair.baseToken.address), + 'quote and base must be different tokens', + ) + + const quoteToBase = pair.quoteToken.address.lt(pair.baseToken.address) + + const tokenLt = quoteToBase ? pair.quoteToken : pair.baseToken + const tokenGt = quoteToBase ? pair.baseToken : pair.quoteToken + + const parsed = parseUnits(price.trim(), Number(pair.quoteToken.decimals)) + + const ltDecimals = BigInt(tokenLt.decimals) + const gtDecimals = BigInt(tokenGt.decimals) + + const marginalE18 = quoteToBase + ? 10n ** (gtDecimals + 18n + ltDecimals) / (parsed * 10n ** gtDecimals) + : (parsed * ONE_E18) / 10n ** ltDecimals + + return PeggedPrice.fromGtPerLtE18(marginalE18, tokenLt, tokenGt) + } + + static fromJSON(input: PeggedPriceJSON): PeggedPrice { + const tokenLt: PeggedTokenRef = { + address: new Address(input.tokenLt.address), + decimals: Number(input.tokenLt.decimals), + } + const tokenGt: PeggedTokenRef = { + address: new Address(input.tokenGt.address), + decimals: Number(input.tokenGt.decimals), + } + assert( + tokenLt.address.lt(tokenGt.address), + 'tokenLt address must be less than tokenGt (canonical order)', + ) + + return new PeggedPrice(BigInt(input.gtPerLtRaw), tokenLt, tokenGt) + } + + private static fromGtPerLtE18( + marginalGtPerLtE18: bigint, + tokenLt: PeggedTokenRef, + tokenGt: PeggedTokenRef, + ): PeggedPrice { + assert(marginalGtPerLtE18 > 0n, 'marginal rate must be positive') + + const scale = BigInt(tokenLt.decimals + tokenGt.decimals) + const gtPerLtRaw = (marginalGtPerLtE18 * 10n ** scale) / ONE_E18 + + return new PeggedPrice(gtPerLtRaw, tokenLt, tokenGt) + } + + matchesTokens(tokenA: Address, tokenB: Address): boolean { + return ( + (tokenA.equal(this.tokenLt.address) && tokenB.equal(this.tokenGt.address)) || + (tokenA.equal(this.tokenGt.address) && tokenB.equal(this.tokenLt.address)) + ) + } + + equals(other: PeggedPrice): boolean { + return ( + this.gtPerLtRaw === other.gtPerLtRaw && + this.tokenLt.address.equal(other.tokenLt.address) && + this.tokenGt.address.equal(other.tokenGt.address) && + BigInt(this.tokenLt.decimals) === BigInt(other.tokenLt.decimals) && + BigInt(this.tokenGt.decimals) === BigInt(other.tokenGt.decimals) + ) + } + + /** + * Decimal string for **quote per 1 base**; rounded half-up to quote token decimals. + */ + toHuman(quoteToken: Address): string { + assert( + quoteToken.equal(this.tokenLt.address) || quoteToken.equal(this.tokenGt.address), + 'quote token must be one of the pair tokens', + ) + + const isQuoteLt = quoteToken.equal(this.tokenLt.address) + + const quoteDecimals = isQuoteLt ? this.tokenLt.decimals : this.tokenGt.decimals + const ltDecimals = BigInt(this.tokenLt.decimals) + const gtDecimals = BigInt(this.tokenGt.decimals) + const marginalE18 = this.toGtPerLtE18() + + const scaled = quoteToken.equal(this.tokenGt.address) + ? (marginalE18 * 10n ** ltDecimals) / ONE_E18 + : 10n ** (gtDecimals + 18n + ltDecimals) / (marginalE18 * 10n ** gtDecimals) + + const full = formatUnits(scaled, Number(quoteDecimals)) + + return truncateHumanDecimalString(full, Number(quoteDecimals)) + } + + /** Marginal gt-per-lt rate in 1e18 fixed-point. */ + toGtPerLtE18(): bigint { + const scale = BigInt(this.tokenLt.decimals + this.tokenGt.decimals) + + return (this.gtPerLtRaw * ONE_E18) / 10n ** scale + } + + toJSON(): PeggedPriceJSON { + return { + gtPerLtRaw: this.gtPerLtRaw.toString(), + tokenLt: { + address: this.tokenLt.address.toString(), + decimals: String(this.tokenLt.decimals), + }, + tokenGt: { + address: this.tokenGt.address.toString(), + decimals: String(this.tokenGt.decimals), + }, + } + } +} diff --git a/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/price/types.ts b/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/price/types.ts new file mode 100644 index 0000000..f875491 --- /dev/null +++ b/typescript/swap-vm/src/swap-vm/instructions/pegged-swap/price/types.ts @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: LicenseRef-Degensoft-SwapVM-1.1 + +import type { Address } from '@1inch/sdk-core' + +export type PeggedTokenRef = { + address: Address + decimals: number +} + +export type PeggedPricePair = { + quoteToken: PeggedTokenRef + baseToken: PeggedTokenRef +} + +export type PeggedTokenReserve = PeggedTokenRef & { + initialReserve: bigint + currentReserve: bigint +} + +export type PeggedReservesInput = { + reserveA: PeggedTokenReserve + reserveB: PeggedTokenReserve + linearWidth: bigint +} + +/** Snapshot from `PeggedPrice.prototype.toJSON` (bigints as decimal strings). */ +export type PeggedPriceJSON = { + gtPerLtRaw: string + tokenLt: { address: string; decimals: string } + tokenGt: { address: string; decimals: string } +} diff --git a/typescript/swap-vm/src/swap-vm/instructions/concentrate/bigint-sqrt.spec.ts b/typescript/swap-vm/src/swap-vm/instructions/utils/bigint-sqrt.test.ts similarity index 94% rename from typescript/swap-vm/src/swap-vm/instructions/concentrate/bigint-sqrt.spec.ts rename to typescript/swap-vm/src/swap-vm/instructions/utils/bigint-sqrt.test.ts index 619dcd9..22410aa 100644 --- a/typescript/swap-vm/src/swap-vm/instructions/concentrate/bigint-sqrt.spec.ts +++ b/typescript/swap-vm/src/swap-vm/instructions/utils/bigint-sqrt.test.ts @@ -1,6 +1,7 @@ // SPDX-License-Identifier: LicenseRef-Degensoft-SwapVM-1.1 import { UINT_256_MAX } from '@1inch/byte-utils' +import { describe, expect, it } from 'vitest' import { bigintSqrt } from './bigint-sqrt' describe('bigintSqrt', () => { diff --git a/typescript/swap-vm/src/swap-vm/instructions/concentrate/bigint-sqrt.ts b/typescript/swap-vm/src/swap-vm/instructions/utils/bigint-sqrt.ts similarity index 100% rename from typescript/swap-vm/src/swap-vm/instructions/concentrate/bigint-sqrt.ts rename to typescript/swap-vm/src/swap-vm/instructions/utils/bigint-sqrt.ts diff --git a/typescript/swap-vm/src/swap-vm/instructions/utils/index.ts b/typescript/swap-vm/src/swap-vm/instructions/utils/index.ts new file mode 100644 index 0000000..c6ed25f --- /dev/null +++ b/typescript/swap-vm/src/swap-vm/instructions/utils/index.ts @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: LicenseRef-Degensoft-SwapVM-1.1 + +export { bigintSqrt } from './bigint-sqrt' +export { truncateHumanDecimalString } from './truncate-human-decimal-string' diff --git a/typescript/swap-vm/src/swap-vm/instructions/concentrate/price/truncate-human-decimal-string.test.ts b/typescript/swap-vm/src/swap-vm/instructions/utils/truncate-human-decimal-string.test.ts similarity index 100% rename from typescript/swap-vm/src/swap-vm/instructions/concentrate/price/truncate-human-decimal-string.test.ts rename to typescript/swap-vm/src/swap-vm/instructions/utils/truncate-human-decimal-string.test.ts diff --git a/typescript/swap-vm/src/swap-vm/instructions/concentrate/price/truncate-human-decimal-string.ts b/typescript/swap-vm/src/swap-vm/instructions/utils/truncate-human-decimal-string.ts similarity index 100% rename from typescript/swap-vm/src/swap-vm/instructions/concentrate/price/truncate-human-decimal-string.ts rename to typescript/swap-vm/src/swap-vm/instructions/utils/truncate-human-decimal-string.ts diff --git a/typescript/swap-vm/tests/swap-vm.spec.ts b/typescript/swap-vm/tests/swap-vm.spec.ts index f954575..b8f0033 100644 --- a/typescript/swap-vm/tests/swap-vm.spec.ts +++ b/typescript/swap-vm/tests/swap-vm.spec.ts @@ -25,6 +25,7 @@ import { Opcode } from '../src/swap-vm/instructions/opcode.js' import type { PricePair } from '../src/swap-vm/instructions/concentrate' import { Price, PriceRange, TokenReserve } from '../src/swap-vm/instructions/concentrate' +import { PeggedPrice, PeggedSwapCalculator } from '../src/swap-vm/instructions/pegged-swap' describe('SwapVM', () => { let forkNode: ReadyEvmFork @@ -978,30 +979,47 @@ describe('SwapVM', () => { const USDC = new Address(ADDRESSES.USDC) const DAI = new Address(ADDRESSES.DAI) + const LINEAR_WIDTH = 8n * 10n ** 26n + + const peggedCalculator = PeggedSwapCalculator.new({ + tokenA: { address: DAI, decimals: 18 }, + tokenB: { address: USDC, decimals: 6 }, + }) + + const spot = PeggedPrice.fromHuman('1', { + quoteToken: { address: USDC, decimals: 6 }, + baseToken: { address: DAI, decimals: 18 }, + }) + + const initialBalances = peggedCalculator.computeFixedAllocation( + spot, + DAI, + parseUnits('100000', 18), + ) const amountsAndTokens = [ { - amount: parseUnits('100000', 6), + amount: initialBalances.reserveGt, token: USDC, }, { - amount: parseUnits('100000', 18), + amount: initialBalances.reserveLt, token: DAI, }, ] const program = AquaPeggedAmmStrategy.new({ tokenA: { - address: amountsAndTokens[0].token, + address: USDC, decimals: 6, - reserve: amountsAndTokens[0].amount, + reserve: initialBalances.reserveGt, }, tokenB: { - address: amountsAndTokens[1].token, + address: DAI, decimals: 18, - reserve: amountsAndTokens[1].amount, + reserve: initialBalances.reserveLt, }, - linearWidth: 8n * 10n ** 26n, + linearWidth: LINEAR_WIDTH, }) .withFeeTokenIn(1) .build()