Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 137 additions & 0 deletions src/instructions/PeggedSwap.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// SPDX-License-Identifier: LicenseRef-Degensoft-ARSL-1.0-Audit

pragma solidity 0.8.30;

import { Calldata } from "../libs/Calldata.sol";
import { Context, ContextLib } from "../libs/VM.sol";
import { PeggedSwapMath } from "../libs/PeggedSwapMath.sol";

library PeggedSwapArgsBuilder {
/// @notice Arguments for the pegged swap instruction (stored in program)
/// @param x0 Initial X reserve (normalization factor for x)
/// @param y0 Initial Y reserve (normalization factor for y)
/// @param linearWidth Linear component coefficient A (* 1e18, e.g., 0.8e18 for A=0.8)
/// @dev Curvature is hardcoded to p=0.5 for optimal gas efficiency and proven behavior
struct Args {
uint256 x0;
uint256 y0;
uint256 linearWidth;
}

function build(Args memory args) internal pure returns (bytes memory) {
return abi.encodePacked(
args.x0,
args.y0,
args.linearWidth
);
}

function parse(bytes calldata data) internal pure returns (Args calldata args) {
assembly ("memory-safe") {
args := data.offset // Zero-copy to calldata pointer casting
}
}
}


/// @title PeggedSwap - Square-root linear swap curve for pegged assets
/// @notice Formula: √(x/X₀) + √(y/Y₀) + A(x/X₀ + y/Y₀) = 1 + A
/// @notice Optimized for pegged assets (stablecoins, wrapped tokens, etc.)
/// @notice Calculates swap output directly using analytical solution with square root curve (p=0.5)
contract PeggedSwap {
using Calldata for bytes;
using ContextLib for Context;

uint256 private constant ONE = 1e18;

error PeggedSwapInvalidArgs();
error PeggedSwapInvalidBalances();

function _divRoundUp(uint256 a, uint256 b) private pure returns (uint256) {
return (a + b - 1) / b;
}

/// @dev Square-root linear swap with direct calculation
/// @param ctx Swap context
/// @param args Swap configuration (X0, Y0, linearWidth) - 96 bytes
/// @notice Calculates output amount directly using analytical solution
function _peggedSwapGrowPriceRange2D(Context memory ctx, bytes calldata args) internal pure {
require(args.length >= 96, PeggedSwapInvalidArgs()); // 3 * 32 bytes

PeggedSwapArgsBuilder.Args calldata config = PeggedSwapArgsBuilder.parse(args);

require(config.x0 > 0 && config.y0 > 0, PeggedSwapInvalidArgs());
require(config.linearWidth <= 2 * ONE, PeggedSwapInvalidArgs()); // A <= 2.0

uint256 x0 = ctx.swap.balanceIn;
uint256 y0 = ctx.swap.balanceOut;

require(x0 > 0 && y0 > 0, PeggedSwapInvalidBalances());

// ╔═══════════════════════════════════════════════════════════════════════════╗
// ║ PEGGED SWAP CURVE FOR PEGGED ASSETS ║
// ║ ║
// ║ Formula: √(x/X₀) + √(y/Y₀) + A(x/X₀ + y/Y₀) = 1 + A ║
// ║ ║
// ║ Where: ║
// ║ - x, y are current reserves (in SwapVM: balanceIn, balanceOut) ║
// ║ - X₀, Y₀ are initial reserves (normalization factors) ║
// ║ - A is linear width parameter (0 to 2.0) ║
// ║ - Curvature p=0.5 is hardcoded for analytical solution ║
// ║ ║
// ║ Benefits for pegged assets: ║
// ║ - Minimal slippage near 1:1 price (when A > 0) ║
// ║ - Smooth price protection at extremes ║
// ║ - Analytical solution - no iterative solving needed ║
// ║ ║
// ║ Parameters guide: ║
// ║ - For stablecoins (USDC/USDT): A ≈ 0.8-1.5 ║
// ║ - For wrapped tokens (WETH/stETH): A ≈ 0.3-0.6 ║
// ║ - For volatile pairs: A ≈ 0.0-0.2 ║
// ╚═══════════════════════════════════════════════════════════════════════════╝

// Calculate target invariant from initial state
uint256 targetInvariant = PeggedSwapMath.invariantFromReserves(
x0,
y0,
config.x0,
config.y0,
config.linearWidth
);

// Calculate new state based on swap direction
uint256 x1;
uint256 y1;

if (ctx.query.isExactIn) {
// ExactIn: calculate y1 from x1 = x0 + amountIn
x1 = x0 + ctx.swap.amountIn;

// Solve for y1: given x1, find y1 that maintains invariant
uint256 u1 = (x1 * ONE) / config.x0; // Round DOWN u1
uint256 v1 = PeggedSwapMath.solve(u1, config.linearWidth, targetInvariant);

// Round UP y1 to ensure amountOut rounds DOWN
y1 = _divRoundUp(v1 * config.y0, ONE);

ctx.swap.amountOut = y0 - y1;
} else {
// ExactOut: calculate x1 from y1 = y0 - amountOut
y1 = y0 - ctx.swap.amountOut;

// Solve for x1: given y1, find x1 that maintains invariant
uint256 v1 = (y1 * ONE) / config.y0; // Round DOWN v1 (conservative)
uint256 u1 = PeggedSwapMath.solve(v1, config.linearWidth, targetInvariant);

// Round UP x1 to ensure amountIn rounds UP
x1 = _divRoundUp(u1 * config.x0, ONE);

ctx.swap.amountIn = x1 - x0;
}

// Update balances
ctx.swap.balanceIn = x1;
ctx.swap.balanceOut = y1;
}
}

130 changes: 130 additions & 0 deletions src/libs/PeggedSwapMath.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// SPDX-License-Identifier: LicenseRef-Degensoft-ARSL-1.0-Audit

pragma solidity 0.8.30;

/// @title PeggedSwapMath - Complete math library for PeggedSwap
/// @notice Provides all mathematical operations for PeggedSwap curve (p=0.5)
/// @notice Formula: √u + √v + A(u + v) = C
library PeggedSwapMath {
uint256 private constant ONE = 1e18;

error PeggedSwapMathNoSolution();
error PeggedSwapMathInvalidInput();

/// @notice Calculate invariant value: √u + √v + A(u + v)
/// @param u Normalized x value (x/X₀) scaled by 1e18
/// @param v Normalized y value (y/Y₀) scaled by 1e18
/// @param a Linear width parameter scaled by 1e18
/// @return Invariant value scaled by 1e18
function invariant(uint256 u, uint256 v, uint256 a) internal pure returns (uint256) {
uint256 sqrtU = sqrt(u);
uint256 sqrtV = sqrt(v);
uint256 linearTerm = (a * (u + v)) / ONE;
return sqrtU + sqrtV + linearTerm;
}

/// @notice Calculate invariant from actual reserves
/// @param x Current x reserve
/// @param y Current y reserve
/// @param x0 Initial X reserve (normalization factor)
/// @param y0 Initial Y reserve (normalization factor)
/// @param a Linear width parameter scaled by 1e18
/// @return Invariant value scaled by 1e18
function invariantFromReserves(
uint256 x,
uint256 y,
uint256 x0,
uint256 y0,
uint256 a
) internal pure returns (uint256) {
uint256 u = (x * ONE) / x0;
uint256 v = (y * ONE) / y0;
return invariant(u, v, a);
}

/// @notice Solve for v analytically using square root curve (p=0.5)
/// @dev √u + √v + a(u + v) = c
/// @dev Rearranges to: √v + av = c - √u - au
/// @dev Let w = √v, then: aw² + w = [c - √u - au]
/// @dev Quadratic in w: aw² + w - rightSide = 0
/// @dev Solution: w = (-1 + √(1 + 4a * rightSide)) / (2a)
/// @param u Normalized x value (x/X₀) scaled by 1e18
/// @param a Linear width parameter scaled by 1e18
/// @param invariantC Target invariant constant scaled by 1e18
/// @return v Normalized y value (y/Y₀) scaled by 1e18
function solve(uint256 u, uint256 a, uint256 invariantC) internal pure returns (uint256 v) {
// Calculate √u with safe handling
uint256 sqrtU = sqrt(u);

// Calculate au safely
uint256 au = (a * u) / ONE;

// Calculate rightSide = c - √u - au
// Need to check: invariantC >= sqrtU + au
uint256 sqrtUPlusAu = sqrtU + au;
require(invariantC >= sqrtUPlusAu, PeggedSwapMathInvalidInput());

uint256 rightSide = invariantC - sqrtUPlusAu;

if (a == 0) {
// Special case: a = 0
// Equation becomes: √v = rightSide
// So: v = rightSide²
v = (rightSide * rightSide) / ONE;
return v;
}

// General case: aw² + w - rightSide = 0
// Quadratic formula: w = (-1 ± √(1 + 4a·rightSide)) / (2a)
// We want the positive root

// Calculate 4a * rightSide carefully to avoid overflow
uint256 fourARightSide = (4 * a * rightSide) / ONE;

// Calculate discriminant: 1 + 4a * rightSide
uint256 discriminant = ONE + fourARightSide;

// Calculate √discriminant
uint256 sqrtDiscriminant = sqrt(discriminant);

// w = (-1 + √discriminant) / (2a)
// sqrtDiscriminant should always be >= 1 since discriminant >= 1
require(sqrtDiscriminant >= ONE, PeggedSwapMathNoSolution());

// numerator = sqrtDiscriminant - 1 (in 1e18 scale)
uint256 numerator = sqrtDiscriminant - ONE;

// denominator = 2a (in 1e18 scale)
uint256 denominator = 2 * a;

// w = numerator * 1e18 / denominator
uint256 w = (numerator * ONE) / denominator;

// v = w² (both scaled by 1e18)
v = (w * w) / ONE;
}

/// @notice Integer square root using Newton's method with proper 1e18 scaling
/// @dev Computes sqrt(x) where both x and result are scaled by 1e18
/// @dev We need: y such that (y/1e18)² = x/1e18, so y² = x * 1e18
/// @dev To avoid overflow, we compute: y = sqrt(x) * 1e9 (since sqrt(1e18) = 1e9)
/// @param x Value to take square root of (scaled by 1e18)
/// @return y Square root of x (scaled by 1e18)
function sqrt(uint256 x) internal pure returns (uint256 y) {
if (x == 0) return 0;
if (x == ONE) return ONE;

// Compute sqrt(x * 1e18), avoid overflow, compute sqrt(x) first, then adjust scaling
uint256 z = (x + 1) / 2;
y = x;

while (z < y) {
y = z;
z = (x / z + z) / 2;
}

// Result scaled by 1e18, so multiply by 1e9
// result = y * 1e9 = sqrt(realValue) * 1e18
y = y * 1e9;
}
}
9 changes: 6 additions & 3 deletions src/opcodes/Opcodes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { BaseFeeAdjuster } from "../instructions/BaseFeeAdjuster.sol";
import { TWAPSwap } from "../instructions/TWAPSwap.sol";
import { Fee } from "../instructions/Fee.sol";
import { Extruction } from "../instructions/Extruction.sol";
import { PeggedSwap } from "../instructions/PeggedSwap.sol";

contract Opcodes is
Controls,
Expand All @@ -35,14 +36,15 @@ contract Opcodes is
BaseFeeAdjuster,
TWAPSwap,
Fee,
Extruction
Extruction,
PeggedSwap
{
constructor(address aqua) Fee(aqua) {}

function _notInstruction(Context memory /* ctx */, bytes calldata /* args */) internal view {}

function _opcodes() internal pure virtual returns (function(Context memory, bytes calldata) internal[] memory result) {
function(Context memory, bytes calldata) internal[43] memory instructions = [
function(Context memory, bytes calldata) internal[44] memory instructions = [
_notInstruction,
// Debug - reserved for debugging utilities (core infrastructure)
_notInstruction,
Expand Down Expand Up @@ -98,7 +100,8 @@ contract Opcodes is
Fee._progressiveFeeInXD,
Fee._progressiveFeeOutXD,
Fee._protocolFeeAmountOutXD,
Fee._aquaProtocolFeeAmountOutXD
Fee._aquaProtocolFeeAmountOutXD,
PeggedSwap._peggedSwapGrowPriceRange2D
];

// Efficiently turning static memory array into dynamic memory array
Expand Down
Loading
Loading