Skip to content
Draft
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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@
path = lib/openzeppelin-contracts-upgradeable
url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable
branch = "release-v4.9"
[submodule "lib/solady"]
path = lib/solady
url = https://github.com/Vectorized/solady
1 change: 1 addition & 0 deletions lib/solady
Submodule solady added at 1f43cc
7 changes: 3 additions & 4 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/
ds-test/=lib/solstat/lib/forge-std/lib/ds-test/src/
erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/
forge-std/=lib/forge-std/src/
openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/
openzeppelin-contracts/=lib/openzeppelin-contracts/
openzeppelin/=lib/openzeppelin-contracts/contracts/
@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
@openzeppelin/contracts-upgradeable=lib/openzeppelin-contracts-upgradeable/contracts
pendle=lib/pendle-core-v2-public/contracts/
pendle/=lib/pendle-core-v2-public/contracts/
solady/=lib/solady/src/
solmate/=lib/solstat/lib/solmate/src/
solstat/=lib/solstat/src/
38 changes: 21 additions & 17 deletions src/LiquidityManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pragma solidity ^0.8.13;

import {IStandardizedYield} from "pendle/interfaces/IStandardizedYield.sol";
import {PYIndexLib, PYIndex} from "pendle/core/StandardizedYield/PYIndex.sol";
import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol";
import {FixedPointMathLib} from "solady/utils/FixedPointMathLib.sol";
import {SafeTransferLib, ERC20} from "solmate/utils/SafeTransferLib.sol";

import {RMM, IPYieldToken, Gaussian, computeTradingFunction} from "./RMM.sol";
Expand All @@ -15,21 +15,28 @@ contract LiquidityManager {
using FixedPointMathLib for uint256;
using SafeTransferLib for ERC20;

function mintSY(address SY, address receiver, address tokenIn, uint256 amountTokenToDeposit, uint256 minSharesOut)
public
payable
returns (uint256 amountOut)
{
function mintSY(
address SY,
address receiver,
address tokenIn,
uint256 amountTokenToDeposit,
uint256 minSharesOut
) public payable returns (uint256 amountOut) {
IStandardizedYield sy = IStandardizedYield(SY);
if (!sy.isValidTokenIn(tokenIn)) revert InvalidTokenIn(tokenIn);

if (!sy.isValidTokenIn(tokenIn)) {
revert InvalidTokenIn(tokenIn);
}

if (msg.value > 0 && sy.isValidTokenIn(address(0))) {
// SY minted check is done in this function instead of relying on the SY contract's deposit().
// Mint SY using ETH
amountOut += sy.deposit{value: msg.value}(address(this), address(0), msg.value, 0);
}

if (tokenIn != address(0)) {
// Transfer tokens from sender to this contract
ERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountTokenToDeposit);
// Mint SY using the transferred tokens
amountOut += sy.deposit(receiver, tokenIn, amountTokenToDeposit, 0);
}

Expand Down Expand Up @@ -76,13 +83,10 @@ contract LiquidityManager {
sy.approve(address(args.rmm), args.amountIn);

// swap syToSwap for pt
rmm.swapExactSyForPt(syToSwap, args.minOut, address(this));
uint256 syBal = sy.balanceOf(address(this));
sy.approve(address(args.rmm), syBal);
uint256 ptBal = pt.balanceOf(address(this));
(uint256 ptOut,) = rmm.swapExactSyForPt(syToSwap, args.minOut, address(this));

pt.approve(address(args.rmm), ptBal);
liquidity = rmm.allocate(true, syBal, args.minLiquidityDelta, msg.sender);
pt.approve(address(args.rmm), ptOut);
liquidity = ptOut > sy.balanceOf(address(this)) ? rmm.allocate(true, sy.balanceOf(address(this)), args.minLiquidityDelta, msg.sender) : rmm.allocate(false, ptOut, args.minLiquidityDelta, msg.sender);
}

function allocateFromPt(AllocateArgs calldata args) external returns (uint256 liquidity) {
Expand Down Expand Up @@ -115,8 +119,8 @@ contract LiquidityManager {
// swap ptToSwap for sy
(uint256 syOut,) = rmm.swapExactPtForSy(ptToSwap, args.minOut, address(this));

sy.approve(address(rmm), type(uint256).max);
liquidity = rmm.allocate(false, pt.balanceOf(address(this)), args.minLiquidityDelta, msg.sender);
sy.approve(address(rmm), syOut);
liquidity = rmm.allocate(true, syOut, args.minLiquidityDelta, msg.sender);
}

struct ComputeArgs {
Expand Down Expand Up @@ -176,7 +180,7 @@ contract LiquidityManager {
}

function isAApproxB(uint256 a, uint256 b, uint256 eps) internal pure returns (bool) {
return b.mulWadDown(1 ether - eps) <= a && a <= b.mulWadDown(1 ether + eps);
return b.mulWad(1 ether - eps) <= a && a <= b.mulWad(1 ether + eps);
}

function calcMaxPtOut(
Expand Down
31 changes: 14 additions & 17 deletions src/RMM.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {PYIndexLib, PYIndex} from "pendle/core/StandardizedYield/PYIndex.sol";
import {IPPrincipalToken} from "pendle/interfaces/IPPrincipalToken.sol";
import {IStandardizedYield} from "pendle/interfaces/IStandardizedYield.sol";
import {IPYieldToken} from "pendle/interfaces/IPYieldToken.sol";
import "forge-std/console2.sol";

import "./lib/RmmLib.sol";
import "./lib/RmmErrors.sol";
Expand Down Expand Up @@ -180,7 +179,7 @@ contract RMM is ERC20 {
uint256 upperBound,
uint256 epsilon,
address to
) external payable lock returns (uint256 amountInWad, uint256 amountOutWad, int256 deltaLiquidity) {
) external payable lock returns (uint256 ytOut, uint256 amountInWad, uint256 amountOutWad, int256 deltaLiquidity) {
SwapToYt memory swap;
swap.tokenIn = token;
swap.amountTokenIn = token == address(0) ? 0 : amountTokenIn;
Expand All @@ -193,27 +192,25 @@ contract RMM is ERC20 {

PYIndex index = YT.newIndex();
uint256 strike_;
uint256 amountOut;

swap.amountPtIn = computeSYToYT(index, swap.realSyMinted, upperBound, block.timestamp, swap.amountPtIn, epsilon);

(amountInWad, amountOutWad, amountOut, deltaLiquidity, strike_) =
(amountInWad, amountOutWad, ytOut, deltaLiquidity, strike_) =
prepareSwapPtIn(swap.amountPtIn, block.timestamp, index);

_adjust(-toInt(amountOutWad), toInt(amountInWad), deltaLiquidity, strike_, index);

// SY is needed to cover the minted PT, so we need to debit the delta from the msg.sender
swap.realYtOut = amountOut + (index.assetToSyUp(amountInWad) - amountOutWad);
ytOut += (index.assetToSyUp(amountInWad) - amountOutWad);

// Converts the SY received from minting it into its components PT and YT.
amountOut = mintPtYt(swap.realYtOut, address(this));
swap.realYtOut = amountOut;
ytOut = mintPtYt(ytOut, address(this));

if (swap.realYtOut < swap.minYtOut) {
revert InsufficientOutput(amountInWad, swap.minYtOut, swap.realYtOut);
if (ytOut < swap.minYtOut) {
revert InsufficientOutput(amountInWad, swap.minYtOut, ytOut);
}

_credit(address(YT), to, swap.realYtOut);
_credit(address(YT), to, ytOut);

uint256 debitSurplus = address(this).balance;
if (debitSurplus > 0) {
Expand All @@ -226,7 +223,7 @@ contract RMM is ERC20 {
address(SY),
address(YT),
swap.amountTokenIn + swap.amountNativeIn - debitSurplus,
swap.realYtOut,
ytOut,
deltaLiquidity
);
}
Expand Down Expand Up @@ -443,11 +440,11 @@ contract RMM is ERC20 {
int256 rt = int256(lastImpliedPrice) * int256(timeToExpiry) / int256(365 * 86400);
int256 lastPrice = rt.expWad();

uint256 a = sigma_.mulWadDown(sigma_).mulWadDown(tau_).mulWadDown(0.5 ether);
uint256 a = sigma_.mulWad(sigma_).mulWad(tau_).mulWad(0.5 ether);
// // $$\Phi^{-1} (1 - \frac{x}{L})$$
int256 b = Gaussian.ppf(int256(1 ether - reserveX_.divWadDown(liquidity)));
int256 b = Gaussian.ppf(int256(1 ether - reserveX_.divWad(liquidity)));
int256 exp = (b * (int256(computeSigmaSqrtTau(sigma_, tau_))) / 1e18 - int256(a)).expWad();
return uint256(lastPrice).divWadDown(uint256(exp));
return uint256(lastPrice).divWad(uint256(exp));
}

function computeTokenToYT(
Expand Down Expand Up @@ -592,7 +589,7 @@ contract RMM is ERC20 {
(deltaXWad, deltaLiquidity) = computeAllocationGivenDeltaY(deltaYWad, reserveX, reserveY, totalLiquidity);
}

lptMinted = deltaLiquidity.mulDivDown(totalSupply, totalLiquidity + deltaLiquidity);
lptMinted = deltaLiquidity.mulDiv(totalSupply, totalLiquidity + deltaLiquidity);
}

function prepareDeallocate(uint256 deltaLiquidity)
Expand All @@ -601,8 +598,8 @@ contract RMM is ERC20 {
returns (uint256 deltaXWad, uint256 deltaYWad, uint256 lptBurned)
{
uint256 liquidity = totalLiquidity;
deltaXWad = deltaLiquidity.mulDivDown(reserveX, liquidity);
deltaYWad = deltaLiquidity.mulDivDown(reserveY, liquidity);
deltaXWad = deltaLiquidity.mulDiv(reserveX, liquidity);
deltaYWad = deltaLiquidity.mulDiv(reserveY, liquidity);
lptBurned = deltaLiquidity.mulDivUp(totalSupply, liquidity);
}

Expand Down
6 changes: 3 additions & 3 deletions src/lib/LiquidityLib.sol
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.13;

import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol";
import {FixedPointMathLib} from "solady/utils/FixedPointMathLib.sol";

using FixedPointMathLib for uint256;
using FixedPointMathLib for int256;

function computeDeltaLGivenDeltaX(uint256 deltaX, uint256 liquidity, uint256 reserveX) pure returns (uint256 deltaL) {
return liquidity.mulDivDown(deltaX, reserveX);
return liquidity.mulDiv(deltaX, reserveX);
}

function computeDeltaLGivenDeltaY(uint256 deltaY, uint256 liquidity, uint256 reserveY) pure returns (uint256 deltaL) {
return liquidity.mulDivDown(deltaY, reserveY);
return liquidity.mulDiv(deltaY, reserveY);
}

function computeDeltaYGivenDeltaX(uint256 deltaX, uint256 reserveX, uint256 reserveY) pure returns (uint256 deltaY) {
Expand Down
42 changes: 17 additions & 25 deletions src/lib/RmmLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pragma solidity ^0.8.13;

import {Gaussian} from "solstat/Gaussian.sol";
import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol";
import {FixedPointMathLib} from "solady/utils/FixedPointMathLib.sol";
import {ERC20} from "solmate/tokens/ERC20.sol";
import {ToUintOverflow, ToIntOverflow} from "./RmmErrors.sol";

Expand All @@ -17,7 +17,7 @@ struct PoolPreCompute {


function computeLnSDivK(uint256 S, uint256 strike_) pure returns (int256) {
return int256(S.divWadDown(strike_)).lnWad();
return int256(S.divWad(strike_)).lnWad();
}

/// @dev Computes σ√τ given `sigma_` σ and `tau` τ.
Expand All @@ -28,7 +28,7 @@ function computeSigmaSqrtTau(uint256 sigma_, uint256 tau_) pure returns (uint256

/// @dev Converts seconds (units of block.timestamp) into years in WAD units.
function computeTauWadYears(uint256 tauSeconds) pure returns (uint256) {
return tauSeconds.mulDivDown(1e18, 365 days);
return tauSeconds.mulDiv(1e18, 365 days);
}

/// @dev k = Φ⁻¹(x/L) + Φ⁻¹(y/μL) + σ√τ
Expand Down Expand Up @@ -60,7 +60,7 @@ function computeSpotPrice(uint256 reserveX_, uint256 totalLiquidity_, uint256 st
returns (uint256)
{
// Φ^-1(1 - x/L)
int256 a = Gaussian.ppf(int256(1 ether - reserveX_.divWadDown(totalLiquidity_)));
int256 a = Gaussian.ppf(int256(1 ether - reserveX_.divWad(totalLiquidity_)));
// σ√τ
int256 b = toInt(computeSigmaSqrtTau(sigma_, tau_));
// 1/2σ^2τ
Expand All @@ -76,7 +76,7 @@ function computeY(uint256 reserveX_, uint256 liquidity, uint256 strike_, uint256
pure
returns (uint256)
{
int256 a = Gaussian.ppf(toInt(1 ether - reserveX_.divWadDown(liquidity)));
int256 a = Gaussian.ppf(toInt(1 ether - reserveX_.divWad(liquidity)));
int256 b = tau_ != 0 ? toInt(computeSigmaSqrtTau(sigma_, tau_)) : int256(0);
int256 c = Gaussian.cdf(a - b);

Expand Down Expand Up @@ -133,7 +133,7 @@ function computeLGivenX(uint256 reserveX_, uint256 S, uint256 strike_, uint256 s
{
int256 lnSDivK = computeLnSDivK(S, strike_);
uint256 sigmaSqrtTau = computeSigmaSqrtTau(sigma_, tau_);
uint256 halfSigmaSquaredTau = sigma_.mulWadDown(sigma_).mulWadDown(0.5 ether).mulWadDown(tau_);
uint256 halfSigmaSquaredTau = sigma_.mulWad(sigma_).mulWad(0.5 ether).mulWad(tau_);
int256 d1 = 1 ether * (lnSDivK + int256(halfSigmaSquaredTau)) / int256(sigmaSqrtTau);
uint256 cdf = uint256(Gaussian.cdf(d1));

Expand Down Expand Up @@ -207,7 +207,7 @@ function computeDeltaLXIn(
) pure returns (uint256 deltaL) {
uint256 fees = swapFee.mulWadUp(amountIn);
uint256 px = computeSpotPrice(reserveX, totalLiquidity, strike, sigma, tau);
deltaL = px.mulWadUp(totalLiquidity).mulWadUp(fees).divWadDown(px.mulWadDown(reserveX) + reserveY);
deltaL = px.mulWadUp(totalLiquidity).mulWadUp(fees).divWad(px.mulWad(reserveX) + reserveY);
}

function computeDeltaLYOut(
Expand All @@ -222,7 +222,7 @@ function computeDeltaLYOut(
) pure returns (uint256 deltaL) {
uint256 fees = swapFee.mulWadUp(amountOut);
uint256 px = computeSpotPrice(reserveX, totalLiquidity, strike, sigma, tau);
deltaL = px.mulWadUp(totalLiquidity).mulWadUp(fees).divWadDown(px.mulWadDown(reserveX) + reserveY);
deltaL = px.mulWadUp(totalLiquidity).mulWadUp(fees).divWad(px.mulWad(reserveX) + reserveY);
}

function computeDeltaLYIn(
Expand All @@ -237,7 +237,7 @@ function computeDeltaLYIn(
) pure returns (uint256 deltaL) {
uint256 fees = swapFee.mulWadUp(amountIn);
uint256 px = computeSpotPrice(reserveX, totalLiquidity, strike, sigma, tau);
deltaL = totalLiquidity.mulWadUp(fees).divWadDown(px.mulWadDown(reserveX) + reserveY);
deltaL = totalLiquidity.mulWadUp(fees).divWad(px.mulWad(reserveX) + reserveY);
}

function findRootNewLiquidity(bytes memory args, uint256 initialGuess, uint256 maxIterations, uint256 tolerance)
Expand Down Expand Up @@ -284,7 +284,7 @@ function findRootNewX(bytes memory args, uint256 initialGuess, uint256 maxIterat

reserveX_next = int256(reserveX_) - fx * 1e18 / dfx;

if (abs(int256(reserveX_) - reserveX_next) <= int256(tolerance) || abs(fx) <= int256(tolerance)) {
if (FixedPointMathLib.abs(int256(reserveX_) - reserveX_next) <= tolerance || FixedPointMathLib.abs(fx) <= tolerance) {
reserveX_ = uint256(reserveX_next);
break;
}
Expand All @@ -310,7 +310,7 @@ function findRootNewY(bytes memory args, uint256 initialGuess, uint256 maxIterat

reserveY_next = int256(reserveY_) - fx * 1e18 / dfx;

if (abs(int256(reserveY_) - reserveY_next) <= int256(tolerance) || abs(fx) <= int256(tolerance)) {
if (FixedPointMathLib.abs(int256(reserveY_) - reserveY_next) <= tolerance || FixedPointMathLib.abs(fx) <= tolerance) {
reserveY_ = uint256(reserveY_next);
break;
}
Expand All @@ -324,10 +324,10 @@ function computeTfDL(bytes memory args, uint256 L) pure returns (int256) {
int256 x = int256(rX);
int256 y = int256(rY);
int256 mu = int256(K);
int256 L_squared = int256(L.mulWadDown(L));
int256 L_squared = int256(L.mulWad(L));

int256 a = Gaussian.ppf(int256(rX.divWadUp(L)));
int256 b = Gaussian.ppf(int256(rY.divWadUp(L.mulWadUp(K))));
int256 a = Gaussian.ppf(int256(rX.divWad(L)));
int256 b = Gaussian.ppf(int256(rY.divWad(L.mulWad(K))));

int256 pdf_a = Gaussian.pdf(a);
int256 pdf_b = Gaussian.pdf(b);
Expand Down Expand Up @@ -458,12 +458,12 @@ function sum(uint256 a, int256 b) pure returns (uint256) {

/// @dev Converts native decimal amount to WAD amount, rounding down.
function upscale(uint256 amount, uint256 scalingFactor) pure returns (uint256) {
return FixedPointMathLib.mulWadDown(amount, scalingFactor);
return FixedPointMathLib.mulWad(amount, scalingFactor);
}

/// @dev Converts a WAD amount to a native DECIMAL amount, rounding down.
function downscaleDown(uint256 amount, uint256 scalar_) pure returns (uint256) {
return FixedPointMathLib.divWadDown(amount, scalar_);
return FixedPointMathLib.divWad(amount, scalar_);
}

/// @dev Converts a WAD amount to a native DECIMAL amount, rounding up.
Expand All @@ -479,14 +479,6 @@ function toUint(int256 x) pure returns (uint256) {
return uint256(x);
}

function abs(int256 x) pure returns (int256) {
if (x < 0) {
return -x;
} else {
return x;
}
}

/// @dev Computes the scalar to multiply to convert between WAD and native units.
function scalar(address token) view returns (uint256) {
uint256 decimals = ERC20(token).decimals();
Expand All @@ -495,5 +487,5 @@ function scalar(address token) view returns (uint256) {
}

function isASmallerApproxB(uint256 a, uint256 b, uint256 eps) pure returns (bool) {
return a <= b && a >= b.mulWadDown(1e18 - eps);
return a <= b && a >= b.mulWad(1e18 - eps);
}
2 changes: 1 addition & 1 deletion test/invariant/RMMHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats {
);

weth.approve(address(rmm), amountTokenIn);
(uint256 amountInWad, uint256 amountOutWad, int256 deltaLiquidity) = rmm.swapExactTokenForYt(
(, uint256 amountInWad, uint256 amountOutWad, int256 deltaLiquidity) = rmm.swapExactTokenForYt(
address(weth),
amountTokenIn,
ytOut,
Expand Down
7 changes: 2 additions & 5 deletions test/invariant/RMMInvariants.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,12 @@ contract RMMInvariantsTest is SetUp {
setUpRMM(getDefaultParams());
handler = new RMMHandler(rmm, PT, SY, YT, weth);

vm.deal(address(this), 10_000 ether);
weth.deposit{value: 10_000 ether}();
weth.transfer(address(handler), 10_000 ether);

mintSY(address(this), 5_000 ether);
mintPY(address(this), 1_000 ether);

mintSY(address(handler), 100_000 ether);
mintPY(address(handler), 50_000 ether);

mintPY(address(handler), 100_000 ether);

handler.init();

Expand Down
Loading