From bc54a6e96688d71f5c5294019a75ace5f3c1c6a2 Mon Sep 17 00:00:00 2001 From: clemlak Date: Mon, 24 Jun 2024 17:55:09 +0400 Subject: [PATCH 001/116] test: add test_swapExactSyForYt_AdjustsReserves --- test/unit/SwapExactSyForYt.t.sol | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/unit/SwapExactSyForYt.t.sol b/test/unit/SwapExactSyForYt.t.sol index 2d93091..db7ff24 100644 --- a/test/unit/SwapExactSyForYt.t.sol +++ b/test/unit/SwapExactSyForYt.t.sol @@ -43,4 +43,21 @@ contract SwapExactSyForYtTest is SetUp { assertEq(ERC20(address(YT)).balanceOf(state.to), preBalances[1] + amtOut); assertEq(ERC20(address(SY)).balanceOf(address(rmm)), preBalances[2] - amountOutWad); } + + function test_swapExactSyForYt_AdjustsReserves() public useSYPool withSY(address(this), 10 ether) { + uint256 preReserveX = rmm.reserveX(); + uint256 preReserveY = rmm.reserveY(); + uint256 preTotalLiquidity = rmm.totalLiquidity(); + + PYIndex index = YT.newIndex(); + uint256 exactSYIn = 1 ether; + uint256 ytOut = rmm.computeSYToYT(index, exactSYIn, 500 ether, block.timestamp, 0, 10_000); + (uint256 amountInWad, uint256 amountOutWad,, int256 deltaLiquidity,) = + rmm.prepareSwapPtIn(ytOut, block.timestamp, index); + rmm.swapExactSyForYt(exactSYIn, ytOut, ytOut.mulDivDown(95, 100), 500 ether, 10_000, address(this)); + + assertEq(rmm.reserveX(), preReserveX - amountOutWad); + assertEq(rmm.reserveY(), preReserveY + amountInWad); + assertEq(rmm.totalLiquidity(), preTotalLiquidity + uint256(deltaLiquidity)); + } } From 126616feb351862d7d5abc7c90742eae3e03f777 Mon Sep 17 00:00:00 2001 From: clemlak Date: Mon, 24 Jun 2024 18:04:57 +0400 Subject: [PATCH 002/116] test: add test_swapExactSyForYt_RevertsWhenExcessInput --- test/unit/SwapExactSyForYt.t.sol | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/unit/SwapExactSyForYt.t.sol b/test/unit/SwapExactSyForYt.t.sol index db7ff24..75d4508 100644 --- a/test/unit/SwapExactSyForYt.t.sol +++ b/test/unit/SwapExactSyForYt.t.sol @@ -5,6 +5,7 @@ import {ERC20} from "solmate/tokens/ERC20.sol"; import {PYIndexLib, IPYieldToken, PYIndex} from "pendle/core/StandardizedYield/PYIndex.sol"; import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol"; import {SetUp} from "../SetUp.sol"; +import {ExcessInput} from "../../src/lib/RmmErrors.sol"; contract SwapExactSyForYtTest is SetUp { using PYIndexLib for IPYieldToken; @@ -60,4 +61,14 @@ contract SwapExactSyForYtTest is SetUp { assertEq(rmm.reserveY(), preReserveY + amountInWad); assertEq(rmm.totalLiquidity(), preTotalLiquidity + uint256(deltaLiquidity)); } + + function test_swapExactSyForYt_RevertsWhenExcessInput() public useSYPool withSY(address(this), 10 ether) { + uint256 exactSYIn = 1 ether; + PYIndex index = YT.newIndex(); + uint256 ytOut = rmm.computeSYToYT(index, exactSYIn, 500 ether, block.timestamp, 0, 10_000); + (uint256 amountInWad, uint256 amountOutWad,,,) = rmm.prepareSwapPtIn(ytOut, block.timestamp, index); + + vm.expectRevert(); + rmm.swapExactSyForYt(exactSYIn - 1 ether, ytOut, amountOutWad, 500 ether, 10_000, address(this)); + } } From e1c48136501e26803b7b0781508a9e870605618f Mon Sep 17 00:00:00 2001 From: clemlak Date: Mon, 24 Jun 2024 18:05:13 +0400 Subject: [PATCH 003/116] test: add test_swapExactSyForYt_RevertsWhenInsufficientOutput --- test/unit/SwapExactSyForYt.t.sol | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/unit/SwapExactSyForYt.t.sol b/test/unit/SwapExactSyForYt.t.sol index 75d4508..aee25dd 100644 --- a/test/unit/SwapExactSyForYt.t.sol +++ b/test/unit/SwapExactSyForYt.t.sol @@ -71,4 +71,14 @@ contract SwapExactSyForYtTest is SetUp { vm.expectRevert(); rmm.swapExactSyForYt(exactSYIn - 1 ether, ytOut, amountOutWad, 500 ether, 10_000, address(this)); } + + function test_swapExactSyForYt_RevertsWhenInsufficientOutput() public useSYPool withSY(address(this), 10 ether) { + uint256 exactSYIn = 1 ether; + PYIndex index = YT.newIndex(); + uint256 ytOut = rmm.computeSYToYT(index, exactSYIn, 500 ether, block.timestamp, 0, 10_000); + (uint256 amountInWad, uint256 amountOutWad,,,) = rmm.prepareSwapPtIn(ytOut, block.timestamp, index); + + vm.expectRevert(); + rmm.swapExactSyForYt(exactSYIn, ytOut, amountOutWad + 1 ether, 500 ether, 10_000, address(this)); + } } From 473714e889671d12a7a992155f9f492252388294 Mon Sep 17 00:00:00 2001 From: clemlak Date: Mon, 24 Jun 2024 18:22:01 +0400 Subject: [PATCH 004/116] feat: turn reentrancy lock private --- src/RMM.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/RMM.sol b/src/RMM.sol index 5089be7..8776218 100644 --- a/src/RMM.sol +++ b/src/RMM.sol @@ -43,13 +43,13 @@ contract RMM is ERC20 { uint256 public lastImpliedPrice; address public curator; - uint256 public lock_ = 1; + uint256 private _lock = 1; modifier lock() { - if (lock_ != 1) revert Reentrancy(); - lock_ = 0; + if (_lock != 1) revert Reentrancy(); + _lock = 0; _; - lock_ = 1; + _lock = 1; } /// @dev Applies updates to the trading function and validates the adjustment. From 932d04cffa6997a253ede0b19cfc03c9d035afb7 Mon Sep 17 00:00:00 2001 From: clemlak Date: Mon, 24 Jun 2024 18:26:00 +0400 Subject: [PATCH 005/116] test: add reserves update in swapExactPtForSy handler --- test/invariant/RMMHandler.sol | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/test/invariant/RMMHandler.sol b/test/invariant/RMMHandler.sol index 87e2386..8c85b57 100644 --- a/test/invariant/RMMHandler.sol +++ b/test/invariant/RMMHandler.sol @@ -164,11 +164,16 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { } function swapExactPtForSy() public createActor countCall(this.swapExactPtForSy.selector) { - deal(address(PT), currentActor, 1 ether); + uint256 amountIn = 1 ether; + deal(address(PT), currentActor, amountIn); vm.startPrank(currentActor); - PT.approve(address(rmm), 1 ether); - rmm.swapExactPtForSy(1 ether, 0, address(currentActor)); + PT.approve(address(rmm), amountIn); + (uint256 amountOut, int256 deltaLiquidity) = rmm.swapExactPtForSy(amountIn, 0, address(currentActor)); vm.stopPrank(); + + ghost_reserveX -= amountIn; + ghost_reserveY += amountOut; + ghost_totalLiquidity += uint256(deltaLiquidity); } function swapExactSyForPt() public createActor countCall(this.swapExactSyForPt.selector) { From f8871b57f3cb049ee75e4a49fa80cef8d585def2 Mon Sep 17 00:00:00 2001 From: clemlak Date: Mon, 24 Jun 2024 18:26:24 +0400 Subject: [PATCH 006/116] chore: reduce invariant tests depth --- test/invariant/RMMInvariants.t.sol | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/invariant/RMMInvariants.t.sol b/test/invariant/RMMInvariants.t.sol index f1f2494..8e8c650 100644 --- a/test/invariant/RMMInvariants.t.sol +++ b/test/invariant/RMMInvariants.t.sol @@ -53,10 +53,16 @@ contract RMMInvariantsTest is SetUp { assertTrue(abs(rmm.tradingFunction(index)) <= 100, "Invariant out of valid range"); } + /// forge-config: default.invariant.runs = 100 + /// forge-config: default.invariant.depth = 2 + /// forge-config: default.invariant.fail-on-revert = false function invariant_ReserveX() public view { assertEq(rmm.reserveX(), handler.ghost_reserveX()); } + /// forge-config: default.invariant.runs = 100 + /// forge-config: default.invariant.depth = 2 + /// forge-config: default.invariant.fail-on-revert = false function invariant_ReserveY() public view { assertEq(rmm.reserveY(), handler.ghost_reserveY()); } From 4038412fa3142cb075a6762b30d6c3aadc51008b Mon Sep 17 00:00:00 2001 From: clemlak Date: Mon, 24 Jun 2024 18:26:38 +0400 Subject: [PATCH 007/116] test: add test_swapExactSyForYt_EmitsEvent --- test/unit/SwapExactSyForYt.t.sol | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/unit/SwapExactSyForYt.t.sol b/test/unit/SwapExactSyForYt.t.sol index aee25dd..e27df21 100644 --- a/test/unit/SwapExactSyForYt.t.sol +++ b/test/unit/SwapExactSyForYt.t.sol @@ -6,6 +6,7 @@ import {PYIndexLib, IPYieldToken, PYIndex} from "pendle/core/StandardizedYield/P import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol"; import {SetUp} from "../SetUp.sol"; import {ExcessInput} from "../../src/lib/RmmErrors.sol"; +import {Swap} from "../../src/lib/RmmEvents.sol"; contract SwapExactSyForYtTest is SetUp { using PYIndexLib for IPYieldToken; @@ -62,6 +63,19 @@ contract SwapExactSyForYtTest is SetUp { assertEq(rmm.totalLiquidity(), preTotalLiquidity + uint256(deltaLiquidity)); } + function test_swapExactSyForYt_EmitsEvent() public useSYPool withSY(address(this), 10 ether) { + PYIndex index = YT.newIndex(); + uint256 exactSYIn = 1 ether; + uint256 ytOut = rmm.computeSYToYT(index, exactSYIn, 500 ether, block.timestamp, 0, 10_000); + (uint256 amountInWad, uint256 amountOutWad, uint256 amountOut, int256 deltaLiquidity,) = + rmm.prepareSwapPtIn(ytOut, block.timestamp, index); + + uint256 delta = index.assetToSyUp(amountInWad) - amountOutWad; + vm.expectEmit(true, true, true, true); + emit Swap(address(this), address(0xbeef), address(SY), address(YT), delta, amountOut, deltaLiquidity); + rmm.swapExactSyForYt(exactSYIn, ytOut, ytOut.mulDivDown(95, 100), 500 ether, 10_000, address(0xbeef)); + } + function test_swapExactSyForYt_RevertsWhenExcessInput() public useSYPool withSY(address(this), 10 ether) { uint256 exactSYIn = 1 ether; PYIndex index = YT.newIndex(); From 5544fd257cc9b1ce2dbd4a4ab58484702d53a847 Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 25 Jun 2024 11:05:59 +0400 Subject: [PATCH 008/116] test: fix reserves tracking in swapExactPtForSy handler --- test/invariant/RMMHandler.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/invariant/RMMHandler.sol b/test/invariant/RMMHandler.sol index 8c85b57..6584943 100644 --- a/test/invariant/RMMHandler.sol +++ b/test/invariant/RMMHandler.sol @@ -171,8 +171,8 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { (uint256 amountOut, int256 deltaLiquidity) = rmm.swapExactPtForSy(amountIn, 0, address(currentActor)); vm.stopPrank(); - ghost_reserveX -= amountIn; - ghost_reserveY += amountOut; + ghost_reserveX -= amountOut; + ghost_reserveY += amountIn; ghost_totalLiquidity += uint256(deltaLiquidity); } From f0a9787c3794f8f2cf86a3c5fb8a39254e4ce509 Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 25 Jun 2024 11:26:49 +0400 Subject: [PATCH 009/116] test: add reserves update to swapExactSyForYt handler --- test/invariant/RMMHandler.sol | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/invariant/RMMHandler.sol b/test/invariant/RMMHandler.sol index 6584943..f61236b 100644 --- a/test/invariant/RMMHandler.sol +++ b/test/invariant/RMMHandler.sol @@ -143,8 +143,14 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { vm.startPrank(currentActor); SY.approve(address(rmm), exactSYIn); + (uint256 amountInWad, uint256 amountOutWad,, int256 deltaLiquidity,) = + rmm.prepareSwapPtIn(ytOut, block.timestamp, index); rmm.swapExactSyForYt(exactSYIn, ytOut, ytOut.mulDivDown(95, 100), 500 ether, 10_000, address(msg.sender)); vm.stopPrank(); + + ghost_reserveX -= amountOutWad; + ghost_reserveY += amountInWad; + ghost_totalLiquidity += uint256(deltaLiquidity); } function swapExactTokenForYt() public createActor countCall(this.swapExactTokenForYt.selector) { From 7c37833e40100fae8e8ed39e31df1f44d13959ec Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 25 Jun 2024 15:55:50 +0400 Subject: [PATCH 010/116] test: add weth to RMMHandler, improve reserves tracking, fix some stuff --- test/invariant/RMMHandler.sol | 69 +++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 19 deletions(-) diff --git a/test/invariant/RMMHandler.sol b/test/invariant/RMMHandler.sol index f61236b..60a4ac6 100644 --- a/test/invariant/RMMHandler.sol +++ b/test/invariant/RMMHandler.sol @@ -3,16 +3,18 @@ pragma solidity ^0.8.13; import {CommonBase} from "forge-std/Base.sol"; import {StdUtils} from "forge-std/StdUtils.sol"; +import {console} from "forge-std/console.sol"; import {PYIndex, PYIndexLib} from "pendle/core/StandardizedYield/PYIndex.sol"; import {StdCheats} from "forge-std/StdCheats.sol"; import {ERC20} from "solmate/tokens/ERC20.sol"; import {IPPrincipalToken} from "pendle/interfaces/IPPrincipalToken.sol"; import {PendleWstEthSY} from "pendle/core/StandardizedYield/implementations/PendleWstEthSY.sol"; import {IPYieldToken} from "pendle/interfaces/IPYieldToken.sol"; +import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol"; +import {WETH} from "solmate/tokens/WETH.sol"; import {RMM} from "../../src/RMM.sol"; import {PYIndex} from "./../../src/RMM.sol"; import {AddressSet, LibAddressSet} from "../helpers/AddressSet.sol"; -import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol"; contract RMMHandler is CommonBase, StdUtils, StdCheats { using LibAddressSet for AddressSet; @@ -23,10 +25,11 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { PendleWstEthSY public SY; IPYieldToken public YT; IPPrincipalToken public PT; + WETH public weth; uint256 public ghost_reserveX; uint256 public ghost_reserveY; - uint256 public ghost_totalLiquidity; + int256 public ghost_totalLiquidity; uint256 public ghost_totalSupply; mapping(bytes4 => uint256) public calls; @@ -54,11 +57,12 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { _; } - constructor(RMM rmm_, IPPrincipalToken PT_, PendleWstEthSY SY_, IPYieldToken YT_) { + constructor(RMM rmm_, IPPrincipalToken PT_, PendleWstEthSY SY_, IPYieldToken YT_, WETH weth_) { rmm = rmm_; PT = PT_; SY = SY_; YT = YT_; + weth = weth_; } // Utility functions @@ -82,9 +86,9 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { */ uint256 priceX = 1 ether; - uint256 amountX = 50 ether; + uint256 amountX = 1000 ether; uint256 strike = 1 ether; - uint256 sigma = 0.015 ether; + uint256 sigma = 0.025 ether; uint256 fee = 0.00016 ether; (uint256 totalLiquidity, uint256 amountY) = rmm.prepareInit(priceX, amountX, strike, sigma, PT.expiry()); @@ -96,7 +100,7 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { ghost_reserveX += amountX; ghost_reserveY += amountY; - ghost_totalLiquidity += totalLiquidity; + ghost_totalLiquidity += int256(totalLiquidity); } // Target functions @@ -119,7 +123,7 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { vm.stopPrank(); - ghost_totalLiquidity += deltaLiquidity; + ghost_totalLiquidity += int256(deltaLiquidity); ghost_reserveX += deltaXWad; ghost_reserveY += deltaYWad; } @@ -131,7 +135,7 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { ghost_reserveX -= deltaXWad; ghost_reserveY -= deltaYWad; - ghost_totalLiquidity -= deltaLiquidity; + ghost_totalLiquidity -= int256(deltaLiquidity); } function swapExactSyForYt() public createActor countCall(this.swapExactSyForYt.selector) { @@ -139,7 +143,7 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { deal(address(SY), address(currentActor), exactSYIn); PYIndex index = YT.newIndex(); - uint256 ytOut = rmm.computeSYToYT(index, exactSYIn, 10 ether, block.timestamp, 0, 10_000); + uint256 ytOut = rmm.computeSYToYT(index, exactSYIn, 500 ether, block.timestamp, 0, 10_000); vm.startPrank(currentActor); SY.approve(address(rmm), exactSYIn); @@ -150,23 +154,42 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { ghost_reserveX -= amountOutWad; ghost_reserveY += amountInWad; - ghost_totalLiquidity += uint256(deltaLiquidity); + ghost_totalLiquidity += int256(deltaLiquidity); } function swapExactTokenForYt() public createActor countCall(this.swapExactTokenForYt.selector) { uint256 amountIn = 1 ether; - address weth = rmm.WETH(); - deal(weth, currentActor, amountIn); + deal(currentActor, amountIn); + + vm.startPrank(currentActor); + weth.deposit{value: amountIn}(); PYIndex index = YT.newIndex(); + (uint256 syMinted, uint256 ytOut) = - rmm.computeTokenToYT(index, weth, amountIn, 10 ether, block.timestamp, 0, 1_000); + rmm.computeTokenToYT(index, address(weth), amountIn, 10 ether, block.timestamp, 0, 1_000); - vm.startPrank(currentActor); + uint256 amountPtIn = rmm.computeSYToYT(index, syMinted, 10 ether, block.timestamp, ytOut, 0.005 ether); + (uint256 amountInWad, uint256 amountOutWad,, int256 deltaLiquidity,) = + rmm.prepareSwapPtIn(amountPtIn, block.timestamp, index); + + weth.approve(address(rmm), amountIn); rmm.swapExactTokenForYt( - weth, amountIn, ytOut, syMinted, ytOut.mulDivDown(99, 100), 10 ether, 0.005 ether, address(currentActor) + address(weth), + amountIn, + ytOut, + syMinted, + ytOut.mulDivDown(99, 100), + 10 ether, + 0.005 ether, + address(currentActor) ); + vm.stopPrank(); + + ghost_reserveX -= amountOutWad; + ghost_reserveY += amountInWad; + ghost_totalLiquidity += int256(deltaLiquidity); } function swapExactPtForSy() public createActor countCall(this.swapExactPtForSy.selector) { @@ -179,15 +202,21 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { ghost_reserveX -= amountOut; ghost_reserveY += amountIn; - ghost_totalLiquidity += uint256(deltaLiquidity); + ghost_totalLiquidity += int256(deltaLiquidity); } function swapExactSyForPt() public createActor countCall(this.swapExactSyForPt.selector) { - deal(address(SY), currentActor, 1 ether); + uint256 amountIn = 1 ether; + + deal(address(SY), currentActor, amountIn); vm.startPrank(currentActor); - SY.approve(address(rmm), 1 ether); - rmm.swapExactSyForPt(1 ether, 0, address(currentActor)); + SY.approve(address(rmm), amountIn); + (uint256 amountOut, int256 deltaLiquidity) = rmm.swapExactSyForPt(amountIn, 0, address(currentActor)); vm.stopPrank(); + + ghost_reserveX -= amountOut; + ghost_reserveY += amountIn; + ghost_totalLiquidity += int256(deltaLiquidity); } function swapExactYtForSy() public createActor countCall(this.swapExactYtForSy.selector) { @@ -196,5 +225,7 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { YT.approve(address(rmm), 1 ether); rmm.swapExactYtForSy(1 ether, 0, address(currentActor)); vm.stopPrank(); + + // TODO: Update the reserves accordingly } } From 9e8a878b0224f1ab866b96bd81c8aba81fd4dc07 Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 25 Jun 2024 15:56:45 +0400 Subject: [PATCH 011/116] test: add swapExactSyForPt handler, update config --- test/invariant/RMMInvariants.t.sol | 33 ++++++++++++++++-------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/test/invariant/RMMInvariants.t.sol b/test/invariant/RMMInvariants.t.sol index 8e8c650..29b97c2 100644 --- a/test/invariant/RMMInvariants.t.sol +++ b/test/invariant/RMMInvariants.t.sol @@ -15,11 +15,11 @@ contract RMMInvariantsTest is SetUp { function setUp() public virtual override { super.setUp(); - handler = new RMMHandler(rmm, PT, SY, YT); + handler = new RMMHandler(rmm, PT, SY, YT, weth); - mintSY(address(handler), 100 ether); - mintSY(address(this), 100 ether); - mintPY(address(handler), 100 ether); + mintSY(address(handler), 1000 ether); + mintSY(address(this), 2000 ether); + mintPY(address(handler), 2000 ether); handler.init(); @@ -27,8 +27,10 @@ contract RMMInvariantsTest is SetUp { selectors[0] = RMMHandler.allocate.selector; selectors[1] = RMMHandler.deallocate.selector; selectors[2] = RMMHandler.swapExactSyForYt.selector; - selectors[3] = RMMHandler.swapExactTokenForYt.selector; - selectors[4] = RMMHandler.swapExactPtForSy.selector; + selectors[3] = RMMHandler.swapExactPtForSy.selector; + selectors[4] = RMMHandler.swapExactSyForPt.selector; + + // selectors[3] = RMMHandler.swapExactTokenForYt.selector; targetSelector(FuzzSelector({addr: address(handler), selectors: selectors})); @@ -42,27 +44,28 @@ contract RMMInvariantsTest is SetUp { console.log("SwapExactSyForYt: ", handler.calls(RMMHandler.swapExactSyForYt.selector)); console.log("SwapExactTokenForYt: ", handler.calls(RMMHandler.swapExactTokenForYt.selector)); console.log("SwapExactPtForSy: ", handler.calls(RMMHandler.swapExactPtForSy.selector)); + console.log("SwapExactSyForPt: ", handler.calls(RMMHandler.swapExactSyForPt.selector)); } - /// forge-config: default.invariant.runs = 100 - /// forge-config: default.invariant.depth = 2 - /// forge-config: default.invariant.fail-on-revert = false + /// forge-config: default.invariant.runs = 10 + /// forge-config: default.invariant.depth = 100 + /// forge-config: default.invariant.fail-on-revert = true function invariant_TradingFunction() public { IPYieldToken YT = handler.YT(); PYIndex index = YT.newIndex(); assertTrue(abs(rmm.tradingFunction(index)) <= 100, "Invariant out of valid range"); } - /// forge-config: default.invariant.runs = 100 - /// forge-config: default.invariant.depth = 2 - /// forge-config: default.invariant.fail-on-revert = false + /// forge-config: default.invariant.runs = 10 + /// forge-config: default.invariant.depth = 100 + /// forge-config: default.invariant.fail-on-revert = true function invariant_ReserveX() public view { assertEq(rmm.reserveX(), handler.ghost_reserveX()); } - /// forge-config: default.invariant.runs = 100 - /// forge-config: default.invariant.depth = 2 - /// forge-config: default.invariant.fail-on-revert = false + /// forge-config: default.invariant.runs = 10 + /// forge-config: default.invariant.depth = 100 + /// forge-config: default.invariant.fail-on-revert = true function invariant_ReserveY() public view { assertEq(rmm.reserveY(), handler.ghost_reserveY()); } From 47d8d175da39dc89c079f039a707508588365a19 Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 25 Jun 2024 16:02:28 +0400 Subject: [PATCH 012/116] test: track reserves in swapExactYtForSy handler --- test/invariant/RMMHandler.sol | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/test/invariant/RMMHandler.sol b/test/invariant/RMMHandler.sol index 60a4ac6..8bbc320 100644 --- a/test/invariant/RMMHandler.sol +++ b/test/invariant/RMMHandler.sol @@ -220,12 +220,17 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { } function swapExactYtForSy() public createActor countCall(this.swapExactYtForSy.selector) { - deal(address(YT), currentActor, 1 ether); + uint256 ytIn = 1 ether; + + deal(address(YT), currentActor, ytIn); vm.startPrank(currentActor); - YT.approve(address(rmm), 1 ether); - rmm.swapExactYtForSy(1 ether, 0, address(currentActor)); + YT.approve(address(rmm), ytIn); + (uint256 amountOut, uint256 amountIn, int256 deltaLiquidity) = + rmm.swapExactYtForSy(ytIn, 0, address(currentActor)); vm.stopPrank(); - // TODO: Update the reserves accordingly + ghost_reserveX += amountIn; + ghost_reserveY -= amountOut; + ghost_totalLiquidity += int256(deltaLiquidity); } } From 9d54ddc3c08142378d7ce167502d24718884eac7 Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 25 Jun 2024 16:03:04 +0400 Subject: [PATCH 013/116] test: add swapExactYtForSy handler to invariant testing --- test/invariant/RMMInvariants.t.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/invariant/RMMInvariants.t.sol b/test/invariant/RMMInvariants.t.sol index 29b97c2..7ca1192 100644 --- a/test/invariant/RMMInvariants.t.sol +++ b/test/invariant/RMMInvariants.t.sol @@ -23,12 +23,13 @@ contract RMMInvariantsTest is SetUp { handler.init(); - bytes4[] memory selectors = new bytes4[](5); + bytes4[] memory selectors = new bytes4[](6); selectors[0] = RMMHandler.allocate.selector; selectors[1] = RMMHandler.deallocate.selector; selectors[2] = RMMHandler.swapExactSyForYt.selector; selectors[3] = RMMHandler.swapExactPtForSy.selector; selectors[4] = RMMHandler.swapExactSyForPt.selector; + selectors[5] = RMMHandler.swapExactYtForSy.selector; // selectors[3] = RMMHandler.swapExactTokenForYt.selector; @@ -45,6 +46,7 @@ contract RMMInvariantsTest is SetUp { console.log("SwapExactTokenForYt: ", handler.calls(RMMHandler.swapExactTokenForYt.selector)); console.log("SwapExactPtForSy: ", handler.calls(RMMHandler.swapExactPtForSy.selector)); console.log("SwapExactSyForPt: ", handler.calls(RMMHandler.swapExactSyForPt.selector)); + console.log("SwapExactYtForSy: ", handler.calls(RMMHandler.swapExactYtForSy.selector)); } /// forge-config: default.invariant.runs = 10 From b6e025a82d8aaf4f83ac07f822e41ccf4cd8acab Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 25 Jun 2024 16:23:44 +0400 Subject: [PATCH 014/116] test: add swapExactTokenForYt to invariant testing targets --- test/invariant/RMMInvariants.t.sol | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/invariant/RMMInvariants.t.sol b/test/invariant/RMMInvariants.t.sol index 7ca1192..711a768 100644 --- a/test/invariant/RMMInvariants.t.sol +++ b/test/invariant/RMMInvariants.t.sol @@ -23,15 +23,14 @@ contract RMMInvariantsTest is SetUp { handler.init(); - bytes4[] memory selectors = new bytes4[](6); + bytes4[] memory selectors = new bytes4[](7); selectors[0] = RMMHandler.allocate.selector; selectors[1] = RMMHandler.deallocate.selector; selectors[2] = RMMHandler.swapExactSyForYt.selector; selectors[3] = RMMHandler.swapExactPtForSy.selector; selectors[4] = RMMHandler.swapExactSyForPt.selector; selectors[5] = RMMHandler.swapExactYtForSy.selector; - - // selectors[3] = RMMHandler.swapExactTokenForYt.selector; + selectors[6] = RMMHandler.swapExactTokenForYt.selector; targetSelector(FuzzSelector({addr: address(handler), selectors: selectors})); From f234d4698d22db37da24a9e23644479987b5f6cb Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 25 Jun 2024 16:24:01 +0400 Subject: [PATCH 015/116] test: add random amount for swapExactSyForYt handler --- test/invariant/RMMHandler.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/invariant/RMMHandler.sol b/test/invariant/RMMHandler.sol index 8bbc320..5e51d8b 100644 --- a/test/invariant/RMMHandler.sol +++ b/test/invariant/RMMHandler.sol @@ -138,8 +138,8 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { ghost_totalLiquidity -= int256(deltaLiquidity); } - function swapExactSyForYt() public createActor countCall(this.swapExactSyForYt.selector) { - uint256 exactSYIn = 1 ether; + function swapExactSyForYt(uint256 exactSYIn) public createActor countCall(this.swapExactSyForYt.selector) { + exactSYIn = bound(exactSYIn, 0.1 ether, 100 ether); deal(address(SY), address(currentActor), exactSYIn); PYIndex index = YT.newIndex(); From 34bc281dfd4f811f4be2eec66121e00437db2760 Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 25 Jun 2024 16:27:28 +0400 Subject: [PATCH 016/116] test: add random amounts to target functions --- test/invariant/RMMHandler.sol | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/test/invariant/RMMHandler.sol b/test/invariant/RMMHandler.sol index 5e51d8b..446cafd 100644 --- a/test/invariant/RMMHandler.sol +++ b/test/invariant/RMMHandler.sol @@ -157,26 +157,30 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { ghost_totalLiquidity += int256(deltaLiquidity); } - function swapExactTokenForYt() public createActor countCall(this.swapExactTokenForYt.selector) { - uint256 amountIn = 1 ether; - deal(currentActor, amountIn); + function swapExactTokenForYt(uint256 amountTokenIn) + public + createActor + countCall(this.swapExactTokenForYt.selector) + { + amountTokenIn = bound(amountTokenIn, 0.1 ether, 100 ether); + deal(currentActor, amountTokenIn); vm.startPrank(currentActor); - weth.deposit{value: amountIn}(); + weth.deposit{value: amountTokenIn}(); PYIndex index = YT.newIndex(); (uint256 syMinted, uint256 ytOut) = - rmm.computeTokenToYT(index, address(weth), amountIn, 10 ether, block.timestamp, 0, 1_000); + rmm.computeTokenToYT(index, address(weth), amountTokenIn, 10 ether, block.timestamp, 0, 1_000); uint256 amountPtIn = rmm.computeSYToYT(index, syMinted, 10 ether, block.timestamp, ytOut, 0.005 ether); (uint256 amountInWad, uint256 amountOutWad,, int256 deltaLiquidity,) = rmm.prepareSwapPtIn(amountPtIn, block.timestamp, index); - weth.approve(address(rmm), amountIn); + weth.approve(address(rmm), amountTokenIn); rmm.swapExactTokenForYt( address(weth), - amountIn, + amountTokenIn, ytOut, syMinted, ytOut.mulDivDown(99, 100), @@ -192,8 +196,8 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { ghost_totalLiquidity += int256(deltaLiquidity); } - function swapExactPtForSy() public createActor countCall(this.swapExactPtForSy.selector) { - uint256 amountIn = 1 ether; + function swapExactPtForSy(uint256 amountIn) public createActor countCall(this.swapExactPtForSy.selector) { + amountIn = bound(amountIn, 0.1 ether, 100 ether); deal(address(PT), currentActor, amountIn); vm.startPrank(currentActor); PT.approve(address(rmm), amountIn); @@ -205,9 +209,8 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { ghost_totalLiquidity += int256(deltaLiquidity); } - function swapExactSyForPt() public createActor countCall(this.swapExactSyForPt.selector) { - uint256 amountIn = 1 ether; - + function swapExactSyForPt(uint256 amountIn) public createActor countCall(this.swapExactSyForPt.selector) { + amountIn = bound(amountIn, 0.1 ether, 100 ether); deal(address(SY), currentActor, amountIn); vm.startPrank(currentActor); SY.approve(address(rmm), amountIn); @@ -219,8 +222,8 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { ghost_totalLiquidity += int256(deltaLiquidity); } - function swapExactYtForSy() public createActor countCall(this.swapExactYtForSy.selector) { - uint256 ytIn = 1 ether; + function swapExactYtForSy(uint256 ytIn) public createActor countCall(this.swapExactYtForSy.selector) { + ytIn = bound(ytIn, 0.1 ether, 100 ether); deal(address(YT), currentActor, ytIn); vm.startPrank(currentActor); From ec7f877bdfecf910ce13e2cf3d6646a8510c41e4 Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 25 Jun 2024 18:43:42 +0400 Subject: [PATCH 017/116] test: revert to hardcoded amounts --- test/invariant/RMMHandler.sol | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/test/invariant/RMMHandler.sol b/test/invariant/RMMHandler.sol index 446cafd..2eb3273 100644 --- a/test/invariant/RMMHandler.sol +++ b/test/invariant/RMMHandler.sol @@ -138,8 +138,8 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { ghost_totalLiquidity -= int256(deltaLiquidity); } - function swapExactSyForYt(uint256 exactSYIn) public createActor countCall(this.swapExactSyForYt.selector) { - exactSYIn = bound(exactSYIn, 0.1 ether, 100 ether); + function swapExactSyForYt() public createActor countCall(this.swapExactSyForYt.selector) { + uint256 exactSYIn = 1 ether; deal(address(SY), address(currentActor), exactSYIn); PYIndex index = YT.newIndex(); @@ -157,12 +157,8 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { ghost_totalLiquidity += int256(deltaLiquidity); } - function swapExactTokenForYt(uint256 amountTokenIn) - public - createActor - countCall(this.swapExactTokenForYt.selector) - { - amountTokenIn = bound(amountTokenIn, 0.1 ether, 100 ether); + function swapExactTokenForYt() public createActor countCall(this.swapExactTokenForYt.selector) { + uint256 amountTokenIn = 1 ether; deal(currentActor, amountTokenIn); vm.startPrank(currentActor); @@ -196,8 +192,8 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { ghost_totalLiquidity += int256(deltaLiquidity); } - function swapExactPtForSy(uint256 amountIn) public createActor countCall(this.swapExactPtForSy.selector) { - amountIn = bound(amountIn, 0.1 ether, 100 ether); + function swapExactPtForSy() public createActor countCall(this.swapExactPtForSy.selector) { + uint256 amountIn = 1 ether; deal(address(PT), currentActor, amountIn); vm.startPrank(currentActor); PT.approve(address(rmm), amountIn); @@ -209,8 +205,8 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { ghost_totalLiquidity += int256(deltaLiquidity); } - function swapExactSyForPt(uint256 amountIn) public createActor countCall(this.swapExactSyForPt.selector) { - amountIn = bound(amountIn, 0.1 ether, 100 ether); + function swapExactSyForPt() public createActor countCall(this.swapExactSyForPt.selector) { + uint256 amountIn = 1 ether; deal(address(SY), currentActor, amountIn); vm.startPrank(currentActor); SY.approve(address(rmm), amountIn); @@ -222,8 +218,8 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { ghost_totalLiquidity += int256(deltaLiquidity); } - function swapExactYtForSy(uint256 ytIn) public createActor countCall(this.swapExactYtForSy.selector) { - ytIn = bound(ytIn, 0.1 ether, 100 ether); + function swapExactYtForSy() public createActor countCall(this.swapExactYtForSy.selector) { + uint256 ytIn = 1 ether; deal(address(YT), currentActor, ytIn); vm.startPrank(currentActor); From c1b4fb03bb95642243e8d18ceea09f835b397528 Mon Sep 17 00:00:00 2001 From: kinrezc Date: Tue, 25 Jun 2024 14:59:09 -0400 Subject: [PATCH 018/116] fix initialization status, reverting with insuficient allowance? --- src/RMM.sol | 11 +++++++++++ test/SetUp.sol | 28 ++++++++++++++-------------- test/invariant/RMMHandler.sol | 11 ++++++----- 3 files changed, 31 insertions(+), 19 deletions(-) diff --git a/src/RMM.sol b/src/RMM.sol index 8776218..9f60c7f 100644 --- a/src/RMM.sol +++ b/src/RMM.sol @@ -11,6 +11,8 @@ import "./lib/RmmLib.sol"; import "./lib/RmmErrors.sol"; import "./lib/RmmEvents.sol"; +import "forge-std/console2.sol"; + contract RMM is ERC20 { using FixedPointMathLib for uint256; using FixedPointMathLib for int256; @@ -304,11 +306,20 @@ contract RMM is ERC20 { revert ExcessInput(ytIn, maxSyIn, amountIn); } + console2.log("here 1?"); _adjust(toInt(amountInWad), -toInt(amountOutWad), deltaLiquidity, strike_, index); (uint256 creditNative) = _debit(address(YT), ytIn); + console2.log("here 2?"); uint256 amountSy = redeemPy(ytIn, address(this)); + console2.log("here 3?"); + console2.log("ytIn", ytIn); + console2.log("amountSyIn", amountSy); + console2.log("amountInWad", amountInWad); + console2.log("amountSy gt amountInWad", amountSy > amountInWad); amountOut = amountSy - amountInWad; + console2.log("amountOut", amountOut); (uint256 debitNative) = _credit(address(SY), to, amountOut); + console2.log("here 4?"); emit Swap(msg.sender, to, address(PT), address(SY), debitNative, creditNative, deltaLiquidity); } diff --git a/test/SetUp.sol b/test/SetUp.sol index 3264fc6..cfb2be7 100644 --- a/test/SetUp.sol +++ b/test/SetUp.sol @@ -11,6 +11,7 @@ import {PendleYieldContractFactoryV2} from "pendle/core/YieldContractsV2/PendleY import {PendleWstEthSY} from "pendle/core/StandardizedYield/implementations/PendleWstEthSY.sol"; import {MockWstETH} from "./mocks/MockWstETH.sol"; import {MockStETH} from "./mocks/MockStETH.sol"; +import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; import {RMM} from "./../src/RMM.sol"; @@ -26,6 +27,9 @@ struct InitParams { address curator; } +address constant wstETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; //real wsteth +address constant WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + contract SetUp is Test { // All the contracts that are needed for the tests. @@ -34,8 +38,6 @@ contract SetUp is Test { PendleWstEthSY public SY; IPYieldToken public YT; IPPrincipalToken public PT; - MockWstETH public wstETH; - MockStETH public stETH; // Some default constants. @@ -45,17 +47,19 @@ contract SetUp is Test { // Main setup functions. function setUpContracts(uint32 expiry) public { + vm.createSelectFork({urlOrAlias: "mainnet", blockNumber: 17_162_783}); weth = new WETH(); - stETH = new MockStETH(); - wstETH = new MockWstETH(address(stETH)); + deal(wstETH, address(this), 1_000_000 ether); + rmm = new RMM(address(weth), "RMM-LP-TOKEN", "RMM-LPT"); SY = new PendleWstEthSY("wstEthSY", "wstEthSY", address(weth), address(wstETH)); + MockERC20(wstETH).approve(address(rmm), type(uint256).max); + MockERC20(wstETH).approve(address(SY), type(uint256).max); vm.label(address(SY), "SY"); vm.label(address(YT), "YT"); vm.label(address(PT), "PT"); vm.label(address(wstETH), "wstETH"); - vm.label(address(stETH), "stETH"); ( address creationCodeContractA, @@ -87,9 +91,7 @@ contract SetUp is Test { // Here are some utility functions, you can use them to set specific states inside of a test. function mintSY(address to, uint256 amount) public { - stETH.mint(address(this), amount); - stETH.approve(address(SY), type(uint256).max); - SY.deposit(address(to), address(stETH), amount, 0); + SY.deposit(address(to), address(wstETH), amount, 0); } function batchMintSY(address[] memory to, uint256[] memory amounts) public { @@ -113,10 +115,10 @@ contract SetUp is Test { function getDefaultParams() internal view returns (InitParams memory) { return InitParams({ - priceX: 1 ether, + priceX: 1.15 ether, totalAsset: 1 ether, - strike: 1 ether, - sigma: 0.015 ether, + strike: 1.15 ether, + sigma: 0.02 ether, maturity: PT.expiry(), PT: address(PT), amountX: 1 ether, @@ -158,9 +160,7 @@ contract SetUp is Test { } modifier withSY(address to, uint256 amount) { - stETH.mint(address(to), amount); - stETH.approve(address(SY), type(uint256).max); - SY.deposit(address(to), address(stETH), amount, 0); + SY.deposit(address(to), address(wstETH), amount, 0); _; } diff --git a/test/invariant/RMMHandler.sol b/test/invariant/RMMHandler.sol index 2eb3273..7842f1b 100644 --- a/test/invariant/RMMHandler.sol +++ b/test/invariant/RMMHandler.sol @@ -85,9 +85,9 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { fee = bound(fee, 0 ether, 1 ether); */ - uint256 priceX = 1 ether; + uint256 priceX = 1.15 ether; uint256 amountX = 1000 ether; - uint256 strike = 1 ether; + uint256 strike = 1.15 ether; uint256 sigma = 0.025 ether; uint256 fee = 0.00016 ether; @@ -95,6 +95,7 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { PT.approve(address(rmm), type(uint256).max); SY.approve(address(rmm), type(uint256).max); + YT.approve(address(rmm), type(uint256).max); rmm.init(address(PT), priceX, amountX, strike, sigma, fee, address(0)); @@ -106,8 +107,8 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { // Target functions function allocate(uint256 deltaX, uint256 deltaY) public createActor countCall(this.allocate.selector) { - deltaX = bound(deltaX, 0.1 ether, 100 ether); - deltaY = bound(deltaY, 0.1 ether, 100 ether); + deltaX = bound(deltaX, 0.1 ether, 10 ether); + deltaY = bound(deltaY, 0.1 ether, 10 ether); deal(address(SY), currentActor, deltaX); deal(address(PT), currentActor, deltaY); @@ -225,7 +226,7 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { vm.startPrank(currentActor); YT.approve(address(rmm), ytIn); (uint256 amountOut, uint256 amountIn, int256 deltaLiquidity) = - rmm.swapExactYtForSy(ytIn, 0, address(currentActor)); + rmm.swapExactYtForSy(ytIn, 1000 ether, address(currentActor)); vm.stopPrank(); ghost_reserveX += amountIn; From d4c87e8958b36e3dbc1e436981e571ab0009d11c Mon Sep 17 00:00:00 2001 From: kinrezc Date: Tue, 25 Jun 2024 15:15:07 -0400 Subject: [PATCH 019/116] fix: allocate returns sy dxWad instead of asset units --- src/RMM.sol | 4 ++-- test/invariant/RMMHandler.sol | 20 +++++++++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/RMM.sol b/src/RMM.sol index 9f60c7f..ead6ea4 100644 --- a/src/RMM.sol +++ b/src/RMM.sol @@ -342,8 +342,8 @@ contract RMM is ERC20 { _mint(to, lptMinted); _adjust(toInt(deltaXWad), toInt(deltaYWad), toInt(deltaLiquidity), strike, index); - (uint256 debitNativeX) = _debit(address(SY), deltaXWad); - (uint256 debitNativeY) = _debit(address(PT), deltaYWad); + (uint256 debitNativeX) = _debit(address(SY), index.assetToSyUp(deltaX)); + (uint256 debitNativeY) = _debit(address(PT), deltaY); emit Allocate(msg.sender, to, debitNativeX, debitNativeY, deltaLiquidity); } diff --git a/test/invariant/RMMHandler.sol b/test/invariant/RMMHandler.sol index 7842f1b..9119f5f 100644 --- a/test/invariant/RMMHandler.sol +++ b/test/invariant/RMMHandler.sol @@ -16,6 +16,8 @@ import {RMM} from "../../src/RMM.sol"; import {PYIndex} from "./../../src/RMM.sol"; import {AddressSet, LibAddressSet} from "../helpers/AddressSet.sol"; +import "forge-std/console2.sol"; + contract RMMHandler is CommonBase, StdUtils, StdCheats { using LibAddressSet for AddressSet; using PYIndexLib for IPYieldToken; @@ -113,14 +115,26 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { deal(address(SY), currentActor, deltaX); deal(address(PT), currentActor, deltaY); + vm.startPrank(currentActor); - SY.approve(address(rmm), deltaX); - PT.approve(address(rmm), deltaY); + console2.log("SY balance", ERC20(address(SY)).balanceOf(address(currentActor))); + console2.log("PT balance", ERC20(address(PT)).balanceOf(address(currentActor))); + console2.log("SY allowance", ERC20(address(SY)).allowance(address(currentActor), address(rmm))); + console2.log("PT allowance", ERC20(address(PT)).allowance(address(currentActor), address(rmm))); + console2.log("here 1"); (uint256 deltaXWad, uint256 deltaYWad,,) = rmm.prepareAllocate(deltaX, deltaY, PYIndex.wrap(rmm.YT().pyIndexCurrent())); - uint256 deltaLiquidity = rmm.allocate(deltaXWad, deltaYWad, 0, address(currentActor)); + + + SY.approve(address(rmm), deltaX); + PT.approve(address(rmm), deltaY); + console2.log("address this", address(this)); + console2.log("SY allowance", ERC20(address(SY)).allowance(address(currentActor), address(rmm))); + console2.log("PT allowance", ERC20(address(PT)).allowance(address(currentActor), address(rmm))); + uint256 deltaLiquidity = rmm.allocate(deltaX, deltaY, 0, address(currentActor)); + console2.log("here 3"); vm.stopPrank(); From 598cccaf36740a44e904158d4e7eeed9d6e5b487 Mon Sep 17 00:00:00 2001 From: kinrezc Date: Tue, 25 Jun 2024 15:24:29 -0400 Subject: [PATCH 020/116] rm console logs --- src/RMM.sol | 9 --------- test/invariant/RMMHandler.sol | 9 --------- 2 files changed, 18 deletions(-) diff --git a/src/RMM.sol b/src/RMM.sol index ead6ea4..53bfa9b 100644 --- a/src/RMM.sol +++ b/src/RMM.sol @@ -306,20 +306,11 @@ contract RMM is ERC20 { revert ExcessInput(ytIn, maxSyIn, amountIn); } - console2.log("here 1?"); _adjust(toInt(amountInWad), -toInt(amountOutWad), deltaLiquidity, strike_, index); (uint256 creditNative) = _debit(address(YT), ytIn); - console2.log("here 2?"); uint256 amountSy = redeemPy(ytIn, address(this)); - console2.log("here 3?"); - console2.log("ytIn", ytIn); - console2.log("amountSyIn", amountSy); - console2.log("amountInWad", amountInWad); - console2.log("amountSy gt amountInWad", amountSy > amountInWad); amountOut = amountSy - amountInWad; - console2.log("amountOut", amountOut); (uint256 debitNative) = _credit(address(SY), to, amountOut); - console2.log("here 4?"); emit Swap(msg.sender, to, address(PT), address(SY), debitNative, creditNative, deltaLiquidity); } diff --git a/test/invariant/RMMHandler.sol b/test/invariant/RMMHandler.sol index 9119f5f..9320da7 100644 --- a/test/invariant/RMMHandler.sol +++ b/test/invariant/RMMHandler.sol @@ -118,11 +118,6 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { vm.startPrank(currentActor); - console2.log("SY balance", ERC20(address(SY)).balanceOf(address(currentActor))); - console2.log("PT balance", ERC20(address(PT)).balanceOf(address(currentActor))); - console2.log("SY allowance", ERC20(address(SY)).allowance(address(currentActor), address(rmm))); - console2.log("PT allowance", ERC20(address(PT)).allowance(address(currentActor), address(rmm))); - console2.log("here 1"); (uint256 deltaXWad, uint256 deltaYWad,,) = rmm.prepareAllocate(deltaX, deltaY, PYIndex.wrap(rmm.YT().pyIndexCurrent())); @@ -130,11 +125,7 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { SY.approve(address(rmm), deltaX); PT.approve(address(rmm), deltaY); - console2.log("address this", address(this)); - console2.log("SY allowance", ERC20(address(SY)).allowance(address(currentActor), address(rmm))); - console2.log("PT allowance", ERC20(address(PT)).allowance(address(currentActor), address(rmm))); uint256 deltaLiquidity = rmm.allocate(deltaX, deltaY, 0, address(currentActor)); - console2.log("here 3"); vm.stopPrank(); From 25ce6a2c6aa3334b8fe60041d8af7f79cff8b8c7 Mon Sep 17 00:00:00 2001 From: kinrezc Date: Tue, 25 Jun 2024 15:27:09 -0400 Subject: [PATCH 021/116] fmt --- test/invariant/RMMHandler.sol | 3 --- test/unit/RMM.t.sol | 9 +++------ test/unit/SwapExactYtForSy.t.sol | 1 - 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/test/invariant/RMMHandler.sol b/test/invariant/RMMHandler.sol index 9320da7..1f2b99a 100644 --- a/test/invariant/RMMHandler.sol +++ b/test/invariant/RMMHandler.sol @@ -115,14 +115,11 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { deal(address(SY), currentActor, deltaX); deal(address(PT), currentActor, deltaY); - vm.startPrank(currentActor); - (uint256 deltaXWad, uint256 deltaYWad,,) = rmm.prepareAllocate(deltaX, deltaY, PYIndex.wrap(rmm.YT().pyIndexCurrent())); - SY.approve(address(rmm), deltaX); PT.approve(address(rmm), deltaY); uint256 deltaLiquidity = rmm.allocate(deltaX, deltaY, 0, address(currentActor)); diff --git a/test/unit/RMM.t.sol b/test/unit/RMM.t.sol index e00d1d0..27f87c6 100644 --- a/test/unit/RMM.t.sol +++ b/test/unit/RMM.t.sol @@ -264,8 +264,7 @@ contract RMMTest is Test { int256 initial = subject().tradingFunction(index); console2.log("loss", uint256(685_040_862_443_611_928) - uint256(685_001_492_551_417_433)); console2.log("loss %", uint256(39_369_892_194_495) * 1 ether / uint256(685_001_492_551_417_433)); - (uint256 amountOut, int256 deltaLiquidity) = - subject().swapExactSyForPt(deltaSy, 0, address(this)); + (uint256 amountOut, int256 deltaLiquidity) = subject().swapExactSyForPt(deltaSy, 0, address(this)); int256 terminal = subject().tradingFunction(index); console2.logInt(initial); console2.logInt(terminal); @@ -286,8 +285,7 @@ contract RMMTest is Test { uint256 expectedL = 2_763_676_832_322_849_396; console2.log("expectedL", expectedL); - (uint256 amountOut, int256 deltaLiquidity) = - subject().swapExactSyForPt(deltaSy, 0, address(this)); + (uint256 amountOut, int256 deltaLiquidity) = subject().swapExactSyForPt(deltaSy, 0, address(this)); int256 terminal = subject().tradingFunction(index); console2.log("initialInvariant", initial); @@ -348,8 +346,7 @@ contract RMMTest is Test { uint256 prevReserveY = subject().reserveY(); uint256 prevPrice = subject().approxSpotPrice(index.syToAsset(subject().reserveX())); uint256 prevLiquidity = subject().totalLiquidity(); - (uint256 amountOut, int256 deltaLiquidity) = - subject().swapExactSyForPt(deltaSy, 0, address(this)); + (uint256 amountOut, int256 deltaLiquidity) = subject().swapExactSyForPt(deltaSy, 0, address(this)); assertTrue(amountOut >= minAmountOut, "Amount out is not greater than or equal to min amount out."); assertTrue(abs(subject().tradingFunction(index)) < 100, "Invalid trading function state."); diff --git a/test/unit/SwapExactYtForSy.t.sol b/test/unit/SwapExactYtForSy.t.sol index 8fb45e8..0823eba 100644 --- a/test/unit/SwapExactYtForSy.t.sol +++ b/test/unit/SwapExactYtForSy.t.sol @@ -22,7 +22,6 @@ contract SwapExactYtForSyTest is SetUp { (uint256 amountInWad, uint256 amountOutWad, uint256 amountIn, int256 deltaLiquidity, uint256 strike_) = rmm.prepareSwapSyForExactPt(1 ether, block.timestamp, YT.newIndex()); - (uint256 amountOut, uint256 exactAmountIn,) = rmm.swapExactYtForSy(1 ether, 1000 ether, address(to)); assertEq(ERC20(address(YT)).balanceOf(address(this)), preYTBalance - 1 ether); From e0c38a9380d4f5acf35b9012959d85e5bdf9ec12 Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 26 Jun 2024 18:44:37 +0400 Subject: [PATCH 022/116] test: add newIndex utils function to SetUp, update SY minting --- test/SetUp.sol | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/test/SetUp.sol b/test/SetUp.sol index cfb2be7..62b3af1 100644 --- a/test/SetUp.sol +++ b/test/SetUp.sol @@ -1,6 +1,7 @@ /// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; +import {PYIndex, PYIndexLib} from "pendle/core/StandardizedYield/PYIndex.sol"; import {Test} from "forge-std/Test.sol"; import {WETH} from "solmate/tokens/WETH.sol"; import {IPPrincipalToken} from "pendle/interfaces/IPPrincipalToken.sol"; @@ -9,8 +10,6 @@ import {BaseSplitCodeFactory} from "pendle/core/libraries/BaseSplitCodeFactory.s import {PendleYieldTokenV2} from "pendle/core/YieldContractsV2/PendleYieldTokenV2.sol"; import {PendleYieldContractFactoryV2} from "pendle/core/YieldContractsV2/PendleYieldContractFactoryV2.sol"; import {PendleWstEthSY} from "pendle/core/StandardizedYield/implementations/PendleWstEthSY.sol"; -import {MockWstETH} from "./mocks/MockWstETH.sol"; -import {MockStETH} from "./mocks/MockStETH.sol"; import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; import {RMM} from "./../src/RMM.sol"; @@ -31,6 +30,8 @@ address constant wstETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; //real wst address constant WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; contract SetUp is Test { + using PYIndexLib for IPYieldToken; + // All the contracts that are needed for the tests. RMM public rmm; @@ -48,8 +49,7 @@ contract SetUp is Test { function setUpContracts(uint32 expiry) public { vm.createSelectFork({urlOrAlias: "mainnet", blockNumber: 17_162_783}); - weth = new WETH(); - deal(wstETH, address(this), 1_000_000 ether); + weth = WETH(payable(WETH_ADDRESS)); rmm = new RMM(address(weth), "RMM-LP-TOKEN", "RMM-LPT"); SY = new PendleWstEthSY("wstEthSY", "wstEthSY", address(weth), address(wstETH)); @@ -91,7 +91,8 @@ contract SetUp is Test { // Here are some utility functions, you can use them to set specific states inside of a test. function mintSY(address to, uint256 amount) public { - SY.deposit(address(to), address(wstETH), amount, 0); + // SY.deposit(address(to), address(wstETH), amount, 0); + deal(address(SY), address(to), amount); } function batchMintSY(address[] memory to, uint256[] memory amounts) public { @@ -121,7 +122,7 @@ contract SetUp is Test { sigma: 0.02 ether, maturity: PT.expiry(), PT: address(PT), - amountX: 1 ether, + amountX: 100 ether, fee: 0.00016 ether, curator: address(0x55) }); @@ -135,7 +136,7 @@ contract SetUp is Test { } modifier useDefaultPool() { - uint256 amount = 1_000 ether; + uint256 amount = 10000 ether; mintSY(address(this), amount); mintPY(address(this), amount / 2); InitParams memory params = getDefaultParams(); @@ -160,7 +161,8 @@ contract SetUp is Test { } modifier withSY(address to, uint256 amount) { - SY.deposit(address(to), address(wstETH), amount, 0); + // SY.deposit(address(to), address(wstETH), amount, 0); + deal(address(SY), address(to), amount); _; } @@ -179,4 +181,8 @@ contract SetUp is Test { function skip() public { vm.skip(true); } + + function newIndex() public returns (PYIndex) { + return YT.newIndex(); + } } From bd8d3650c8fae0ff1a84ed12c2fd899b412eab45 Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Wed, 26 Jun 2024 15:11:43 -0400 Subject: [PATCH 023/116] add _updateReserves internal function and remove redundant trading function invocation in evolve modifier --- src/RMM.sol | 19 ++++++++--- src/lib/LiquidityLib.sol | 73 ++++++++++++++++++++++++++++++++++++++++ src/lib/RmmErrors.sol | 4 +-- test/SetUp.sol | 18 +++++----- 4 files changed, 100 insertions(+), 14 deletions(-) create mode 100644 src/lib/LiquidityLib.sol diff --git a/src/RMM.sol b/src/RMM.sol index 53bfa9b..4d986ed 100644 --- a/src/RMM.sol +++ b/src/RMM.sol @@ -56,12 +56,11 @@ contract RMM is ERC20 { /// @dev Applies updates to the trading function and validates the adjustment. modifier evolve(PYIndex index) { - int256 initial = tradingFunction(index); _; int256 terminal = tradingFunction(index); if (abs(terminal) > 10) { - revert OutOfRange(initial, terminal); + revert OutOfRange(terminal); } } @@ -315,7 +314,7 @@ contract RMM is ERC20 { emit Swap(msg.sender, to, address(PT), address(SY), debitNative, creditNative, deltaLiquidity); } - /// todo: should allocates be executed on the stale curve? I dont think the curve should be updated in allocates. + /// @notice uint256 deltaX is expected in SY units, uint256 deltaY is expected in PT units -- these are later converted into asset units for the math function allocate(uint256 deltaX, uint256 deltaY, uint256 minLiquidityOut, address to) external lock @@ -367,6 +366,17 @@ contract RMM is ERC20 { emit Deallocate(msg.sender, to, creditNativeX, creditNativeY, deltaLiquidity); } + /// @notice this is used in place of _adjust when executing an allocate or deallocate. + /// @dev Apply reserve and liquidity updates then validate trading function with evolve modifier + function _updateReserves(int256 deltaX, int256 deltaY, int256 deltaLiquidity, PYIndex index) + internal + evolve(index) + { + reserveX = sum(reserveX, deltaX); + reserveY = sum(reserveY, deltaY); + totalLiquidity = sum(totalLiquidity, deltaLiquidity); + } + // state updates /// @dev Applies an adjustment to the reserves, liquidity, and last timestamp before validating it with the trading function. function _adjust(int256 deltaX, int256 deltaY, int256 deltaLiquidity, uint256 strike_, PYIndex index) @@ -600,12 +610,13 @@ contract RMM is ERC20 { deltaLiquidity = toInt(nextLiquidity) - toInt(totalLiquidity); } + /// @notice uint256 deltaX is expected in SY units, uint256 deltaY is expected in PT units function prepareAllocate(uint256 deltaX, uint256 deltaY, PYIndex index) public view returns (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity, uint256 lptMinted) { - deltaXWad = upscale(index.syToAsset(deltaX), scalar(address(SY))); + deltaXWad = upscale(deltaX, scalar(address(SY))); deltaYWad = upscale(deltaY, scalar(address(PT))); PoolPreCompute memory comp = diff --git a/src/lib/LiquidityLib.sol b/src/lib/LiquidityLib.sol new file mode 100644 index 0000000..03be469 --- /dev/null +++ b/src/lib/LiquidityLib.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.13; + +import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol"; + +using FixedPointMathLib for uint256; +using FixedPointMathLib for int256; + +function computeAllocationGivenX(bool add, uint256 amountX, uint256 rx, uint256 L) + pure + returns (uint256 nextRx, uint256 nextL) +{ + uint256 deltaL = amountX.mulDivDown(L, rx); + nextRx = add ? rx + amountX : rx - amountX; + nextL = add ? L + deltaL : L - deltaL; +} + +function computeAllocationGivenY(bool add, uint256 amountY, uint256 ry, uint256 L) + pure + returns (uint256 nextRy, uint256 nextL) +{ + uint256 deltaL = amountY.mulDivDown(L, ry); + nextRy = add ? ry + amountY : ry - amountY; + nextL = add ? L + deltaL : L - deltaL; +} + +function computeDeltaLGivenDeltaX(uint256 deltaX, uint256 liquidity, uint256 reserveX) pure returns (uint256 deltaL) { + return liquidity.mulDivDown(deltaX, reserveX); +} + +function computeDeltaLGivenDeltaY(uint256 deltaY, uint256 liquidity, uint256 reserveY) pure returns (uint256 deltaL) { + return liquidity.mulDivDown(deltaY, reserveY); +} + +function computeDeltaYGivenDeltaX(uint256 deltaX, uint256 reserveX, uint256 reserveY) pure returns (uint256 deltaY) { + return reserveY.mulDivUp(deltaX, reserveX); +} + +function computeDeltaXGivenDeltaY(uint256 deltaY, uint256 reserveX, uint256 reserveY) pure returns (uint256 deltaX) { + return reserveX.mulDivUp(deltaY, reserveY); +} + +function computeDeltaXGivenDeltaL(uint256 deltaL, uint256 liquidity, uint256 reserveX) pure returns (uint256 deltaX) { + return reserveX.mulDivUp(deltaL, liquidity); +} + +function computeDeltaYGivenDeltaL(uint256 deltaL, uint256 liquidity, uint256 reserveY) pure returns (uint256 deltaX) { + return reserveY.mulDivUp(deltaL, liquidity); +} + +function computeAllocationGivenDeltaX(uint256 deltaX, uint256 reserveX, uint256 reserveY, uint256 liquidity) + pure + returns (uint256 deltaY, uint256 deltaL) +{ + deltaY = computeDeltaYGivenDeltaX(deltaX, reserveX, reserveY); + deltaL = computeDeltaLGivenDeltaX(deltaX, liquidity, reserveX); +} + +function computeAllocationGivenDeltaY(uint256 deltaY, uint256 reserveX, uint256 reserveY, uint256 liquidity) + pure + returns (uint256 deltaX, uint256 deltaL) +{ + deltaX = computeDeltaXGivenDeltaY(deltaY, reserveX, reserveY); + deltaL = computeDeltaLGivenDeltaY(deltaY, liquidity, reserveY); +} + +function computeAllocationGivenDeltaL(uint256 deltaL, uint256 reserveX, uint256 reserveY, uint256 liquidity) + pure + returns (uint256 deltaX, uint256 deltaY) +{ + deltaX = computeDeltaXGivenDeltaL(deltaL, reserveX, liquidity); + deltaY = computeDeltaYGivenDeltaL(deltaL, reserveY, liquidity); +} diff --git a/src/lib/RmmErrors.sol b/src/lib/RmmErrors.sol index bccf590..0f15245 100644 --- a/src/lib/RmmErrors.sol +++ b/src/lib/RmmErrors.sol @@ -21,8 +21,8 @@ error ExcessInput(uint256 amountOut, uint256 maxAmountIn, uint256 amountIn); error InvalidAllocate(uint256 deltaX, uint256 deltaY, uint256 currLiquidity, uint256 nextLiquidity); /// @dev Thrown on `init` when a token has invalid decimals. error InvalidDecimals(address token, uint256 decimals); -/// @dev Thrown when the trading function result is less than the previous invariant. -error OutOfRange(int256 initial, int256 terminal); +/// @dev Thrown when the trading function result is out of bounds +error OutOfRange(int256 terminal); /// @dev Thrown when a payment to or from the user returns false or no data. error PaymentFailed(address token, address from, address to, uint256 amount); /// @dev Thrown when a token passed to `mintSY` is not valid diff --git a/test/SetUp.sol b/test/SetUp.sol index 62b3af1..995d963 100644 --- a/test/SetUp.sol +++ b/test/SetUp.sol @@ -11,6 +11,8 @@ import {PendleYieldTokenV2} from "pendle/core/YieldContractsV2/PendleYieldTokenV import {PendleYieldContractFactoryV2} from "pendle/core/YieldContractsV2/PendleYieldContractFactoryV2.sol"; import {PendleWstEthSY} from "pendle/core/StandardizedYield/implementations/PendleWstEthSY.sol"; import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; +import {MockWstETH} from "./mocks/MockWstETH.sol"; +import {MockStETH} from "./mocks/MockStETH.sol"; import {RMM} from "./../src/RMM.sol"; @@ -26,19 +28,20 @@ struct InitParams { address curator; } -address constant wstETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; //real wsteth -address constant WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; +// address constant wstETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; //real wsteth +// address constant WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; contract SetUp is Test { using PYIndexLib for IPYieldToken; // All the contracts that are needed for the tests. - RMM public rmm; WETH public weth; PendleWstEthSY public SY; IPYieldToken public YT; IPPrincipalToken public PT; + MockWstETH public wstETH; + MockStETH public stETH; // Some default constants. @@ -48,18 +51,17 @@ contract SetUp is Test { // Main setup functions. function setUpContracts(uint32 expiry) public { - vm.createSelectFork({urlOrAlias: "mainnet", blockNumber: 17_162_783}); - weth = WETH(payable(WETH_ADDRESS)); - + weth = new WETH(); + stETH = new MockStETH(); + wstETH = new MockWstETH(address(stETH)); rmm = new RMM(address(weth), "RMM-LP-TOKEN", "RMM-LPT"); SY = new PendleWstEthSY("wstEthSY", "wstEthSY", address(weth), address(wstETH)); - MockERC20(wstETH).approve(address(rmm), type(uint256).max); - MockERC20(wstETH).approve(address(SY), type(uint256).max); vm.label(address(SY), "SY"); vm.label(address(YT), "YT"); vm.label(address(PT), "PT"); vm.label(address(wstETH), "wstETH"); + vm.label(address(stETH), "stETH"); ( address creationCodeContractA, From cb52fbfb46925e1400b7268945913e4da413d0c9 Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Wed, 26 Jun 2024 15:38:21 -0400 Subject: [PATCH 024/116] fix allocate --- src/RMM.sol | 48 ++++++++++++++++++------------------------- src/lib/RmmErrors.sol | 2 +- 2 files changed, 21 insertions(+), 29 deletions(-) diff --git a/src/RMM.sol b/src/RMM.sol index 4d986ed..d042ffa 100644 --- a/src/RMM.sol +++ b/src/RMM.sol @@ -10,6 +10,7 @@ import {IPYieldToken} from "pendle/interfaces/IPYieldToken.sol"; import "./lib/RmmLib.sol"; import "./lib/RmmErrors.sol"; import "./lib/RmmEvents.sol"; +import "./lib/LiquidityLib.sol"; import "forge-std/console2.sol"; @@ -315,27 +316,27 @@ contract RMM is ERC20 { } /// @notice uint256 deltaX is expected in SY units, uint256 deltaY is expected in PT units -- these are later converted into asset units for the math - function allocate(uint256 deltaX, uint256 deltaY, uint256 minLiquidityOut, address to) + function allocate(bool inTermsOfX, uint256 amount, uint256 minLiquidityOut, address to) external lock - returns (uint256 deltaLiquidity) + returns (uint256) { - uint256 deltaXWad; - uint256 deltaYWad; - uint256 lptMinted; PYIndex index = YT.newIndex(); - (deltaXWad, deltaYWad, deltaLiquidity, lptMinted) = prepareAllocate(deltaX, deltaY, index); + (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity, uint256 lptMinted) = + prepareAllocate(inTermsOfX, amount, index); if (deltaLiquidity < minLiquidityOut) { - revert InsufficientLiquidityOut(deltaX, deltaY, minLiquidityOut, deltaLiquidity); + revert InsufficientLiquidityOut(inTermsOfX, amount, minLiquidityOut, deltaLiquidity); } _mint(to, lptMinted); - _adjust(toInt(deltaXWad), toInt(deltaYWad), toInt(deltaLiquidity), strike, index); + _updateReserves(toInt(deltaXWad), toInt(deltaYWad), toInt(deltaLiquidity), index); - (uint256 debitNativeX) = _debit(address(SY), index.assetToSyUp(deltaX)); - (uint256 debitNativeY) = _debit(address(PT), deltaY); + (uint256 debitNativeX) = _debit(address(SY), deltaXWad); + (uint256 debitNativeY) = _debit(address(PT), deltaYWad); emit Allocate(msg.sender, to, debitNativeX, debitNativeY, deltaLiquidity); + + return deltaLiquidity; } /// @dev Burns `deltaLiquidity` * `totalSupply` / `totalLiquidity` rounded up @@ -611,29 +612,20 @@ contract RMM is ERC20 { } /// @notice uint256 deltaX is expected in SY units, uint256 deltaY is expected in PT units - function prepareAllocate(uint256 deltaX, uint256 deltaY, PYIndex index) + function prepareAllocate(bool inTermsOfX, uint256 amount, PYIndex index) public view returns (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity, uint256 lptMinted) { - deltaXWad = upscale(deltaX, scalar(address(SY))); - deltaYWad = upscale(deltaY, scalar(address(PT))); - - PoolPreCompute memory comp = - PoolPreCompute({reserveInAsset: index.syToAsset(reserveX + deltaXWad), strike_: strike, tau_: lastTau()}); - uint256 nextLiquidity = solveL( - comp, - computeLGivenX( - comp.reserveInAsset + deltaXWad, approxSpotPrice(comp.reserveInAsset), strike, sigma, lastTau() - ), - reserveY + deltaYWad, - sigma - ); - if (nextLiquidity < totalLiquidity) { - revert InvalidAllocate(deltaX, deltaY, totalLiquidity, nextLiquidity); + if (inTermsOfX) { + deltaXWad = upscale(amount, scalar(address(SY))); + (deltaYWad, deltaLiquidity) = computeAllocationGivenDeltaX(deltaXWad, reserveX, reserveY, totalLiquidity); + } else { + deltaYWad = upscale(amount, scalar(address(PT))); + (deltaXWad, deltaLiquidity) = computeAllocationGivenDeltaY(deltaYWad, reserveX, reserveY, totalLiquidity); } - deltaLiquidity = nextLiquidity - totalLiquidity; - lptMinted = deltaLiquidity.mulDivDown(totalSupply, nextLiquidity); + + lptMinted = deltaLiquidity.mulDivDown(totalSupply, totalLiquidity + deltaLiquidity); } function prepareDeallocate(uint256 deltaLiquidity) diff --git a/src/lib/RmmErrors.sol b/src/lib/RmmErrors.sol index 0f15245..6d904ad 100644 --- a/src/lib/RmmErrors.sol +++ b/src/lib/RmmErrors.sol @@ -10,7 +10,7 @@ error BalanceError(); /// @dev Thrown when a payment to this contract is insufficient. error InsufficientPayment(address token, uint256 actual, uint256 expected); /// @dev Thrown when a mint does not output enough liquidity. -error InsufficientLiquidityOut(uint256 deltaX, uint256 deltaY, uint256 minLiquidity, uint256 liquidity); +error InsufficientLiquidityOut(bool inTermsOfX, uint256 amount, uint256 minLiquidity, uint256 liquidity); /// @dev Thrown when a swap does not output enough tokens. error InsufficientOutput(uint256 amountIn, uint256 minAmountOut, uint256 amountOut); /// @dev Thrown when a swap does not mint sufficient SY tokens given the minimum amount. From e5c929e8ead756611ebc34a32d88b40f6631d899 Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Wed, 26 Jun 2024 15:45:47 -0400 Subject: [PATCH 025/116] fix allocate related tests --- test/invariant/RMMHandler.sol | 9 ++++----- test/unit/Allocate.t.sol | 32 +++++++++++++++---------------- test/unit/Deallocate.t.sol | 36 +++++++++++++++++------------------ test/unit/SwapPt.sol | 28 +++++++++++++-------------- test/unit/SwapSy.sol | 28 +++++++++++++-------------- 5 files changed, 66 insertions(+), 67 deletions(-) diff --git a/test/invariant/RMMHandler.sol b/test/invariant/RMMHandler.sol index 1f2b99a..d3d7e5b 100644 --- a/test/invariant/RMMHandler.sol +++ b/test/invariant/RMMHandler.sol @@ -110,23 +110,22 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { function allocate(uint256 deltaX, uint256 deltaY) public createActor countCall(this.allocate.selector) { deltaX = bound(deltaX, 0.1 ether, 10 ether); - deltaY = bound(deltaY, 0.1 ether, 10 ether); deal(address(SY), currentActor, deltaX); deal(address(PT), currentActor, deltaY); vm.startPrank(currentActor); - (uint256 deltaXWad, uint256 deltaYWad,,) = - rmm.prepareAllocate(deltaX, deltaY, PYIndex.wrap(rmm.YT().pyIndexCurrent())); + (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = + rmm.prepareAllocate(true, deltaX, PYIndex.wrap(rmm.YT().pyIndexCurrent())); SY.approve(address(rmm), deltaX); PT.approve(address(rmm), deltaY); - uint256 deltaLiquidity = rmm.allocate(deltaX, deltaY, 0, address(currentActor)); + uint256 realDeltaLiquidity = rmm.allocate(true, deltaX, deltaLiquidity, address(currentActor)); vm.stopPrank(); - ghost_totalLiquidity += int256(deltaLiquidity); + ghost_totalLiquidity += int256(realDeltaLiquidity); ghost_reserveX += deltaXWad; ghost_reserveY += deltaYWad; } diff --git a/test/unit/Allocate.t.sol b/test/unit/Allocate.t.sol index 6b8e2a9..a666f65 100644 --- a/test/unit/Allocate.t.sol +++ b/test/unit/Allocate.t.sol @@ -8,11 +8,11 @@ import {InsufficientLiquidityOut} from "../../src/lib/RmmErrors.sol"; contract AllocateTest is SetUp { function test_allocate_MintsLiquidity() public useDefaultPool { - (uint256 deltaXWad, uint256 deltaYWad,,) = - rmm.prepareAllocate(0.1 ether, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = + rmm.prepareAllocate(true, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); uint256 preTotalLiquidity = rmm.totalLiquidity(); - uint256 deltaLiquidity = rmm.allocate(deltaXWad, deltaYWad, 0, address(this)); + deltaLiquidity = rmm.allocate(true, 0.1 ether, 0, address(this)); assertEq(rmm.totalLiquidity(), preTotalLiquidity + deltaLiquidity); } @@ -22,8 +22,8 @@ contract AllocateTest is SetUp { uint256 preBalance = rmm.balanceOf(address(this)); uint256 preTotalSupply = rmm.totalSupply(); (uint256 deltaXWad, uint256 deltaYWad,, uint256 lpMinted) = - rmm.prepareAllocate(0.1 ether, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); - rmm.allocate(deltaXWad, deltaYWad, 0, address(this)); + rmm.prepareAllocate(true, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + rmm.allocate(true, 0.1 ether, 0, address(this)); assertEq(rmm.balanceOf(address(this)), preBalance + lpMinted); assertEq(rmm.totalSupply(), preTotalSupply + lpMinted); } @@ -34,9 +34,9 @@ contract AllocateTest is SetUp { uint256 preReserveX = rmm.reserveX(); uint256 preReserveY = rmm.reserveY(); - (uint256 deltaXWad, uint256 deltaYWad,,) = - rmm.prepareAllocate(0.1 ether, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); - rmm.allocate(deltaXWad, deltaYWad, 0, address(this)); + (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = + rmm.prepareAllocate(true, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + rmm.allocate(true, 0.1 ether, deltaLiquidity, address(this)); assertEq(rmm.reserveX(), preReserveX + deltaXWad); assertEq(rmm.reserveY(), preReserveY + deltaYWad); @@ -51,9 +51,9 @@ contract AllocateTest is SetUp { uint256 rmmPreBalanceSY = SY.balanceOf(address(rmm)); uint256 rmmPreBalancePT = PT.balanceOf(address(rmm)); - (uint256 deltaXWad, uint256 deltaYWad,,) = - rmm.prepareAllocate(0.1 ether, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); - rmm.allocate(deltaXWad, deltaYWad, 0, address(this)); + (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = + rmm.prepareAllocate(true, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + rmm.allocate(true, 0.1 ether, deltaLiquidity, address(this)); assertEq(SY.balanceOf(address(this)), thisPreBalanceSY - deltaXWad); assertEq(PT.balanceOf(address(this)), thisPreBalancePT - deltaYWad); @@ -65,25 +65,25 @@ contract AllocateTest is SetUp { deal(address(SY), address(this), 1_000 ether); (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = - rmm.prepareAllocate(0.1 ether, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + rmm.prepareAllocate(true, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); vm.expectEmit(true, true, true, true); emit Allocate(address(this), address(this), deltaXWad, deltaYWad, deltaLiquidity); - rmm.allocate(deltaXWad, deltaYWad, 0, address(this)); + rmm.allocate(true, 0.1 ether, deltaLiquidity, address(this)); } function test_allocate_RevertsIfInsufficientLiquidityOut() public useDefaultPool { deal(address(SY), address(this), 1_000 ether); (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = - rmm.prepareAllocate(0.1 ether, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + rmm.prepareAllocate(true, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); vm.expectRevert( abi.encodeWithSelector( - InsufficientLiquidityOut.selector, deltaXWad, deltaYWad, deltaLiquidity + 1, deltaLiquidity + InsufficientLiquidityOut.selector, true, 0.1 ether, deltaLiquidity + 1, deltaLiquidity ) ); - rmm.allocate(deltaXWad, deltaYWad, deltaLiquidity + 1, address(this)); + rmm.allocate(true, 0.1 ether, deltaLiquidity + 1, address(this)); } } diff --git a/test/unit/Deallocate.t.sol b/test/unit/Deallocate.t.sol index 6916fe7..b73e902 100644 --- a/test/unit/Deallocate.t.sol +++ b/test/unit/Deallocate.t.sol @@ -8,10 +8,10 @@ import {InsufficientOutput} from "../../src/lib/RmmErrors.sol"; contract DeallocateTest is SetUp { function test_deallocate_BurnsLiquidity() public useDefaultPool { - (uint256 deltaXWad, uint256 deltaYWad,,) = - rmm.prepareAllocate(0.1 ether, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = + rmm.prepareAllocate(true, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); - (uint256 deltaLiquidity) = rmm.allocate(deltaXWad, deltaYWad, 0, address(this)); + deltaLiquidity = rmm.allocate(true, 0.1 ether, deltaLiquidity, address(this)); uint256 lptBurned; (deltaXWad, deltaYWad, lptBurned) = rmm.prepareDeallocate(deltaLiquidity / 2); @@ -22,10 +22,10 @@ contract DeallocateTest is SetUp { } function test_deallocate_AdjustsPool() public useDefaultPool withSY(address(this), 1_000 ether) { - (uint256 deltaXWad, uint256 deltaYWad,,) = - rmm.prepareAllocate(0.1 ether, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = + rmm.prepareAllocate(true, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); - (uint256 deltaLiquidity) = rmm.allocate(deltaXWad, deltaYWad, 0, address(this)); + deltaLiquidity = rmm.allocate(true, 0.1 ether, deltaLiquidity, address(this)); uint256 lptBurned; (deltaXWad, deltaYWad, lptBurned) = rmm.prepareDeallocate(deltaLiquidity / 2); @@ -40,9 +40,9 @@ contract DeallocateTest is SetUp { } function test_deallocate_TransfersTokens() public useDefaultPool withSY(address(this), 1_000 ether) { - (uint256 deltaXWad, uint256 deltaYWad,,) = - rmm.prepareAllocate(0.1 ether, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); - (uint256 deltaLiquidity) = rmm.allocate(deltaXWad, deltaYWad, 0, address(this)); + (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = + rmm.prepareAllocate(true, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + deltaLiquidity = rmm.allocate(true, 0.1 ether, deltaLiquidity, address(this)); uint256 lptBurned; uint256 thisPreBalanceSY = SY.balanceOf(address(this)); @@ -60,9 +60,9 @@ contract DeallocateTest is SetUp { } function test_deallocate_EmitsDeallocate() public useDefaultPool withSY(address(this), 1_000 ether) { - (uint256 deltaXWad, uint256 deltaYWad,,) = - rmm.prepareAllocate(0.1 ether, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); - (uint256 deltaLiquidity) = rmm.allocate(deltaXWad, deltaYWad, 0, address(this)); + (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = + rmm.prepareAllocate(true, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + deltaLiquidity = rmm.allocate(true, 0.1 ether, deltaLiquidity, address(this)); uint256 lptBurned; (deltaXWad, deltaYWad, lptBurned) = rmm.prepareDeallocate(deltaLiquidity / 2); @@ -73,9 +73,9 @@ contract DeallocateTest is SetUp { } function test_deallocate_RevertsIfInsufficientSYOutput() public useDefaultPool withSY(address(this), 1_000 ether) { - (uint256 deltaXWad, uint256 deltaYWad,,) = - rmm.prepareAllocate(0.1 ether, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); - (uint256 deltaLiquidity) = rmm.allocate(deltaXWad, deltaYWad, 0, address(this)); + (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = + rmm.prepareAllocate(true, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + deltaLiquidity = rmm.allocate(true, 0.1 ether, deltaLiquidity, address(this)); uint256 lptBurned; (deltaXWad, deltaYWad, lptBurned) = rmm.prepareDeallocate(deltaLiquidity / 2); @@ -86,9 +86,9 @@ contract DeallocateTest is SetUp { } function test_deallocate_RevertsIfInsufficientPTOutput() public useDefaultPool withSY(address(this), 1_000 ether) { - (uint256 deltaXWad, uint256 deltaYWad,,) = - rmm.prepareAllocate(0.1 ether, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); - (uint256 deltaLiquidity) = rmm.allocate(deltaXWad, deltaYWad, 0, address(this)); + (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = + rmm.prepareAllocate(true, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + deltaLiquidity = rmm.allocate(true, 0.1 ether, deltaLiquidity, address(this)); uint256 lptBurned; (deltaXWad, deltaYWad, lptBurned) = rmm.prepareDeallocate(deltaLiquidity / 2); diff --git a/test/unit/SwapPt.sol b/test/unit/SwapPt.sol index 39e6106..4aee75f 100644 --- a/test/unit/SwapPt.sol +++ b/test/unit/SwapPt.sol @@ -8,9 +8,9 @@ import {InsufficientOutput} from "../../src/lib/RmmErrors.sol"; contract SwapPtTest is SetUp { function test_swapPt_AdjustsPool() public useDefaultPool withSY(address(this), 1_000 ether) { - (uint256 deltaXWad, uint256 deltaYWad,,) = - rmm.prepareAllocate(1 ether, 1 ether, PYIndex.wrap(YT.pyIndexCurrent())); - rmm.allocate(deltaXWad, deltaYWad, 0, address(this)); + (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = + rmm.prepareAllocate(true, 1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + rmm.allocate(true, 1 ether, deltaLiquidity, address(this)); PYIndex index = PYIndex.wrap(YT.pyIndexCurrent()); uint256 amountIn = 1 ether; @@ -26,9 +26,9 @@ contract SwapPtTest is SetUp { } function test_swapPt_TransfersTokens() public useDefaultPool withSY(address(this), 1_000 ether) { - (uint256 deltaXWad, uint256 deltaYWad,,) = - rmm.prepareAllocate(1 ether, 1 ether, PYIndex.wrap(YT.pyIndexCurrent())); - rmm.allocate(deltaXWad, deltaYWad, 0, address(this)); + (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = + rmm.prepareAllocate(true, 1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + rmm.allocate(true, 1 ether, deltaLiquidity, address(this)); PYIndex index = PYIndex.wrap(YT.pyIndexCurrent()); uint256 amountIn = 1 ether; @@ -48,23 +48,23 @@ contract SwapPtTest is SetUp { } function test_swapPt_EmitsSwap() public useDefaultPool withSY(address(this), 1_000 ether) { - (uint256 deltaXWad, uint256 deltaYWad,,) = - rmm.prepareAllocate(1 ether, 1 ether, PYIndex.wrap(YT.pyIndexCurrent())); - rmm.allocate(deltaXWad, deltaYWad, 0, address(this)); + (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = + rmm.prepareAllocate(true, 1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + rmm.allocate(true, 1 ether, deltaLiquidity, address(this)); PYIndex index = PYIndex.wrap(YT.pyIndexCurrent()); uint256 amountIn = 1 ether; - (,, uint256 minAmountOut, int256 deltaLiquidity,) = rmm.prepareSwapPtIn(amountIn, block.timestamp, index); + (,, uint256 minAmountOut, int256 deltaLiquiditySwap,) = rmm.prepareSwapPtIn(amountIn, block.timestamp, index); vm.expectEmit(true, true, true, true); - emit Swap(address(this), address(this), address(PT), address(SY), amountIn, minAmountOut, deltaLiquidity); + emit Swap(address(this), address(this), address(PT), address(SY), amountIn, minAmountOut, deltaLiquiditySwap); rmm.swapExactPtForSy(amountIn, minAmountOut, address(this)); } function test_swapPt_RevertsWhenInsufficientOutput() public useDefaultPool withSY(address(this), 1_000 ether) { - (uint256 deltaXWad, uint256 deltaYWad,,) = - rmm.prepareAllocate(1 ether, 1 ether, PYIndex.wrap(YT.pyIndexCurrent())); - rmm.allocate(deltaXWad, deltaYWad, 0, address(this)); + (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = + rmm.prepareAllocate(true, 1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + rmm.allocate(true, 1 ether, deltaLiquidity, address(this)); PYIndex index = PYIndex.wrap(YT.pyIndexCurrent()); uint256 amountIn = 1 ether; diff --git a/test/unit/SwapSy.sol b/test/unit/SwapSy.sol index 4fd5df5..0d57b11 100644 --- a/test/unit/SwapSy.sol +++ b/test/unit/SwapSy.sol @@ -8,9 +8,9 @@ import {InsufficientOutput} from "../../src/lib/RmmErrors.sol"; contract SwapSyTest is SetUp { function test_swapSy_AdjustsPool() public useDefaultPool withSY(address(this), 1_000 ether) { - (uint256 deltaXWad, uint256 deltaYWad,,) = - rmm.prepareAllocate(1 ether, 1 ether, PYIndex.wrap(YT.pyIndexCurrent())); - rmm.allocate(deltaXWad, deltaYWad, 0, address(this)); + (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = + rmm.prepareAllocate(true, 1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + rmm.allocate(true, 1 ether, deltaLiquidity, address(this)); PYIndex index = PYIndex.wrap(YT.pyIndexCurrent()); uint256 amountIn = 1 ether; @@ -26,9 +26,9 @@ contract SwapSyTest is SetUp { } function test_swapSy_TransfersTokens() public useDefaultPool withSY(address(this), 1_000 ether) { - (uint256 deltaXWad, uint256 deltaYWad,,) = - rmm.prepareAllocate(1 ether, 1 ether, PYIndex.wrap(YT.pyIndexCurrent())); - rmm.allocate(deltaXWad, deltaYWad, 0, address(this)); + (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = + rmm.prepareAllocate(true, 1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + rmm.allocate(true, 1 ether, deltaLiquidity, address(this)); PYIndex index = PYIndex.wrap(YT.pyIndexCurrent()); uint256 amountIn = 1 ether; @@ -48,23 +48,23 @@ contract SwapSyTest is SetUp { } function test_swapSy_EmitsSwap() public useDefaultPool withSY(address(this), 1_000 ether) { - (uint256 deltaXWad, uint256 deltaYWad,,) = - rmm.prepareAllocate(1 ether, 1 ether, PYIndex.wrap(YT.pyIndexCurrent())); - rmm.allocate(deltaXWad, deltaYWad, 0, address(this)); + (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = + rmm.prepareAllocate(true, 1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + rmm.allocate(true, 1 ether, deltaLiquidity, address(this)); PYIndex index = PYIndex.wrap(YT.pyIndexCurrent()); uint256 amountIn = 1 ether; - (,, uint256 minAmountOut, int256 deltaLiquidity,) = rmm.prepareSwapSyIn(amountIn, block.timestamp, index); + (,, uint256 minAmountOut, int256 deltaLiquiditySwap,) = rmm.prepareSwapSyIn(amountIn, block.timestamp, index); vm.expectEmit(true, true, true, true); - emit Swap(address(this), address(this), address(SY), address(PT), amountIn, minAmountOut, deltaLiquidity); + emit Swap(address(this), address(this), address(SY), address(PT), amountIn, minAmountOut, deltaLiquiditySwap); rmm.swapExactSyForPt(amountIn, 0, address(this)); } function test_swapSy_RevertsWhenInsufficientOutput() public useDefaultPool withSY(address(this), 1_000 ether) { - (uint256 deltaXWad, uint256 deltaYWad,,) = - rmm.prepareAllocate(1 ether, 1 ether, PYIndex.wrap(YT.pyIndexCurrent())); - rmm.allocate(deltaXWad, deltaYWad, 0, address(this)); + (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = + rmm.prepareAllocate(true, 1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + rmm.allocate(true, 1 ether, deltaLiquidity, address(this)); PYIndex index = PYIndex.wrap(YT.pyIndexCurrent()); uint256 amountIn = 1 ether; From d6d0db33cfc0c97baed5d89c1c7d0fa7d8383c8e Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Wed, 26 Jun 2024 15:50:35 -0400 Subject: [PATCH 026/116] assert strike >= 1e18 instead of simply > 1e18 --- src/RMM.sol | 2 +- test/unit/Init.t.sol | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/RMM.sol b/src/RMM.sol index d042ffa..4ef473b 100644 --- a/src/RMM.sol +++ b/src/RMM.sol @@ -82,7 +82,7 @@ contract RMM is ERC20 { address curator_ ) external lock { if (strike != 0) revert AlreadyInitialized(); - if (strike_ < 1e18) revert InvalidStrike(); + if (strike_ <= 1e18) revert InvalidStrike(); PT = IPPrincipalToken(PT_); SY = IStandardizedYield(PT.SY()); YT = IPYieldToken(PT.YT()); diff --git a/test/unit/Init.t.sol b/test/unit/Init.t.sol index 3f2bb5e..101c4eb 100644 --- a/test/unit/Init.t.sol +++ b/test/unit/Init.t.sol @@ -97,9 +97,9 @@ contract InitTest is SetUp { initParams.curator ); - assertEq(rmm.lastTimestamp(), block.timestamp); - assertEq(rmm.reserveX(), initParams.amountX); - assertEq(rmm.reserveY(), amountY); + assertEq(rmm.lastTimestamp(), block.timestamp, "lastTimestamp"); + assertEq(rmm.reserveX(), initParams.amountX, "reserveX"); + assertEq(rmm.reserveY(), amountY, "reserveY"); } function test_init_TransfersTokens() From bf237b8ca5b118071539503f391972915e86e500 Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Wed, 26 Jun 2024 15:52:26 -0400 Subject: [PATCH 027/116] pass amountX instead of totalAsset to prepareInit --- src/RMM.sol | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/RMM.sol b/src/RMM.sol index 4ef473b..47f6753 100644 --- a/src/RMM.sol +++ b/src/RMM.sol @@ -100,8 +100,6 @@ contract RMM is ERC20 { } PYIndex index = YT.newIndex(); - uint256 totalAsset = index.syToAsset(amountX); - sigma = sigma_; maturity = PT.expiry(); fee = fee_; @@ -109,7 +107,7 @@ contract RMM is ERC20 { initTimestamp = block.timestamp; curator = curator_; - (uint256 totalLiquidity_, uint256 amountY) = prepareInit(priceX, totalAsset, strike_, sigma_, maturity); + (uint256 totalLiquidity_, uint256 amountY) = prepareInit(priceX, amountX, strike_, sigma_, maturity, index); _mint(msg.sender, totalLiquidity_ - BURNT_LIQUIDITY); _mint(address(0), BURNT_LIQUIDITY); @@ -528,11 +526,15 @@ contract RMM is ERC20 { } //prepare calls - function prepareInit(uint256 priceX, uint256 totalAsset, uint256 strike_, uint256 sigma_, uint256 maturity_) - public - view - returns (uint256 totalLiquidity_, uint256 amountY) - { + function prepareInit( + uint256 priceX, + uint256 amountX, + uint256 strike_, + uint256 sigma_, + uint256 maturity_, + PYIndex index + ) public view returns (uint256 totalLiquidity_, uint256 amountY) { + uint256 totalAsset = index.syToAsset(amountX); uint256 tau_ = computeTauWadYears(maturity_ - block.timestamp); PoolPreCompute memory comp = PoolPreCompute({reserveInAsset: totalAsset, strike_: strike_, tau_: tau_}); uint256 initialLiquidity = From 58cb7d139c34c56cf7e887b2108ddcd71ae571bd Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Wed, 26 Jun 2024 16:04:59 -0400 Subject: [PATCH 028/116] fix init tests --- test/SetUp.sol | 6 ++---- test/invariant/RMMHandler.sol | 4 +++- test/unit/Init.t.sol | 18 +++++++++++++----- test/unit/RMM.t.sol | 4 ++-- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/test/SetUp.sol b/test/SetUp.sol index 995d963..d643e87 100644 --- a/test/SetUp.sol +++ b/test/SetUp.sol @@ -18,12 +18,11 @@ import {RMM} from "./../src/RMM.sol"; struct InitParams { uint256 priceX; - uint256 totalAsset; + uint256 amountX; uint256 strike; uint256 sigma; uint256 maturity; address PT; - uint256 amountX; uint256 fee; address curator; } @@ -119,12 +118,11 @@ contract SetUp is Test { function getDefaultParams() internal view returns (InitParams memory) { return InitParams({ priceX: 1.15 ether, - totalAsset: 1 ether, + amountX: 100 ether, strike: 1.15 ether, sigma: 0.02 ether, maturity: PT.expiry(), PT: address(PT), - amountX: 100 ether, fee: 0.00016 ether, curator: address(0x55) }); diff --git a/test/invariant/RMMHandler.sol b/test/invariant/RMMHandler.sol index d3d7e5b..b3c56a1 100644 --- a/test/invariant/RMMHandler.sol +++ b/test/invariant/RMMHandler.sol @@ -93,7 +93,9 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { uint256 sigma = 0.025 ether; uint256 fee = 0.00016 ether; - (uint256 totalLiquidity, uint256 amountY) = rmm.prepareInit(priceX, amountX, strike, sigma, PT.expiry()); + PYIndex index = PYIndex.wrap(rmm.YT().pyIndexCurrent()); + + (uint256 totalLiquidity, uint256 amountY) = rmm.prepareInit(priceX, amountX, strike, sigma, PT.expiry(), index); PT.approve(address(rmm), type(uint256).max); SY.approve(address(rmm), type(uint256).max); diff --git a/test/unit/Init.t.sol b/test/unit/Init.t.sol index 101c4eb..b4d9997 100644 --- a/test/unit/Init.t.sol +++ b/test/unit/Init.t.sol @@ -4,8 +4,12 @@ pragma solidity ^0.8.13; import {SetUp, RMM, InitParams} from "../SetUp.sol"; import {Init} from "../../src/lib/RmmEvents.sol"; import {AlreadyInitialized, InvalidStrike} from "../../src/lib/RmmErrors.sol"; +import {PYIndex, PYIndexLib, IPYieldToken} from "./../../src/RMM.sol"; contract InitTest is SetUp { + using PYIndexLib for IPYieldToken; + using PYIndexLib for PYIndex; + function test_init_StoresInitParams() public withSY(address(this), 2000000 ether) @@ -60,9 +64,10 @@ contract InitTest is SetUp { withPY(address(this), 1000000 ether) { InitParams memory initParams = getDefaultParams(); + PYIndex index = IPYieldToken(PT.YT()).newIndex(); (uint256 totalLiquidity,) = rmm.prepareInit( - initParams.priceX, initParams.totalAsset, initParams.strike, initParams.sigma, initParams.maturity + initParams.priceX, initParams.amountX, initParams.strike, initParams.sigma, initParams.maturity, index ); rmm.init( @@ -82,9 +87,10 @@ contract InitTest is SetUp { function test_init_AdjustsPool() public withSY(address(this), 2000000 ether) withPY(address(this), 1000000 ether) { InitParams memory initParams = getDefaultParams(); + PYIndex index = IPYieldToken(PT.YT()).newIndex(); (, uint256 amountY) = rmm.prepareInit( - initParams.priceX, initParams.totalAsset, initParams.strike, initParams.sigma, initParams.maturity + initParams.priceX, initParams.amountX, initParams.strike, initParams.sigma, initParams.maturity, index ); rmm.init( @@ -108,9 +114,10 @@ contract InitTest is SetUp { withPY(address(this), 1000000 ether) { InitParams memory initParams = getDefaultParams(); + PYIndex index = IPYieldToken(PT.YT()).newIndex(); (, uint256 amountY) = rmm.prepareInit( - initParams.priceX, initParams.totalAsset, initParams.strike, initParams.sigma, initParams.maturity + initParams.priceX, initParams.amountX, initParams.strike, initParams.sigma, initParams.maturity, index ); uint256 thisPreBalanceSY = SY.balanceOf(address(this)); @@ -140,9 +147,10 @@ contract InitTest is SetUp { withPY(address(this), 1000000 ether) { InitParams memory initParams = getDefaultParams(); + PYIndex index = IPYieldToken(PT.YT()).newIndex(); (uint256 totalLiquidity, uint256 amountY) = rmm.prepareInit( - initParams.priceX, initParams.totalAsset, initParams.strike, initParams.sigma, initParams.maturity + initParams.priceX, initParams.amountX, initParams.strike, initParams.sigma, initParams.maturity, index ); vm.expectEmit(true, true, true, true); @@ -206,7 +214,7 @@ contract InitTest is SetUp { initParams.PT, initParams.priceX, initParams.amountX, - 1 ether - 1, + 1 ether, initParams.sigma, initParams.fee, initParams.curator diff --git a/test/unit/RMM.t.sol b/test/unit/RMM.t.sol index 27f87c6..c9d4eb4 100644 --- a/test/unit/RMM.t.sol +++ b/test/unit/RMM.t.sol @@ -130,7 +130,7 @@ contract RMMTest is Test { PT_: address(PT), priceX: price, amountX: amountX, // using the equivalent amount of tokens - strike_: 1 ether, //uint256(pendleRateAnchor), + strike_: 1.05 ether, //uint256(pendleRateAnchor), sigma_: 0.015 ether, fee_: 0.00016 ether, curator_: address(0x55) @@ -324,7 +324,7 @@ contract RMMTest is Test { InitParams basicParams = InitParams({ priceX: 1 ether, amountX: 1 ether, - strike: 1 ether, + strike: 1.05 ether, sigma: 0.015 ether, fee: 0, maturity: 1_717_214_400, From ff74153e4f2931d691bcf8ba5e4c59fb729632ab Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Wed, 26 Jun 2024 16:37:36 -0400 Subject: [PATCH 029/116] fix invariant init failure --- test/invariant/RMMHandler.sol | 2 +- test/unit/Init.t.sol | 2 +- test/unit/SwapExactSyForYt.t.sol | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/invariant/RMMHandler.sol b/test/invariant/RMMHandler.sol index b3c56a1..c31615a 100644 --- a/test/invariant/RMMHandler.sol +++ b/test/invariant/RMMHandler.sol @@ -93,7 +93,7 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { uint256 sigma = 0.025 ether; uint256 fee = 0.00016 ether; - PYIndex index = PYIndex.wrap(rmm.YT().pyIndexCurrent()); + PYIndex index = IPYieldToken(PT.YT()).newIndex(); (uint256 totalLiquidity, uint256 amountY) = rmm.prepareInit(priceX, amountX, strike, sigma, PT.expiry(), index); diff --git a/test/unit/Init.t.sol b/test/unit/Init.t.sol index b4d9997..5c6908a 100644 --- a/test/unit/Init.t.sol +++ b/test/unit/Init.t.sol @@ -153,7 +153,7 @@ contract InitTest is SetUp { initParams.priceX, initParams.amountX, initParams.strike, initParams.sigma, initParams.maturity, index ); - vm.expectEmit(true, true, true, true); + vm.expectEmit(); emit Init( address(this), diff --git a/test/unit/SwapExactSyForYt.t.sol b/test/unit/SwapExactSyForYt.t.sol index e27df21..54d1e8f 100644 --- a/test/unit/SwapExactSyForYt.t.sol +++ b/test/unit/SwapExactSyForYt.t.sol @@ -71,7 +71,7 @@ contract SwapExactSyForYtTest is SetUp { rmm.prepareSwapPtIn(ytOut, block.timestamp, index); uint256 delta = index.assetToSyUp(amountInWad) - amountOutWad; - vm.expectEmit(true, true, true, true); + vm.expectEmit(); emit Swap(address(this), address(0xbeef), address(SY), address(YT), delta, amountOut, deltaLiquidity); rmm.swapExactSyForYt(exactSYIn, ytOut, ytOut.mulDivDown(95, 100), 500 ether, 10_000, address(0xbeef)); } @@ -80,7 +80,7 @@ contract SwapExactSyForYtTest is SetUp { uint256 exactSYIn = 1 ether; PYIndex index = YT.newIndex(); uint256 ytOut = rmm.computeSYToYT(index, exactSYIn, 500 ether, block.timestamp, 0, 10_000); - (uint256 amountInWad, uint256 amountOutWad,,,) = rmm.prepareSwapPtIn(ytOut, block.timestamp, index); + (, uint256 amountOutWad,,,) = rmm.prepareSwapPtIn(ytOut, block.timestamp, index); vm.expectRevert(); rmm.swapExactSyForYt(exactSYIn - 1 ether, ytOut, amountOutWad, 500 ether, 10_000, address(this)); @@ -90,7 +90,7 @@ contract SwapExactSyForYtTest is SetUp { uint256 exactSYIn = 1 ether; PYIndex index = YT.newIndex(); uint256 ytOut = rmm.computeSYToYT(index, exactSYIn, 500 ether, block.timestamp, 0, 10_000); - (uint256 amountInWad, uint256 amountOutWad,,,) = rmm.prepareSwapPtIn(ytOut, block.timestamp, index); + (, uint256 amountOutWad,,,) = rmm.prepareSwapPtIn(ytOut, block.timestamp, index); vm.expectRevert(); rmm.swapExactSyForYt(exactSYIn, ytOut, amountOutWad + 1 ether, 500 ether, 10_000, address(this)); From bf41f48f0f55079580d9c336cb8fa4ad6b6c552d Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Wed, 26 Jun 2024 17:17:53 -0400 Subject: [PATCH 030/116] fiddling with invariant tests --- test/invariant/RMMHandler.sol | 4 ++-- test/invariant/RMMInvariants.t.sol | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/invariant/RMMHandler.sol b/test/invariant/RMMHandler.sol index c31615a..1bac65a 100644 --- a/test/invariant/RMMHandler.sol +++ b/test/invariant/RMMHandler.sol @@ -87,9 +87,9 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { fee = bound(fee, 0 ether, 1 ether); */ - uint256 priceX = 1.15 ether; + uint256 priceX = 1.05 ether; uint256 amountX = 1000 ether; - uint256 strike = 1.15 ether; + uint256 strike = 1.05 ether; uint256 sigma = 0.025 ether; uint256 fee = 0.00016 ether; diff --git a/test/invariant/RMMInvariants.t.sol b/test/invariant/RMMInvariants.t.sol index 711a768..e6b01d1 100644 --- a/test/invariant/RMMInvariants.t.sol +++ b/test/invariant/RMMInvariants.t.sol @@ -40,12 +40,12 @@ contract RMMInvariantsTest is SetUp { function afterInvariant() public view { console.log("Calls: ", handler.totalCalls()); console.log("Allocate: ", handler.calls(RMMHandler.allocate.selector)); - console.log("Deallocate: ", handler.calls(RMMHandler.deallocate.selector)); - console.log("SwapExactSyForYt: ", handler.calls(RMMHandler.swapExactSyForYt.selector)); - console.log("SwapExactTokenForYt: ", handler.calls(RMMHandler.swapExactTokenForYt.selector)); - console.log("SwapExactPtForSy: ", handler.calls(RMMHandler.swapExactPtForSy.selector)); - console.log("SwapExactSyForPt: ", handler.calls(RMMHandler.swapExactSyForPt.selector)); - console.log("SwapExactYtForSy: ", handler.calls(RMMHandler.swapExactYtForSy.selector)); + // console.log("Deallocate: ", handler.calls(RMMHandler.deallocate.selector)); + // console.log("SwapExactSyForYt: ", handler.calls(RMMHandler.swapExactSyForYt.selector)); + // console.log("SwapExactTokenForYt: ", handler.calls(RMMHandler.swapExactTokenForYt.selector)); + // console.log("SwapExactPtForSy: ", handler.calls(RMMHandler.swapExactPtForSy.selector)); + // console.log("SwapExactSyForPt: ", handler.calls(RMMHandler.swapExactSyForPt.selector)); + // console.log("SwapExactYtForSy: ", handler.calls(RMMHandler.swapExactYtForSy.selector)); } /// forge-config: default.invariant.runs = 10 From 353c5740e6617b6b79c034d6771f46246ebf07bb Mon Sep 17 00:00:00 2001 From: kinrezc Date: Thu, 27 Jun 2024 14:50:03 -0400 Subject: [PATCH 031/116] wip rY invariant tests --- test/invariant/RMMHandler.sol | 63 ++++++++++++++++++++---------- test/invariant/RMMInvariants.t.sol | 25 ++++++++---- 2 files changed, 60 insertions(+), 28 deletions(-) diff --git a/test/invariant/RMMHandler.sol b/test/invariant/RMMHandler.sol index 1bac65a..1cf4136 100644 --- a/test/invariant/RMMHandler.sol +++ b/test/invariant/RMMHandler.sol @@ -79,19 +79,18 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { } function init() public { - /* - priceX = bound(priceX, 1 ether, 10 ether); - amountX = bound(amountX, 1 ether, 10 ether); - strike = bound(strike, 1 ether, 10 ether); - sigma = bound(sigma, 0 ether, 1 ether); - fee = bound(fee, 0 ether, 1 ether); - */ - - uint256 priceX = 1.05 ether; - uint256 amountX = 1000 ether; - uint256 strike = 1.05 ether; - uint256 sigma = 0.025 ether; - uint256 fee = 0.00016 ether; + uint256 priceX; + uint256 amountX; + uint256 strike; + uint256 sigma; + uint256 fee; + + priceX = bound(priceX, 1.05 ether, 1.15 ether); + amountX = bound(amountX, 100 ether, 1000 ether); + strike = bound(strike, 1.05 ether, 1.15 ether); + sigma = bound(sigma, 0.02 ether, 0.05 ether); + fee = bound(fee, 0.0001 ether, 0.001 ether); + PYIndex index = IPYieldToken(PT.YT()).newIndex(); @@ -106,6 +105,13 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { ghost_reserveX += amountX; ghost_reserveY += amountY; ghost_totalLiquidity += int256(totalLiquidity); + + console2.log("ghost_reserveX", ghost_reserveX); + console2.log("rmm.reserveX()", rmm.reserveX()); + console2.log("ghost_reserveY", ghost_reserveY); + console2.log("rmm.reserveY()", rmm.reserveY()); + console2.log("ghost_totalLiquidity", ghost_totalLiquidity); + console2.log("rmm.totalLiquidity()", rmm.totalLiquidity()); } // Target functions @@ -113,16 +119,17 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { function allocate(uint256 deltaX, uint256 deltaY) public createActor countCall(this.allocate.selector) { deltaX = bound(deltaX, 0.1 ether, 10 ether); - deal(address(SY), currentActor, deltaX); - deal(address(PT), currentActor, deltaY); vm.startPrank(currentActor); (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = rmm.prepareAllocate(true, deltaX, PYIndex.wrap(rmm.YT().pyIndexCurrent())); - SY.approve(address(rmm), deltaX); - PT.approve(address(rmm), deltaY); + deal(address(SY), currentActor, deltaXWad); + deal(address(PT), currentActor, deltaYWad); + + SY.approve(address(rmm), deltaXWad); + PT.approve(address(rmm), deltaYWad); uint256 realDeltaLiquidity = rmm.allocate(true, deltaX, deltaLiquidity, address(currentActor)); vm.stopPrank(); @@ -146,8 +153,10 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { uint256 exactSYIn = 1 ether; deal(address(SY), address(currentActor), exactSYIn); + uint256 rX = rmm.reserveX(); + PYIndex index = YT.newIndex(); - uint256 ytOut = rmm.computeSYToYT(index, exactSYIn, 500 ether, block.timestamp, 0, 10_000); + uint256 ytOut = rmm.computeSYToYT(index, exactSYIn, rX.mulDivDown(95, 100), block.timestamp, 0, 10_000); vm.startPrank(currentActor); SY.approve(address(rmm), exactSYIn); @@ -228,12 +237,24 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { deal(address(YT), currentActor, ytIn); vm.startPrank(currentActor); YT.approve(address(rmm), ytIn); + console2.log("rYPre", rmm.reserveY()); (uint256 amountOut, uint256 amountIn, int256 deltaLiquidity) = rmm.swapExactYtForSy(ytIn, 1000 ether, address(currentActor)); vm.stopPrank(); - ghost_reserveX += amountIn; - ghost_reserveY -= amountOut; - ghost_totalLiquidity += int256(deltaLiquidity); + // the workflow here is: + // 1. YT -> RMM + // 2. Flash amountYt of PT from RMM, so the reserveY should be reduced by ytIn + // 3. recombine the YT and PT into SY + // 4. send the SY to rmm to cover the cost of the PT + // 5. remainder SY sent to currentActor + // in the end the reserves are mutated such that rY = rYStart - ytIn, rX = rXStart + syCreated - sySwapped + // ghost_reserveX += amountIn; + console2.log("ghost_reserveY", ghost_reserveY); + console2.log("amountIn", amountIn); + console2.log("ghost_reserveY - amountIn", ghost_reserveY - amountIn); + console2.log("rmm.reserveY()", rmm.reserveY()); + ghost_reserveY -= ytIn; + // ghost_totalLiquidity += int256(deltaLiquidity); } } diff --git a/test/invariant/RMMInvariants.t.sol b/test/invariant/RMMInvariants.t.sol index e6b01d1..280e1fa 100644 --- a/test/invariant/RMMInvariants.t.sol +++ b/test/invariant/RMMInvariants.t.sol @@ -7,6 +7,7 @@ import {abs} from "./../../src/lib/RmmLib.sol"; import {IPYieldToken} from "pendle/interfaces/IPYieldToken.sol"; import {SetUp} from "../SetUp.sol"; import {RMMHandler} from "./RMMHandler.sol"; +import "forge-std/console2.sol"; contract RMMInvariantsTest is SetUp { using PYIndexLib for IPYieldToken; @@ -31,21 +32,30 @@ contract RMMInvariantsTest is SetUp { selectors[4] = RMMHandler.swapExactSyForPt.selector; selectors[5] = RMMHandler.swapExactYtForSy.selector; selectors[6] = RMMHandler.swapExactTokenForYt.selector; - + + console.logBytes4(selectors[0]); + console.logBytes4(selectors[1]); + console.logBytes4(selectors[2]); + console.logBytes4(selectors[3]); + console.logBytes4(selectors[4]); + console.logBytes4(selectors[5]); + console.logBytes4(selectors[6]); targetSelector(FuzzSelector({addr: address(handler), selectors: selectors})); + console2.log("got here?"); targetContract(address(handler)); + console2.log("here?"); } function afterInvariant() public view { console.log("Calls: ", handler.totalCalls()); console.log("Allocate: ", handler.calls(RMMHandler.allocate.selector)); - // console.log("Deallocate: ", handler.calls(RMMHandler.deallocate.selector)); - // console.log("SwapExactSyForYt: ", handler.calls(RMMHandler.swapExactSyForYt.selector)); - // console.log("SwapExactTokenForYt: ", handler.calls(RMMHandler.swapExactTokenForYt.selector)); - // console.log("SwapExactPtForSy: ", handler.calls(RMMHandler.swapExactPtForSy.selector)); - // console.log("SwapExactSyForPt: ", handler.calls(RMMHandler.swapExactSyForPt.selector)); - // console.log("SwapExactYtForSy: ", handler.calls(RMMHandler.swapExactYtForSy.selector)); + console.log("Deallocate: ", handler.calls(RMMHandler.deallocate.selector)); + console.log("SwapExactSyForYt: ", handler.calls(RMMHandler.swapExactSyForYt.selector)); + console.log("SwapExactTokenForYt: ", handler.calls(RMMHandler.swapExactTokenForYt.selector)); + console.log("SwapExactPtForSy: ", handler.calls(RMMHandler.swapExactPtForSy.selector)); + console.log("SwapExactSyForPt: ", handler.calls(RMMHandler.swapExactSyForPt.selector)); + console.log("SwapExactYtForSy: ", handler.calls(RMMHandler.swapExactYtForSy.selector)); } /// forge-config: default.invariant.runs = 10 @@ -68,6 +78,7 @@ contract RMMInvariantsTest is SetUp { /// forge-config: default.invariant.depth = 100 /// forge-config: default.invariant.fail-on-revert = true function invariant_ReserveY() public view { + console2.log("here?"); assertEq(rmm.reserveY(), handler.ghost_reserveY()); } } From 2d3346b37fcdc48aa66750642310fc5cbdea3df7 Mon Sep 17 00:00:00 2001 From: kinrezc Date: Thu, 27 Jun 2024 16:09:10 -0400 Subject: [PATCH 032/116] fix sy for pt ghost reserves --- test/invariant/RMMHandler.sol | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/test/invariant/RMMHandler.sol b/test/invariant/RMMHandler.sol index 1cf4136..c0add3e 100644 --- a/test/invariant/RMMHandler.sol +++ b/test/invariant/RMMHandler.sol @@ -105,13 +105,6 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { ghost_reserveX += amountX; ghost_reserveY += amountY; ghost_totalLiquidity += int256(totalLiquidity); - - console2.log("ghost_reserveX", ghost_reserveX); - console2.log("rmm.reserveX()", rmm.reserveX()); - console2.log("ghost_reserveY", ghost_reserveY); - console2.log("rmm.reserveY()", rmm.reserveY()); - console2.log("ghost_totalLiquidity", ghost_totalLiquidity); - console2.log("rmm.totalLiquidity()", rmm.totalLiquidity()); } // Target functions @@ -226,8 +219,8 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { (uint256 amountOut, int256 deltaLiquidity) = rmm.swapExactSyForPt(amountIn, 0, address(currentActor)); vm.stopPrank(); - ghost_reserveX -= amountOut; - ghost_reserveY += amountIn; + ghost_reserveX += amountIn; + ghost_reserveY -= amountOut; ghost_totalLiquidity += int256(deltaLiquidity); } From f3ff06c92dfa28f45e11757f06fe9b97e6a7bbca Mon Sep 17 00:00:00 2001 From: kinrezc Date: Thu, 27 Jun 2024 16:13:33 -0400 Subject: [PATCH 033/116] change tokenForYt upper bound approximation --- test/invariant/RMMHandler.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/invariant/RMMHandler.sol b/test/invariant/RMMHandler.sol index c0add3e..dfe17af 100644 --- a/test/invariant/RMMHandler.sol +++ b/test/invariant/RMMHandler.sol @@ -173,9 +173,9 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { PYIndex index = YT.newIndex(); (uint256 syMinted, uint256 ytOut) = - rmm.computeTokenToYT(index, address(weth), amountTokenIn, 10 ether, block.timestamp, 0, 1_000); + rmm.computeTokenToYT(index, address(weth), amountTokenIn, rmm.reserveX().mulDivDown(95, 100), block.timestamp, 0, 1_000); - uint256 amountPtIn = rmm.computeSYToYT(index, syMinted, 10 ether, block.timestamp, ytOut, 0.005 ether); + uint256 amountPtIn = rmm.computeSYToYT(index, syMinted, rmm.reserveX().mulDivDown(95, 100), block.timestamp, ytOut, 0.005 ether); (uint256 amountInWad, uint256 amountOutWad,, int256 deltaLiquidity,) = rmm.prepareSwapPtIn(amountPtIn, block.timestamp, index); From 6ee1a20511e21471fe2abab5e33219c3504820c2 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 28 Jun 2024 11:39:05 +0400 Subject: [PATCH 034/116] test: add test_swapExactPtForSy_EmitsEvent --- test/unit/RMM.t.sol | 13 ------------- test/unit/SwapExactPtForSy.t.sol | 9 +++++++++ 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/test/unit/RMM.t.sol b/test/unit/RMM.t.sol index c9d4eb4..a50ba5c 100644 --- a/test/unit/RMM.t.sol +++ b/test/unit/RMM.t.sol @@ -453,17 +453,4 @@ contract RMMTest is Test { ); subject().swapExactPtForSy(deltaPt, minAmountOut + 10, address(this)); } - - function test_swapPt_event() public basic { - PYIndex index = YT.newIndex(); - uint256 deltaPt = 1 ether; - (,, uint256 minAmountOut, int256 delLiq,) = subject().prepareSwapPtIn(deltaPt, block.timestamp, index); - deal(address(SY), address(subject()), minAmountOut); - deal(address(PT), address(this), deltaPt); - PT.approve(address(subject()), deltaPt); - - vm.expectEmit(); - emit Swap(address(this), address(this), address(PT), address(SY), deltaPt, minAmountOut, delLiq); - subject().swapExactPtForSy(deltaPt, minAmountOut, address(this)); - } } diff --git a/test/unit/SwapExactPtForSy.t.sol b/test/unit/SwapExactPtForSy.t.sol index f1f2b10..e900faf 100644 --- a/test/unit/SwapExactPtForSy.t.sol +++ b/test/unit/SwapExactPtForSy.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.13; import {ERC20} from "solmate/tokens/ERC20.sol"; import {InsufficientOutput} from "../../src/lib/RmmErrors.sol"; +import {Swap} from "../../src/lib/RmmEvents.sol"; import {SetUp} from "../SetUp.sol"; contract SwapExactPtForSyTest is SetUp { @@ -43,4 +44,12 @@ contract SwapExactPtForSyTest is SetUp { vm.expectRevert(); rmm.swapExactPtForSy(1 ether, type(uint256).max, address(this)); } + + function test_swapExactPtForSy_EmitsEvent() public useDefaultPool { + uint256 deltaPt = 1 ether; + (,, uint256 minAmountOut, int256 deltaLiquidity,) = rmm.prepareSwapPtIn(deltaPt, block.timestamp, newIndex()); + vm.expectEmit(true, true, true, true); + emit Swap(address(this), address(this), address(PT), address(SY), deltaPt, minAmountOut, deltaLiquidity); + rmm.swapExactPtForSy(deltaPt, minAmountOut, address(this)); + } } From 8d31296c29dd3c70959587b57f79b84fb6516735 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 28 Jun 2024 11:40:34 +0400 Subject: [PATCH 035/116] test: update test_swapExactPtForSy_RevertsIfInsufficientOutput --- test/unit/RMM.t.sol | 16 ---------------- test/unit/SwapExactPtForSy.t.sol | 7 +++++-- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/test/unit/RMM.t.sol b/test/unit/RMM.t.sol index a50ba5c..70b6ec2 100644 --- a/test/unit/RMM.t.sol +++ b/test/unit/RMM.t.sol @@ -437,20 +437,4 @@ contract RMMTest is Test { "Price did not increase after buying Y." ); } - - function test_swapPt_reverts_InsufficientOutput() public basic { - PYIndex index = YT.newIndex(); - uint256 deltaPt = 1 ether; - (,, uint256 minAmountOut,,) = subject().prepareSwapPtIn(deltaPt, block.timestamp, index); - - mintSY(minAmountOut); - mintPtYt(deltaPt); - - vm.expectRevert( - abi.encodeWithSelector( - InsufficientOutput.selector, upscale(deltaPt, scalar(address(PT))), minAmountOut + 10, minAmountOut - ) - ); - subject().swapExactPtForSy(deltaPt, minAmountOut + 10, address(this)); - } } diff --git a/test/unit/SwapExactPtForSy.t.sol b/test/unit/SwapExactPtForSy.t.sol index e900faf..547114e 100644 --- a/test/unit/SwapExactPtForSy.t.sol +++ b/test/unit/SwapExactPtForSy.t.sol @@ -41,8 +41,11 @@ contract SwapExactPtForSyTest is SetUp { } function test_swapExactPtForSy_RevertsIfInsufficientOutput() public useDefaultPool { - vm.expectRevert(); - rmm.swapExactPtForSy(1 ether, type(uint256).max, address(this)); + uint256 deltaPt = 1 ether; + (,, uint256 minAmountOut,,) = rmm.prepareSwapPtIn(deltaPt, block.timestamp, newIndex()); + + vm.expectRevert(abi.encodeWithSelector(InsufficientOutput.selector, deltaPt, minAmountOut + 10, minAmountOut)); + rmm.swapExactPtForSy(deltaPt, minAmountOut + 10, address(this)); } function test_swapExactPtForSy_EmitsEvent() public useDefaultPool { From 11f9370059f6fc15fe926b42c2a6705ec5daf6dd Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 28 Jun 2024 11:48:31 +0400 Subject: [PATCH 036/116] test: add trading function and price checks to swapExactPtForSy tests --- test/unit/RMM.t.sol | 37 -------------------------------- test/unit/SwapExactPtForSy.t.sol | 20 +++++++++++++++++ 2 files changed, 20 insertions(+), 37 deletions(-) diff --git a/test/unit/RMM.t.sol b/test/unit/RMM.t.sol index 70b6ec2..4896421 100644 --- a/test/unit/RMM.t.sol +++ b/test/unit/RMM.t.sol @@ -400,41 +400,4 @@ contract RMMTest is Test { emit Swap(address(this), address(this), address(SY), address(PT), deltaSy, minAmountOut, delLiq); subject().swapExactSyForPt(deltaSy, 0, address(this)); } - - function test_swapY() public basic { - PYIndex index = YT.newIndex(); - uint256 deltaPt = 1 ether; - (,, uint256 minAmountOut,,) = subject().prepareSwapPtIn(deltaPt, block.timestamp, index); - deal(address(SY), address(subject()), minAmountOut); - deal(address(PT), address(this), deltaPt); - PT.approve(address(subject()), deltaPt); - - uint256 prevBalanceX = balanceWad(address(SY), address(this)); - uint256 prevBalanceY = balanceWad(address(PT), address(this)); - uint256 prevPrice = subject().approxSpotPrice(index.syToAsset(subject().reserveX())); - uint256 prevReserveY = subject().reserveY(); - uint256 prevLiquidity = subject().totalLiquidity(); - (uint256 amountOut, int256 deltaLiquidity) = subject().swapExactPtForSy(deltaPt, minAmountOut, address(this)); - - assertTrue(amountOut >= minAmountOut, "Amount out is not greater than or equal to min amount out."); - assertTrue(abs(subject().tradingFunction(index)) < 100, "Trading function invalid"); - assertEq(subject().reserveX(), basicParams.amountX - amountOut, "Reserve X did not decrease by amount in."); - assertEq(subject().reserveY(), prevReserveY + deltaPt, "Reserve Y did not increase by delta Y."); - assertEq( - subject().totalLiquidity(), - sum(prevLiquidity, deltaLiquidity), - "Total liquidity did not increase by delta liquidity." - ); - - assertEq( - balanceWad(address(SY), address(this)), prevBalanceX + amountOut, "Balance X did not increase by amount in." - ); - assertEq( - balanceWad(address(PT), address(this)), prevBalanceY - deltaPt, "Balance Y did not decrease by delta Y." - ); - assertTrue( - subject().approxSpotPrice(index.syToAsset(subject().reserveX())) > prevPrice, - "Price did not increase after buying Y." - ); - } } diff --git a/test/unit/SwapExactPtForSy.t.sol b/test/unit/SwapExactPtForSy.t.sol index 547114e..69ad657 100644 --- a/test/unit/SwapExactPtForSy.t.sol +++ b/test/unit/SwapExactPtForSy.t.sol @@ -5,8 +5,12 @@ import {ERC20} from "solmate/tokens/ERC20.sol"; import {InsufficientOutput} from "../../src/lib/RmmErrors.sol"; import {Swap} from "../../src/lib/RmmEvents.sol"; import {SetUp} from "../SetUp.sol"; +import {abs} from "./../../src/lib/RmmLib.sol"; +import {PYIndex, PYIndexLib} from "pendle/core/StandardizedYield/PYIndex.sol"; contract SwapExactPtForSyTest is SetUp { + using PYIndexLib for PYIndex; + function test_swapExactPtForSy_TransfersTokens() public useDefaultPool { address to = address(0xbeef); @@ -40,6 +44,22 @@ contract SwapExactPtForSyTest is SetUp { assertEq(rmm.strike(), preStrike); } + function test_swapExactPtForSy_MaintainsTradingFunction() public useDefaultPool { + uint256 amountIn = 1 ether; + rmm.swapExactPtForSy(amountIn, 0, address(this)); + assertTrue(abs(rmm.tradingFunction(newIndex())) < 100, "Trading function invalid"); + } + + function test_swapExactPtForSy_MaintainsPrice() public useDefaultPool { + uint256 amountIn = 1 ether; + uint256 prevPrice = rmm.approxSpotPrice(newIndex().syToAsset(rmm.reserveX())); + rmm.swapExactPtForSy(amountIn, 0, address(this)); + assertTrue( + rmm.approxSpotPrice(newIndex().syToAsset(rmm.reserveX())) > prevPrice, + "Price did not increase after buying Y." + ); + } + function test_swapExactPtForSy_RevertsIfInsufficientOutput() public useDefaultPool { uint256 deltaPt = 1 ether; (,, uint256 minAmountOut,,) = rmm.prepareSwapPtIn(deltaPt, block.timestamp, newIndex()); From 1fdb2d3e7d80894e4ff640d994c1de6cd0cfe000 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 28 Jun 2024 11:50:31 +0400 Subject: [PATCH 037/116] test: add test_swapExactSyForPt_EmitsEvent --- test/unit/RMM.t.sol | 13 ------------- test/unit/SwapExactSyForPt.t.sol | 9 +++++++++ 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/test/unit/RMM.t.sol b/test/unit/RMM.t.sol index 4896421..5d0e2ae 100644 --- a/test/unit/RMM.t.sol +++ b/test/unit/RMM.t.sol @@ -387,17 +387,4 @@ contract RMMTest is Test { ); subject().swapExactSyForPt(deltaSy, minAmountOut + 10, address(this)); } - - function test_swapSy_event() public basic { - PYIndex index = YT.newIndex(); - uint256 deltaSy = 1 ether; - (,, uint256 minAmountOut, int256 delLiq,) = subject().prepareSwapSyIn(deltaSy, block.timestamp, index); - deal(address(PT), address(subject()), minAmountOut); - deal(address(SY), address(this), deltaSy); - SY.approve(address(subject()), deltaSy); - - vm.expectEmit(); - emit Swap(address(this), address(this), address(SY), address(PT), deltaSy, minAmountOut, delLiq); - subject().swapExactSyForPt(deltaSy, 0, address(this)); - } } diff --git a/test/unit/SwapExactSyForPt.t.sol b/test/unit/SwapExactSyForPt.t.sol index b1b2b94..919f7f8 100644 --- a/test/unit/SwapExactSyForPt.t.sol +++ b/test/unit/SwapExactSyForPt.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.13; import {SetUp} from "../SetUp.sol"; import {ERC20} from "solmate/tokens/ERC20.sol"; +import {Swap} from "../../src/lib/RmmEvents.sol"; contract SwapExactSyForPtTest is SetUp { function test_swapExactSyForPt_TransfersTokens() public useDefaultPool { @@ -41,6 +42,14 @@ contract SwapExactSyForPtTest is SetUp { assertEq(rmm.totalLiquidity(), preTotalLiquidity + uint256(deltaLiquidity)); } + function test_swapExactSyForPt_EmitsEvent() public useDefaultPool { + uint256 deltaSy = 1 ether; + (,, uint256 minAmountOut, int256 deltaLiquidity,) = rmm.prepareSwapSyIn(deltaSy, block.timestamp, newIndex()); + vm.expectEmit(true, true, true, true); + emit Swap(address(this), address(this), address(SY), address(PT), deltaSy, minAmountOut, deltaLiquidity); + rmm.swapExactSyForPt(deltaSy, minAmountOut, address(this)); + } + function test_swapExactSyForPt_RevertsIfInsufficientOutput(address to) public useDefaultPool { deal(address(SY), address(this), 1 ether); vm.expectRevert(); From 0e69fcbd41c0e650108d3ada45f9bb29fba77fd7 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 28 Jun 2024 11:57:15 +0400 Subject: [PATCH 038/116] test: improve test_swapExactSyForPt_RevertsIfInsufficientOutput --- test/unit/RMM.t.sol | 16 ---------------- test/unit/SwapExactSyForPt.t.sol | 10 ++++++---- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/test/unit/RMM.t.sol b/test/unit/RMM.t.sol index 5d0e2ae..a5ae0fc 100644 --- a/test/unit/RMM.t.sol +++ b/test/unit/RMM.t.sol @@ -371,20 +371,4 @@ contract RMMTest is Test { "Price did not decrease after selling X." ); } - - function test_swapSy_reverts_InsufficientOutput() public basic { - PYIndex index = YT.newIndex(); - uint256 deltaSy = 1 ether; - (,, uint256 minAmountOut,,) = subject().prepareSwapSyIn(deltaSy, block.timestamp, index); - deal(address(PT), address(subject()), minAmountOut); - deal(address(SY), address(this), deltaSy); - SY.approve(address(subject()), deltaSy); - - vm.expectRevert( - abi.encodeWithSelector( - InsufficientOutput.selector, upscale(deltaSy, scalar(address(SY))), minAmountOut + 10, minAmountOut - ) - ); - subject().swapExactSyForPt(deltaSy, minAmountOut + 10, address(this)); - } } diff --git a/test/unit/SwapExactSyForPt.t.sol b/test/unit/SwapExactSyForPt.t.sol index 919f7f8..ca7c089 100644 --- a/test/unit/SwapExactSyForPt.t.sol +++ b/test/unit/SwapExactSyForPt.t.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.13; import {SetUp} from "../SetUp.sol"; import {ERC20} from "solmate/tokens/ERC20.sol"; import {Swap} from "../../src/lib/RmmEvents.sol"; +import {InsufficientOutput} from "../../src/lib/RmmErrors.sol"; contract SwapExactSyForPtTest is SetUp { function test_swapExactSyForPt_TransfersTokens() public useDefaultPool { @@ -50,9 +51,10 @@ contract SwapExactSyForPtTest is SetUp { rmm.swapExactSyForPt(deltaSy, minAmountOut, address(this)); } - function test_swapExactSyForPt_RevertsIfInsufficientOutput(address to) public useDefaultPool { - deal(address(SY), address(this), 1 ether); - vm.expectRevert(); - rmm.swapExactSyForPt(1 ether, type(uint256).max, address(to)); + function test_swapExactSyForPt_RevertsIfInsufficientOutput() public useDefaultPool { + uint256 deltaSy = 1 ether; + (,, uint256 minAmountOut,,) = rmm.prepareSwapSyIn(deltaSy, block.timestamp, newIndex()); + vm.expectRevert(abi.encodeWithSelector(InsufficientOutput.selector, deltaSy, minAmountOut + 10, minAmountOut)); + rmm.swapExactSyForPt(deltaSy, minAmountOut + 10, address(this)); } } From 15b7e5bd1173c65bb54b52a722f144cb619cb171 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 28 Jun 2024 12:05:47 +0400 Subject: [PATCH 039/116] test: add syToAsset to SetUp base test file --- test/SetUp.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/SetUp.sol b/test/SetUp.sol index d643e87..338deb3 100644 --- a/test/SetUp.sol +++ b/test/SetUp.sol @@ -32,6 +32,7 @@ struct InitParams { contract SetUp is Test { using PYIndexLib for IPYieldToken; + using PYIndexLib for PYIndex; // All the contracts that are needed for the tests. RMM public rmm; @@ -185,4 +186,8 @@ contract SetUp is Test { function newIndex() public returns (PYIndex) { return YT.newIndex(); } + + function syToAsset(uint256 amount) public returns (uint256) { + return newIndex().syToAsset(amount); + } } From 804442892b9974069d5a538e80eea889be9a36da Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 28 Jun 2024 12:06:23 +0400 Subject: [PATCH 040/116] test: use syToAsset --- test/unit/SwapExactPtForSy.t.sol | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/test/unit/SwapExactPtForSy.t.sol b/test/unit/SwapExactPtForSy.t.sol index 69ad657..3b111d1 100644 --- a/test/unit/SwapExactPtForSy.t.sol +++ b/test/unit/SwapExactPtForSy.t.sol @@ -6,11 +6,8 @@ import {InsufficientOutput} from "../../src/lib/RmmErrors.sol"; import {Swap} from "../../src/lib/RmmEvents.sol"; import {SetUp} from "../SetUp.sol"; import {abs} from "./../../src/lib/RmmLib.sol"; -import {PYIndex, PYIndexLib} from "pendle/core/StandardizedYield/PYIndex.sol"; contract SwapExactPtForSyTest is SetUp { - using PYIndexLib for PYIndex; - function test_swapExactPtForSy_TransfersTokens() public useDefaultPool { address to = address(0xbeef); @@ -52,20 +49,9 @@ contract SwapExactPtForSyTest is SetUp { function test_swapExactPtForSy_MaintainsPrice() public useDefaultPool { uint256 amountIn = 1 ether; - uint256 prevPrice = rmm.approxSpotPrice(newIndex().syToAsset(rmm.reserveX())); + uint256 prevPrice = rmm.approxSpotPrice(syToAsset(rmm.reserveX())); rmm.swapExactPtForSy(amountIn, 0, address(this)); - assertTrue( - rmm.approxSpotPrice(newIndex().syToAsset(rmm.reserveX())) > prevPrice, - "Price did not increase after buying Y." - ); - } - - function test_swapExactPtForSy_RevertsIfInsufficientOutput() public useDefaultPool { - uint256 deltaPt = 1 ether; - (,, uint256 minAmountOut,,) = rmm.prepareSwapPtIn(deltaPt, block.timestamp, newIndex()); - - vm.expectRevert(abi.encodeWithSelector(InsufficientOutput.selector, deltaPt, minAmountOut + 10, minAmountOut)); - rmm.swapExactPtForSy(deltaPt, minAmountOut + 10, address(this)); + assertTrue(rmm.approxSpotPrice(syToAsset(rmm.reserveX())) > prevPrice, "Price did not increase after buying Y."); } function test_swapExactPtForSy_EmitsEvent() public useDefaultPool { @@ -75,4 +61,12 @@ contract SwapExactPtForSyTest is SetUp { emit Swap(address(this), address(this), address(PT), address(SY), deltaPt, minAmountOut, deltaLiquidity); rmm.swapExactPtForSy(deltaPt, minAmountOut, address(this)); } + + function test_swapExactPtForSy_RevertsIfInsufficientOutput() public useDefaultPool { + uint256 deltaPt = 1 ether; + (,, uint256 minAmountOut,,) = rmm.prepareSwapPtIn(deltaPt, block.timestamp, newIndex()); + + vm.expectRevert(abi.encodeWithSelector(InsufficientOutput.selector, deltaPt, minAmountOut + 10, minAmountOut)); + rmm.swapExactPtForSy(deltaPt, minAmountOut + 10, address(this)); + } } From f889eb9e35c81481b79ed721cd3e30694214296d Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 28 Jun 2024 12:06:45 +0400 Subject: [PATCH 041/116] test: add trading function and price checks to SwapExactSyForPtTest --- test/unit/SwapExactSyForPt.t.sol | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/test/unit/SwapExactSyForPt.t.sol b/test/unit/SwapExactSyForPt.t.sol index ca7c089..d70f065 100644 --- a/test/unit/SwapExactSyForPt.t.sol +++ b/test/unit/SwapExactSyForPt.t.sol @@ -1,10 +1,11 @@ /// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import {SetUp} from "../SetUp.sol"; import {ERC20} from "solmate/tokens/ERC20.sol"; +import {SetUp} from "../SetUp.sol"; import {Swap} from "../../src/lib/RmmEvents.sol"; import {InsufficientOutput} from "../../src/lib/RmmErrors.sol"; +import {abs} from "./../../src/lib/RmmLib.sol"; contract SwapExactSyForPtTest is SetUp { function test_swapExactSyForPt_TransfersTokens() public useDefaultPool { @@ -43,6 +44,19 @@ contract SwapExactSyForPtTest is SetUp { assertEq(rmm.totalLiquidity(), preTotalLiquidity + uint256(deltaLiquidity)); } + function test_swapExactSyForPt_MaintainsTradingFunction() public useDefaultPool { + uint256 amountIn = 1 ether; + rmm.swapExactSyForPt(amountIn, 0, address(this)); + assertTrue(abs(rmm.tradingFunction(newIndex())) < 100, "Trading function invalid"); + } + + function test_swapExactSyForPt_MaintainsPrice() public useDefaultPool { + uint256 amountIn = 1 ether; + uint256 prevPrice = rmm.approxSpotPrice(syToAsset(rmm.reserveX())); + rmm.swapExactSyForPt(amountIn, 0, address(this)); + assertTrue(rmm.approxSpotPrice(syToAsset(rmm.reserveX())) > prevPrice, "Price did not increase after buying Y."); + } + function test_swapExactSyForPt_EmitsEvent() public useDefaultPool { uint256 deltaSy = 1 ether; (,, uint256 minAmountOut, int256 deltaLiquidity,) = rmm.prepareSwapSyIn(deltaSy, block.timestamp, newIndex()); From 39ae334dd7c76d3f5f308ade3ccde20cf138a060 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 28 Jun 2024 14:32:34 +0400 Subject: [PATCH 042/116] test: fix test_swapExactSyForPt_MaintainsPrice --- test/unit/SwapExactSyForPt.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/SwapExactSyForPt.t.sol b/test/unit/SwapExactSyForPt.t.sol index d70f065..4788de3 100644 --- a/test/unit/SwapExactSyForPt.t.sol +++ b/test/unit/SwapExactSyForPt.t.sol @@ -54,7 +54,7 @@ contract SwapExactSyForPtTest is SetUp { uint256 amountIn = 1 ether; uint256 prevPrice = rmm.approxSpotPrice(syToAsset(rmm.reserveX())); rmm.swapExactSyForPt(amountIn, 0, address(this)); - assertTrue(rmm.approxSpotPrice(syToAsset(rmm.reserveX())) > prevPrice, "Price did not increase after buying Y."); + assertTrue(rmm.approxSpotPrice(syToAsset(rmm.reserveX())) < prevPrice, "Price did not increase after buying Y."); } function test_swapExactSyForPt_EmitsEvent() public useDefaultPool { From 008c2e7d19ffdac5d4a1010c9246b09ff8e80912 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 28 Jun 2024 14:37:36 +0400 Subject: [PATCH 043/116] test: add setLastTimestamp to MockRMM --- test/MockRMM.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/MockRMM.sol b/test/MockRMM.sol index 16cf74c..df1b99d 100644 --- a/test/MockRMM.sol +++ b/test/MockRMM.sol @@ -19,4 +19,8 @@ contract MockRMM is RMM { function adjust(int256 deltaX, int256 deltaY, int256 deltaLiquidity, uint256 strike_, PYIndex index) public { _adjust(deltaX, deltaY, deltaLiquidity, strike_, index); } + + function setLastTimestamp(uint256 lastTimestamp_) public { + lastTimestamp = lastTimestamp_; + } } From 9ae663693ff524148438aa32cad67016c8c02abd Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 28 Jun 2024 14:41:22 +0400 Subject: [PATCH 044/116] test: use MockRMM instead of RMM for testing --- test/SetUp.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/SetUp.sol b/test/SetUp.sol index 338deb3..7621839 100644 --- a/test/SetUp.sol +++ b/test/SetUp.sol @@ -15,6 +15,7 @@ import {MockWstETH} from "./mocks/MockWstETH.sol"; import {MockStETH} from "./mocks/MockStETH.sol"; import {RMM} from "./../src/RMM.sol"; +import {MockRMM} from "./MockRMM.sol"; struct InitParams { uint256 priceX; @@ -35,7 +36,7 @@ contract SetUp is Test { using PYIndexLib for PYIndex; // All the contracts that are needed for the tests. - RMM public rmm; + MockRMM public rmm; WETH public weth; PendleWstEthSY public SY; IPYieldToken public YT; @@ -54,7 +55,7 @@ contract SetUp is Test { weth = new WETH(); stETH = new MockStETH(); wstETH = new MockWstETH(address(stETH)); - rmm = new RMM(address(weth), "RMM-LP-TOKEN", "RMM-LPT"); + rmm = new MockRMM(address(weth), "RMM-LP-TOKEN", "RMM-LPT"); SY = new PendleWstEthSY("wstEthSY", "wstEthSY", address(weth), address(wstETH)); vm.label(address(SY), "SY"); From ec4670d8f9ab7e4e8cffd6acd72ec4636c79abd5 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 28 Jun 2024 14:41:34 +0400 Subject: [PATCH 045/116] test: add test_approxSpotPrice_IncreasesOverTime --- test/unit/ApproxSpotPrice.t.sol | 13 ++++++++ test/unit/RMM.t.sol | 54 --------------------------------- 2 files changed, 13 insertions(+), 54 deletions(-) create mode 100644 test/unit/ApproxSpotPrice.t.sol diff --git a/test/unit/ApproxSpotPrice.t.sol b/test/unit/ApproxSpotPrice.t.sol new file mode 100644 index 0000000..dd97b5d --- /dev/null +++ b/test/unit/ApproxSpotPrice.t.sol @@ -0,0 +1,13 @@ +/// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {SetUp, RMM} from "../SetUp.sol"; + +contract ApproxSpotPrice is SetUp { + function test_approxSpotPrice_IncreasesOverTime() public useDefaultPool { + uint256 preSpotPrice = rmm.approxSpotPrice(syToAsset(rmm.reserveX())); + rmm.setLastTimestamp(block.timestamp + 10 days); + uint256 postSpotPrice = rmm.approxSpotPrice(syToAsset(rmm.reserveX())); + assertGt(postSpotPrice, preSpotPrice); + } +} diff --git a/test/unit/RMM.t.sol b/test/unit/RMM.t.sol index a5ae0fc..c3cc2b1 100644 --- a/test/unit/RMM.t.sol +++ b/test/unit/RMM.t.sol @@ -295,21 +295,6 @@ contract RMMTest is Test { // assertTrue(abs(terminal) < 10, "Trading function invalid."); } - function test_price_increase_over_time() public basic { - PYIndex index = YT.newIndex(); - uint256 timeDelta = 10 days; - - uint256 initial = subject().approxSpotPrice(index.syToAsset(subject().reserveX())); - vm.warp(timeDelta); - vm.store(address(subject()), bytes32(LAST_TIMESTAMP_SLOT), bytes32(uint256(block.timestamp))); - uint256 terminal = subject().approxSpotPrice(index.syToAsset(subject().reserveX())); - - console2.logUint(initial); - console2.logUint(terminal); - - assertTrue(terminal > initial, "Price did not increase over time."); - } - // avoids stack too deep in tests. struct InitParams { uint256 priceX; @@ -332,43 +317,4 @@ contract RMMTest is Test { }); // init - - function test_swapSy_basic() public basic { - PYIndex index = YT.newIndex(); - uint256 deltaSy = 1 ether; - (,, uint256 minAmountOut,,) = subject().prepareSwapSyIn(deltaSy, block.timestamp, index); - deal(address(PT), address(subject()), minAmountOut); - deal(address(SY), address(this), deltaSy); - SY.approve(address(subject()), deltaSy); - - uint256 prevBalanceX = balanceWad(address(SY), address(this)); - uint256 prevBalanceY = balanceWad(address(PT), address(this)); - uint256 prevReserveY = subject().reserveY(); - uint256 prevPrice = subject().approxSpotPrice(index.syToAsset(subject().reserveX())); - uint256 prevLiquidity = subject().totalLiquidity(); - (uint256 amountOut, int256 deltaLiquidity) = subject().swapExactSyForPt(deltaSy, 0, address(this)); - - assertTrue(amountOut >= minAmountOut, "Amount out is not greater than or equal to min amount out."); - assertTrue(abs(subject().tradingFunction(index)) < 100, "Invalid trading function state."); - assertEq(subject().reserveX(), basicParams.amountX + deltaSy, "Reserve X did not increase by delta X."); - assertEq(subject().reserveY(), prevReserveY - amountOut, "Reserve Y did not decrease by amount out."); - assertEq( - subject().totalLiquidity(), - sum(prevLiquidity, deltaLiquidity), - "Total liquidity did not increase by delta liquidity." - ); - - assertEq( - balanceWad(address(SY), address(this)), prevBalanceX - deltaSy, "Balance Sy did not decrease by delta X." - ); - assertEq( - balanceWad(address(PT), address(this)), - prevBalanceY + amountOut, - "Balance Y did not increase by amount out." - ); - assertTrue( - subject().approxSpotPrice(index.syToAsset(subject().reserveX())) < prevPrice, - "Price did not decrease after selling X." - ); - } } From 08062f729310ccbee6e7161ae888b9e35d3fbf8e Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 28 Jun 2024 14:43:57 +0400 Subject: [PATCH 046/116] test: delete old RMM test file --- test/unit/RMM.t.sol | 320 -------------------------------------------- 1 file changed, 320 deletions(-) delete mode 100644 test/unit/RMM.t.sol diff --git a/test/unit/RMM.t.sol b/test/unit/RMM.t.sol deleted file mode 100644 index c3cc2b1..0000000 --- a/test/unit/RMM.t.sol +++ /dev/null @@ -1,320 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {RMM, toInt, toUint, upscale, downscaleDown, scalar, sum, abs} from "../../src/RMM.sol"; - -import {Test, console2} from "forge-std/Test.sol"; -import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol"; -import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; - -import "pendle/core/Market/MarketMathCore.sol"; -import {IPPrincipalToken} from "pendle/interfaces/IPPrincipalToken.sol"; -import {IStandardizedYield} from "pendle/interfaces/IStandardizedYield.sol"; -import {IPYieldToken} from "pendle/interfaces/IPYieldToken.sol"; - -import {PendleERC20SY} from "pendle/core/StandardizedYield/implementations/PendleERC20SY.sol"; -import {SYBase} from "pendle/core/StandardizedYield/SYBase.sol"; -import {PendleYieldContractFactoryV2} from "pendle/core/YieldContractsV2/PendleYieldContractFactoryV2.sol"; -import {PendleYieldTokenV2} from "pendle/core/YieldContractsV2/PendleYieldTokenV2.sol"; -import {BaseSplitCodeFactory} from "pendle/core/libraries/BaseSplitCodeFactory.sol"; -import {Swap, Allocate, Deallocate} from "../../src/lib/RmmEvents.sol"; -import {InsufficientOutput} from "../../src/lib/RmmErrors.sol"; - -import "../../src/lib/RmmLib.sol"; - -// slot numbers. double check these if changes are made. -uint256 constant offset = 6; // ERC20 inheritance adds 6 storage slots. -uint256 constant PT_SLOT = 0 + offset; -uint256 constant SY_SLOT = 1 + offset; -uint256 constant YT_SLOT = 2 + offset; -uint256 constant RESERVE_X_SLOT = 3 + offset; -uint256 constant RESERVE_Y_SLOT = 4 + offset; -uint256 constant TOTAL_LIQUIDITY_SLOT = 5 + offset; -uint256 constant STRIKE_SLOT = 6 + offset; -uint256 constant SIGMA_SLOT = 7 + offset; -uint256 constant FEE_SLOT = 8 + offset; -uint256 constant MATURITY_SLOT = 9 + offset; -uint256 constant INIT_TIMESTAMP_SLOT = 10 + offset; -uint256 constant LAST_TIMESTAMP_SLOT = 11 + offset; -uint256 constant CURATOR_SLOT = 12 + offset; -uint256 constant LOCK_SLOT = 13 + offset; - -contract RMMTest is Test { - using MarketMathCore for MarketState; - using MarketMathCore for int256; - using MarketMathCore for uint256; - using FixedPointMathLib for uint256; - using PYIndexLib for IPYieldToken; - using PYIndexLib for PYIndex; - - RMM public __subject__; - address public wstETH; - IStandardizedYield public SY; - IPPrincipalToken public PT; - IPYieldToken public YT; - MarketState public pendleMarketState; - int256 pendleRateAnchor; - int256 pendleRateScalar; - uint256 timeToExpiry; - - function setUp() public { - vm.warp(0); - __subject__ = new RMM(address(0), "LPToken", "LPT"); - vm.label(address(__subject__), "RMM"); - - uint32 _expiry = 1_717_214_400; - - wstETH = address(new MockERC20("Wrapped stETH", "wstETH", 18)); - SYBase SY_ = new PendleERC20SY("Standard Yield wstETH", "SYwstETH", wstETH); - SY = IStandardizedYield(SY_); - (address ytCodeContractA, uint256 ytCodeSizeA, address ytCodeContractB, uint256 ytCodeSizeB) = - BaseSplitCodeFactory.setCreationCode(type(PendleYieldTokenV2).creationCode); - PendleYieldContractFactoryV2 YCF = new PendleYieldContractFactoryV2({ - _ytCreationCodeContractA: ytCodeContractA, - _ytCreationCodeSizeA: ytCodeSizeA, - _ytCreationCodeContractB: ytCodeContractB, - _ytCreationCodeSizeB: ytCodeSizeB - }); - - YCF.initialize(1, 2e17, 0, address(this)); - YCF.createYieldContract(address(SY), _expiry, true); - YT = IPYieldToken(YCF.getYT(address(SY), _expiry)); - PT = IPPrincipalToken(YCF.getPT(address(SY), _expiry)); - - deal(wstETH, address(this), 1_000_000e18); - - mintSY(100_000 ether); - mintPtYt(50_000 ether); - - IERC20(wstETH).approve(address(subject()), type(uint256).max); - IERC20(SY).approve(address(subject()), type(uint256).max); - IERC20(PT).approve(address(subject()), type(uint256).max); - IERC20(YT).approve(address(subject()), type(uint256).max); - } - - function subject() public view returns (RMM) { - return __subject__; - } - - function balanceNative(address token, address account) internal view returns (uint256) { - if (token == address(0)) { - return address(this).balance; - } - - return MockERC20(token).balanceOf(account); - } - - function balanceWad(address token, address account) internal view returns (uint256) { - return upscale(balanceNative(token, account), scalar(token)); - } - - function mintSY(uint256 amount) public { - IERC20(wstETH).approve(address(SY), type(uint256).max); - SY.deposit(address(this), address(wstETH), amount, 1); - } - - function mintPtYt(uint256 amount) public { - SY.transfer(address(YT), amount); - YT.mintPY(address(this), address(this)); - } - - modifier basic() { - uint256 price = 1 ether; //uint256(getPtExchangeRate()); - console2.log("initial price", price); - console2.log("rate anchor", pendleRateAnchor); - console2.log("totalSY", pendleMarketState.totalSy); - console2.log("totalPT", pendleMarketState.totalPt); - uint256 amountX = YT.newIndex().assetToSy(price); - console2.log("amountX", amountX); - subject().init({ - PT_: address(PT), - priceX: price, - amountX: amountX, // using the equivalent amount of tokens - strike_: 1.05 ether, //uint256(pendleRateAnchor), - sigma_: 0.015 ether, - fee_: 0.00016 ether, - curator_: address(0x55) - }); - - _; - } - - function test_basic_trading_function_result() public basic { - PYIndex index = YT.newIndex(); - int256 result = subject().tradingFunction(index); - assertTrue(abs(result) < 10, "Trading function result is not within init epsilon."); - } - - // todo: whats the error? - function test_basic_price() public basic { - uint256 price = subject().approxSpotPrice(YT.newIndex().syToAsset(subject().reserveX())); - assertApproxEqAbs(price, 1 ether, 10_000, "Price is not approximately 1 ether."); - } - - // no fee btw - // function test_basic_adjust_invalid_allocate() public basic { - // uint256 deltaX = 1 ether; - // uint256 approximatedDeltaY = 0.685040862443611931 ether; - - // deal(address(subject().SY()), address(this), deltaX); - // deal(address(subject().PT()), address(this), approximatedDeltaY); - // SY.approve(address(subject()), deltaX); - // PT.approve(address(subject()), approximatedDeltaY); - - // vm.expectRevert(); - // subject().adjust(toInt(deltaX), -toInt(approximatedDeltaY - 3), toInt(1 ether)); - // } - - // function test_basic_adjust_single_allocate_x_increases() public basic { - // PYIndex index = YT.newIndex(); - // uint256 deltaX = 1; - - // deal(address(subject().SY()), address(this), deltaX); - // SY.approve(address(subject()), deltaX); - - // subject().adjust(toInt(deltaX), toInt(0), toInt(0)); - // int256 post = subject().tradingFunction(index); - - // assertTrue(abs(post) < 10, "Trading function invalid."); - // } - - // function test_basic_adjust_single_allocate_y_increases() public basic { - // PYIndex index = YT.newIndex(); - // uint256 deltaY = 4; - - // deal(address(subject().PT()), address(this), deltaY); - // PT.approve(address(subject()), deltaY); - - // subject().adjust(toInt(0), toInt(deltaY), toInt(0)); - // int256 post = subject().tradingFunction(index); - - // assertTrue(abs(post) < 10, "Trading function invalid."); - // } - - // todo: improve test - function test_basic_solve_y() public basic { - uint256 deltaX = 1 ether; - uint256 computedYGivenXAdjustment = computeY( - subject().reserveX() + deltaX, - subject().totalLiquidity(), - subject().strike(), - subject().sigma(), - subject().lastTau() - ); - console2.log("computedYGivenXAdjustment", computedYGivenXAdjustment); - - uint256 nextReserveY = solveY( - subject().reserveX() + deltaX, - subject().totalLiquidity(), - subject().strike(), - subject().sigma(), - subject().lastTau() - ); - console2.log("nextReserveY", nextReserveY); - - uint256 actualDeltaY = subject().reserveY() - nextReserveY; - console2.log("actualDeltaY", actualDeltaY); - - uint256 approximatedDeltaY = 0.685040862443611931 ether; - uint256 diff = - approximatedDeltaY > actualDeltaY ? approximatedDeltaY - actualDeltaY : actualDeltaY - approximatedDeltaY; - console2.log("diff", diff, approximatedDeltaY > actualDeltaY); - } - - // todo: improve test - function test_basic_solve_x() public basic { - PYIndex index = YT.newIndex(); - uint256 deltaSy = 1 ether; - (,, uint256 approximatedDeltaY,,) = subject().prepareSwapSyIn(deltaSy, block.timestamp, index); - - uint256 proportionalLGivenX = deltaSy * subject().totalLiquidity() / subject().reserveX(); - uint256 proportionalLGivenY = approximatedDeltaY * subject().totalLiquidity() / subject().reserveY(); - console2.log("proportionalLGivenX", proportionalLGivenX); - console2.log("proportionalLGivenY", proportionalLGivenY); - - uint256 computedXGivenYAdjustment = computeX( - subject().reserveY() - approximatedDeltaY, - subject().totalLiquidity(), - subject().strike(), - subject().sigma(), - subject().lastTau() - ); - console2.log("computedXGivenYAdjustment", computedXGivenYAdjustment); - - uint256 nextReserveX = solveX( - subject().reserveY() - approximatedDeltaY, - subject().totalLiquidity(), - subject().strike(), - subject().sigma(), - subject().lastTau() - ); - uint256 actualDeltaX = nextReserveX - subject().reserveX(); - console2.log("nextReserveX", nextReserveX); - console2.log("actualDeltaX", actualDeltaX); - } - - function test_swap_sy() public basic { - PYIndex index = YT.newIndex(); - uint256 deltaSy = 1 ether; - uint256 minAmountOut = 0.685040862443611931 ether; - deal(address(subject().PT()), address(subject()), minAmountOut * 150 / 100); - deal(address(subject().SY()), address(this), deltaSy); - SY.approve(address(subject()), deltaSy); - - int256 initial = subject().tradingFunction(index); - console2.log("loss", uint256(685_040_862_443_611_928) - uint256(685_001_492_551_417_433)); - console2.log("loss %", uint256(39_369_892_194_495) * 1 ether / uint256(685_001_492_551_417_433)); - (uint256 amountOut, int256 deltaLiquidity) = subject().swapExactSyForPt(deltaSy, 0, address(this)); - int256 terminal = subject().tradingFunction(index); - console2.logInt(initial); - console2.logInt(terminal); - console2.logUint(amountOut); - console2.logInt(deltaLiquidity); - } - - function test_swapSy_over_time_basic() public basic { - PYIndex index = YT.newIndex(); - uint256 deltaSy = 1 ether; - (,, uint256 minAmountOut,,) = subject().prepareSwapSyIn(deltaSy, block.timestamp, index); - deal(address(subject().PT()), address(subject()), 1 ether); - deal(address(subject().SY()), address(this), deltaSy); - SY.approve(address(subject()), deltaSy); - - int256 initial = subject().tradingFunction(index); - vm.warp(365 days / 2); - - uint256 expectedL = 2_763_676_832_322_849_396; - console2.log("expectedL", expectedL); - (uint256 amountOut, int256 deltaLiquidity) = subject().swapExactSyForPt(deltaSy, 0, address(this)); - int256 terminal = subject().tradingFunction(index); - - console2.log("initialInvariant", initial); - console2.log("terminalInvariant", terminal); - console2.log("amountOut", amountOut); - console2.log("deltaLiquidity", deltaLiquidity); - // assertTrue(abs(terminal) < 10, "Trading function invalid."); - } - - // avoids stack too deep in tests. - struct InitParams { - uint256 priceX; - uint256 amountX; - uint256 strike; - uint256 sigma; - uint256 fee; - uint256 maturity; - address curator; - } - - InitParams basicParams = InitParams({ - priceX: 1 ether, - amountX: 1 ether, - strike: 1.05 ether, - sigma: 0.015 ether, - fee: 0, - maturity: 1_717_214_400, - curator: address(0x55) - }); - - // init -} From e87219091374543ffdcdaebc1946f87a6ee5e528 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 28 Jun 2024 14:44:19 +0400 Subject: [PATCH 047/116] chore: rename ApproxSpotPriceTest contract --- test/unit/ApproxSpotPrice.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/ApproxSpotPrice.t.sol b/test/unit/ApproxSpotPrice.t.sol index dd97b5d..a09722c 100644 --- a/test/unit/ApproxSpotPrice.t.sol +++ b/test/unit/ApproxSpotPrice.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.13; import {SetUp, RMM} from "../SetUp.sol"; -contract ApproxSpotPrice is SetUp { +contract ApproxSpotPriceTest is SetUp { function test_approxSpotPrice_IncreasesOverTime() public useDefaultPool { uint256 preSpotPrice = rmm.approxSpotPrice(syToAsset(rmm.reserveX())); rmm.setLastTimestamp(block.timestamp + 10 days); From 9e3798c8143df93d132689dddd2de1bf7837cced Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 28 Jun 2024 14:55:41 +0400 Subject: [PATCH 048/116] chore: clean up unused or duplicate tests --- test/unit/RmmSy.t.sol | 232 ------------------------------------------ 1 file changed, 232 deletions(-) diff --git a/test/unit/RmmSy.t.sol b/test/unit/RmmSy.t.sol index 91407a5..cd623b2 100644 --- a/test/unit/RmmSy.t.sol +++ b/test/unit/RmmSy.t.sol @@ -243,14 +243,6 @@ contract ForkRMMTest is Test { // assertEq(sharesOut, expectedShares, "Minting with wETH did not return the expected amount of shares."); } - function test_mintSY_with_ETH() public basic_sy { - PYIndex index = YT.newIndex(); - uint256 amountIn = 1 ether; - - uint256 sharesOut = subject().mintSY{value: amountIn}(address(this), address(0), amountIn, 0); - // assertEq(sharesOut, expectedShares, "Minting with ETH did not return the expected amount of shares."); - } - function test_pt_flash_swap_changes_balances() public basic_sy { SY.transfer(address(0x55), SY.balanceOf(address(this))); PT.transfer(address(0x55), PT.balanceOf(address(this))); @@ -264,228 +256,4 @@ contract ForkRMMTest is Test { (uint256 amtOut,) = subject().swapExactSyForYt(1 ether, ytOut, ytOut.mulDivDown(95, 100), 500 ether, 10_000, address(this)); } - - function test_pt_flash_swap_adjusts_balances_correctly() public basic_sy { - PYIndex index = YT.newIndex(); - - SY.transfer(address(0x55), SY.balanceOf(address(this))); - PT.transfer(address(0x55), PT.balanceOf(address(this))); - YT.transfer(address(0x55), YT.balanceOf(address(this))); - - // assert balance of address(this) is 0 for SY, PT, and YT - assertEq(SY.balanceOf(address(this)), 0, "SY balance of address(this) is not 0."); - assertEq(PT.balanceOf(address(this)), 0, "PT balance of address(this) is not 0."); - assertEq(YT.balanceOf(address(this)), 0, "YT balance of address(this) is not 0."); - - // mint 1 SY for the flash swap - mintSY(1 ether); - - uint256 ytOut = subject().computeSYToYT(index, 1 ether, 500 ether, block.timestamp, 0, 10_000); - (uint256 amtOut,) = subject().swapExactSyForYt(1 ether, ytOut, 0, 500 ether, eps, address(this)); - - // assert balance of address(this) is 0 for SY, PT, and YT - assertEq(PT.balanceOf(address(this)), 0, "PT balance at the end of the test is not 0."); - assertApproxEqAbs(SY.balanceOf(address(this)), 0, 10_000, "SY balance at the end of the test is not approx 0."); - assertEq( - YT.balanceOf(address(this)), ytOut, "YT balance at the end of the test is not equal to the returned ytOut." - ); - assertEq( - YT.balanceOf(address(this)), - amtOut, - "YT balance at the end of the test is not equal to the returned amtOut." - ); - } - - function test_approx_sy_pendle() public basic_sy { - (MarketState memory ms,) = getPendleMarketData(); - console2.log("market sy", ms.totalSy); - console2.log("market pt", ms.totalPt); - vm.warp(block.timestamp + 30 days); - int256 rateAnchor = getPendleRateAnchor(); - ApproxParams memory approx = - ApproxParams({guessMin: 1 ether, guessMax: 500 ether, guessOffchain: 0, maxIteration: 256, eps: 10_000}); - (uint256 netYtOutMarket,) = ms.approxSwapExactSyForYt(YT.newIndex(), 1 ether, block.timestamp, approx); - console2.log("netYtOutMarket", netYtOutMarket); - console2.log("rateAnchor", rateAnchor); - } - - function test_pt_flash_swap_calculation() public basic_sy { - PYIndex index = YT.newIndex(); - uint256 rPT = subject().reserveX(); - uint256 rSY = subject().reserveY(); - vm.warp(block.timestamp + 30 days); - uint256 k = getRmmStrikePrice(); - uint256 ytOut = subject().computeSYToYT(index, 1 ether, 500 ether, block.timestamp, 0, 10_000); - console2.log("k", k); - console2.log("ytOut", ytOut); - console2.log("rPT", rPT); - console2.log("rSY", rSY); - } - - function test_pendle_rateAnchor_over_time() public basic_sy { - int256 rate1 = getPendleRateAnchor(); - vm.warp(block.timestamp + 10 days); - int256 rate2 = getPendleRateAnchor(); - console2.log("rate1", rate1); - console2.log("rate2", rate2); - console2.log("rate1 - rate2", rate1 - rate2); - } - - function test_rmm_k_over_time() public basic_sy { - uint256 k1 = getRmmStrikePrice(); - vm.warp(block.timestamp + 10 days); - uint256 k2 = getRmmStrikePrice(); - console2.log("k1", k1); - console2.log("k2", k2); - console2.log("k1 - k2", k1 - k2); - } - - function test_diff_k_rateAnchor_over_time() public basic_sy { - uint256 k1 = getRmmStrikePrice(); - int256 rate1 = getPendleRateAnchor(); - vm.warp(block.timestamp + 10 days); - uint256 k2 = getRmmStrikePrice(); - int256 rate2 = getPendleRateAnchor(); - console2.log("k1", k1); - console2.log("rate1", rate1); - console2.log("k2", k2); - console2.log("rate2", rate2); - console2.log("k1 - k2", k1 - k2); - console2.log("rate1 - rate2", rate1 - rate2); - } - - function getPendleRateAnchor() public returns (int256 rateAnchor) { - (, MarketPreCompute memory mp) = getPendleMarketData(); - rateAnchor = mp.rateAnchor; - } - - function getRmmStrikePrice() public returns (uint256 k) { - PYIndex index = YT.newIndex(); - PoolPreCompute memory comp = subject().preparePoolPreCompute(index, block.timestamp); - k = comp.strike_; - } - - function test_exact_yt_for_sy() public basic_sy { - uint256 ytIn = 1 ether; - uint256 maxSyIn = 1000 ether; - subject().swapExactYtForSy(ytIn, maxSyIn, address(this)); - } - - function test_Swapping1YtForSyUpdatesBalancesCorrectly() public basic_sy { - PT.transfer(address(0x55), PT.balanceOf(address(this))); - YT.transfer(address(0x55), YT.balanceOf(address(this))); - - assertEq(PT.balanceOf(address(this)), 0, "PT balance of address(this) is not 0."); - assertEq(YT.balanceOf(address(this)), 0, "YT balance of address(this) is not 0."); - - mintPtYt(1 ether); - - SY.transfer(address(0x55), SY.balanceOf(address(this))); - PT.transfer(address(0x55), PT.balanceOf(address(this))); - assertEq(PT.balanceOf(address(this)), 0, "PT balance of address(this) is not 0."); - assertEq(SY.balanceOf(address(this)), 0, "SY balance of address(this) is not 0."); - - uint256 ytIn = YT.balanceOf(address(this)); - uint256 maxSyIn = 10 ether; - (uint256 amountOut,,) = subject().swapExactYtForSy(ytIn, maxSyIn, address(this)); - assertEq(YT.balanceOf(address(this)), 0, "YT balance of address(this) is not 0."); - assertEq(SY.balanceOf(address(this)), amountOut, "SY balance of address(this) is not equal to amountOut."); - } - - function test_compute_eth_to_yt() public basic_sy { - SY.transfer(address(0x55), SY.balanceOf(address(this))); - PT.transfer(address(0x55), PT.balanceOf(address(this))); - YT.transfer(address(0x55), YT.balanceOf(address(this))); - - // assert balance of address(this) is 0 for SY, PT, and YT - assertEq(SY.balanceOf(address(this)), 0, "SY balance of address(this) is not 0."); - assertEq(PT.balanceOf(address(this)), 0, "PT balance of address(this) is not 0."); - assertEq(YT.balanceOf(address(this)), 0, "YT balance of address(this) is not 0."); - - uint256 amountIn = 1 ether; - PYIndex index = YT.newIndex(); - (uint256 syMinted, uint256 ytOut) = - subject().computeTokenToYT(index, address(0), amountIn, 500 ether, block.timestamp, 0, 1_000); - subject().swapExactTokenForYt{value: amountIn}( - address(0), 0, ytOut, syMinted, ytOut.mulDivDown(99, 100), 500 ether, eps, address(this) - ); - assertApproxEqAbs( - YT.balanceOf(address(this)), ytOut, 1_000, "YT balance of address(this) is not equal to ytOut." - ); - } - - function test_compute_token_to_yt() public basic_sy { - SY.transfer(address(0x55), SY.balanceOf(address(this))); - PT.transfer(address(0x55), PT.balanceOf(address(this))); - YT.transfer(address(0x55), YT.balanceOf(address(this))); - - // assert balance of address(this) is 0 for SY, PT, and YT - assertEq(SY.balanceOf(address(this)), 0, "SY balance of address(this) is not 0."); - assertEq(PT.balanceOf(address(this)), 0, "PT balance of address(this) is not 0."); - assertEq(YT.balanceOf(address(this)), 0, "YT balance of address(this) is not 0."); - - uint256 amountIn = 1 ether; - PYIndex index = YT.newIndex(); - (uint256 syMinted, uint256 ytOut) = - subject().computeTokenToYT(index, address(subject().WETH()), amountIn, 500 ether, block.timestamp, 0, 1_000); - deal(subject().WETH(), address(this), amountIn); - IERC20(subject().WETH()).approve(address(subject()), amountIn); - subject().swapExactTokenForYt( - address(subject().WETH()), - amountIn, - ytOut, - syMinted, - ytOut.mulDivDown(99, 100), - 500 ether, - eps, - address(this) - ); - assertApproxEqAbs( - YT.balanceOf(address(this)), ytOut, 1_000, "YT balance of address(this) is not equal to ytOut." - ); - } - - // TODO: add functionality for handling these on the new swaps - // function test_swapX_usingIbToken() public basic_sy { - // uint256 wstethBalanceInitial = IERC20(wstETH).balanceOf(address(this)); - // uint256 deltaX = 1 ether; - // uint256 minSYMinted = SY.previewDeposit(address(wstETH), deltaX); - // subject().swapX(address(wstETH), minSYMinted, deltaX, 0, address(this), ""); - // uint256 wstethBalanceAfter = IERC20(wstETH).balanceOf(address(this)); - // assertTrue( - // wstethBalanceAfter < wstethBalanceInitial, "wstETH balance after swap is not greater than initial balance." - // ); - // assertTrue( - // wstethBalanceInitial - 1e18 == wstethBalanceAfter, - // "wstETH balance after swap is not 1e18 less than initial balance." - // ); - // } - - // function test_swapX_usingNativeToken() public basic_sy { - // uint256 balanceEthInitial = address(this).balance; - // uint256 deltaX = 1 ether; - // uint256 minSYMinted = SY.previewDeposit(address(0), deltaX); - // subject().swapX{value: deltaX}(address(0), minSYMinted, deltaX, 0, address(this), ""); - // uint256 balanceEthAfter = address(this).balance; - // assertTrue(balanceEthAfter < balanceEthInitial, "wstETH balance after swap is not less than initial balance."); - // assertTrue( - // balanceEthInitial - 1e18 == balanceEthAfter, - // "wstETH balance after swap is not 1e18 less than initial balance." - // ); - // } - - // function test_swapX_usingWETH() public basic_sy { - // deal(subject().WETH(), address(this), 1 ether); - // IERC20(subject().WETH()).approve(address(subject()), type(uint256).max); - // uint256 balanceWethInitial = IERC20(subject().WETH()).balanceOf(address(this)); - // uint256 deltaX = 1 ether; - // uint256 minSYMinted = SY.previewDeposit(address(subject().WETH()), deltaX); - // subject().swapX(address(subject().WETH()), minSYMinted, deltaX, 0, address(this), ""); - // uint256 balanceWethAfter = IERC20(subject().WETH()).balanceOf(address(this)); - // assertTrue(balanceWethAfter < balanceWethInitial, "wstETH balance after swap is not less than initial balance."); - // assertTrue( - // balanceWethInitial - 1e18 == balanceWethAfter, - // "wstETH balance after swap is not 1e18 less than initial balance." - // ); - // } } From 2dec962d8502b8353251dc91a5e29a24d76b259e Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 28 Jun 2024 14:55:47 +0400 Subject: [PATCH 049/116] test: add test_mintSY_MintsSYUsingETH --- test/unit/MintSY.t.sol | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 test/unit/MintSY.t.sol diff --git a/test/unit/MintSY.t.sol b/test/unit/MintSY.t.sol new file mode 100644 index 0000000..a42924a --- /dev/null +++ b/test/unit/MintSY.t.sol @@ -0,0 +1,11 @@ +/// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {SetUp} from "../SetUp.sol"; + +contract MintSYTest is SetUp { + function test_mintSY_MintsSYUsingETH() public useDefaultPool { + rmm.mintSY{value: 1 ether}(address(0xbeef), address(0), 1 ether, 0); + assertEq(SY.balanceOf(address(0xbeef)), 1 ether); + } +} From 47a2b57dced75792d49da1b441d78cc9e93eb9a8 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 28 Jun 2024 14:56:13 +0400 Subject: [PATCH 050/116] fix: wrong receiver param using ETH in _mintSYFromNativeAndToken --- src/RMM.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/RMM.sol b/src/RMM.sol index 47f6753..4b19faa 100644 --- a/src/RMM.sol +++ b/src/RMM.sol @@ -696,7 +696,7 @@ contract RMM is ERC20 { if (!SY.isValidTokenIn(tokenIn)) revert InvalidTokenIn(tokenIn); if (tokenIn == address(0)) { - amountSyOut = SY.deposit{value: msg.value}(address(this), address(0), msg.value, minSyMinted); + amountSyOut = SY.deposit{value: msg.value}(receiver, address(0), msg.value, minSyMinted); } else { ERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountTokenIn); amountSyOut = SY.deposit(receiver, tokenIn, amountTokenIn, minSyMinted); From 1bb3819e422eee03818d41e8422dba880b6aa32c Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 28 Jun 2024 14:57:08 +0400 Subject: [PATCH 051/116] test: add test_mintSY_MintsSYUsingWETH --- test/unit/MintSY.t.sol | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/unit/MintSY.t.sol b/test/unit/MintSY.t.sol index a42924a..05e96bc 100644 --- a/test/unit/MintSY.t.sol +++ b/test/unit/MintSY.t.sol @@ -8,4 +8,10 @@ contract MintSYTest is SetUp { rmm.mintSY{value: 1 ether}(address(0xbeef), address(0), 1 ether, 0); assertEq(SY.balanceOf(address(0xbeef)), 1 ether); } + + function test_mintSY_MintsSYUsingWETH() public useDefaultPool { + weth.deposit{value: 1 ether}(); + rmm.mintSY(address(0xbeef), address(weth), 1 ether, 0); + assertEq(SY.balanceOf(address(0xbeef)), 1 ether); + } } From 7d4405f29ad3f52bd2edacd9553cf39f8079f8b7 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 28 Jun 2024 15:01:39 +0400 Subject: [PATCH 052/116] test: add test_approxSpotPrice_OneAtMaturity --- test/unit/ApproxSpotPrice.t.sol | 7 +++++++ test/unit/RmmSy.t.sol | 31 ------------------------------- 2 files changed, 7 insertions(+), 31 deletions(-) diff --git a/test/unit/ApproxSpotPrice.t.sol b/test/unit/ApproxSpotPrice.t.sol index a09722c..6c44973 100644 --- a/test/unit/ApproxSpotPrice.t.sol +++ b/test/unit/ApproxSpotPrice.t.sol @@ -10,4 +10,11 @@ contract ApproxSpotPriceTest is SetUp { uint256 postSpotPrice = rmm.approxSpotPrice(syToAsset(rmm.reserveX())); assertGt(postSpotPrice, preSpotPrice); } + + function test_approxSpotPrice_OneAtMaturity() public useDefaultPool { + vm.warp(rmm.maturity()); + rmm.swapExactSyForPt(1 ether, 0, address(this)); + uint256 maturityPrice = rmm.approxSpotPrice(syToAsset(rmm.reserveX())); + assertEq(maturityPrice, 1 ether); + } } diff --git a/test/unit/RmmSy.t.sol b/test/unit/RmmSy.t.sol index cd623b2..ffb5729 100644 --- a/test/unit/RmmSy.t.sol +++ b/test/unit/RmmSy.t.sol @@ -204,20 +204,6 @@ contract ForkRMMTest is Test { assertEq(subject().strike(), 1 ether, "Strike is not approximately 1 ether."); } - function test_spot_price_at_maturity() public basic_sy { - PYIndex index = YT.newIndex(); - uint256 deltaSy = 1 ether; - vm.warp(subject().maturity()); - subject().prepareSwapSyIn(deltaSy, block.timestamp, index); - subject().swapExactSyForPt(deltaSy, 0, address(this)); - assertApproxEqAbs( - subject().approxSpotPrice(index.syToAsset(subject().reserveX())), - 1 ether, - 1e18, - "Spot price is not approximately 1 ether." - ); - } - function test_mintSY_with_wstETH() public basic_sy { uint256 amountIn = 1 ether; uint256 expectedShares = amountIn; // 1:1 exchange rate for wstETH to shares @@ -226,23 +212,6 @@ contract ForkRMMTest is Test { // assertEq(sharesOut, expectedShares, "Minting with wstETH did not return the expected amount of shares."); } - // function test_mintSY_with_stETH() public basic_sy { - // uint256 amountIn = 1 ether; - // uint256 expectedShares = IWstETH(SY.wstETH()).wrap(amountIn); // Wrap stETH to wstETH - - // uint256 sharesOut = subject().mintSY(SY.stETH(), amountIn); - // assertEq(sharesOut, expectedShares, "Minting with stETH did not return the expected amount of shares."); - // } - - function test_mintSY_with_wETH() public basic_sy { - IERC20(subject().WETH()).approve(address(subject()), type(uint256).max); - deal(subject().WETH(), address(this), 1_000 ether); - uint256 amountIn = 1 ether; - - uint256 sharesOut = subject().mintSY(address(this), subject().WETH(), amountIn, 0); - // assertEq(sharesOut, expectedShares, "Minting with wETH did not return the expected amount of shares."); - } - function test_pt_flash_swap_changes_balances() public basic_sy { SY.transfer(address(0x55), SY.balanceOf(address(this))); PT.transfer(address(0x55), PT.balanceOf(address(this))); From d20849cfcf038fec05a2d3b91ce54ec7d0920db2 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 28 Jun 2024 15:03:26 +0400 Subject: [PATCH 053/116] test: add test_strike_OneAtMaturity_IncreasesOverTime --- test/unit/RmmSy.t.sol | 9 --------- test/unit/Strike.t.sol | 12 ++++++++++++ 2 files changed, 12 insertions(+), 9 deletions(-) create mode 100644 test/unit/Strike.t.sol diff --git a/test/unit/RmmSy.t.sol b/test/unit/RmmSy.t.sol index ffb5729..f829a9f 100644 --- a/test/unit/RmmSy.t.sol +++ b/test/unit/RmmSy.t.sol @@ -195,15 +195,6 @@ contract ForkRMMTest is Test { console2.log("priceAfter", priceAfter); } - function test_strike_converges_to_one_at_maturity() public basic_sy { - PYIndex index = YT.newIndex(); - uint256 deltaSy = 1 ether; - vm.warp(subject().maturity()); - subject().prepareSwapSyIn(deltaSy, block.timestamp, index); - subject().swapExactSyForPt(deltaSy, 0, address(this)); - assertEq(subject().strike(), 1 ether, "Strike is not approximately 1 ether."); - } - function test_mintSY_with_wstETH() public basic_sy { uint256 amountIn = 1 ether; uint256 expectedShares = amountIn; // 1:1 exchange rate for wstETH to shares diff --git a/test/unit/Strike.t.sol b/test/unit/Strike.t.sol new file mode 100644 index 0000000..afff704 --- /dev/null +++ b/test/unit/Strike.t.sol @@ -0,0 +1,12 @@ +/// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {SetUp} from "../SetUp.sol"; + +contract StrikeTest is SetUp { + function test_strike_OneAtMaturity_IncreasesOverTime() public useDefaultPool { + vm.warp(rmm.maturity()); + rmm.swapExactSyForPt(1 ether, 0, address(this)); + assertEq(rmm.strike(), 1 ether); + } +} From 1dffe1951754992f3883d67fd2f6c30c852078e5 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 28 Jun 2024 15:03:35 +0400 Subject: [PATCH 054/116] chore: remove unused import --- test/unit/ApproxSpotPrice.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/ApproxSpotPrice.t.sol b/test/unit/ApproxSpotPrice.t.sol index 6c44973..dacec5a 100644 --- a/test/unit/ApproxSpotPrice.t.sol +++ b/test/unit/ApproxSpotPrice.t.sol @@ -1,7 +1,7 @@ /// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import {SetUp, RMM} from "../SetUp.sol"; +import {SetUp} from "../SetUp.sol"; contract ApproxSpotPriceTest is SetUp { function test_approxSpotPrice_IncreasesOverTime() public useDefaultPool { From 67d0c8b6713705612870603c68896208169e5196 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 28 Jun 2024 15:05:09 +0400 Subject: [PATCH 055/116] test: remove old test file --- test/unit/RmmSy.t.sol | 219 ------------------------------------------ 1 file changed, 219 deletions(-) delete mode 100644 test/unit/RmmSy.t.sol diff --git a/test/unit/RmmSy.t.sol b/test/unit/RmmSy.t.sol deleted file mode 100644 index f829a9f..0000000 --- a/test/unit/RmmSy.t.sol +++ /dev/null @@ -1,219 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Test, console2} from "forge-std/Test.sol"; -import {RMM, toInt, toUint, upscale, downscaleDown, scalar, sum, abs, PoolPreCompute} from "../../src/RMM.sol"; -import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; -import {ReturnsTooLittleToken} from "solmate/test/utils/weird-tokens/ReturnsTooLittleToken.sol"; -import {ReturnsTooMuchToken} from "solmate/test/utils/weird-tokens/ReturnsTooMuchToken.sol"; -import {MissingReturnToken} from "solmate/test/utils/weird-tokens/MissingReturnToken.sol"; -import {ReturnsFalseToken} from "solmate/test/utils/weird-tokens/ReturnsFalseToken.sol"; -import {IPMarket} from "pendle/interfaces/IPMarket.sol"; -import "pendle/core/Market/MarketMathCore.sol"; -import "pendle/interfaces/IPAllActionV3.sol"; -import {IPPrincipalToken} from "pendle/interfaces/IPPrincipalToken.sol"; -import {IStandardizedYield} from "pendle/interfaces/IStandardizedYield.sol"; -import {IPYieldToken} from "pendle/interfaces/IPYieldToken.sol"; -import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol"; - -// slot numbers. double check these if changes are made. -uint256 constant offset = 6; // ERC20 inheritance adds 6 storage slots. -uint256 constant PT_SLOT = 0 + offset; -uint256 constant SY_SLOT = 1 + offset; -uint256 constant YT_SLOT = 2 + offset; -uint256 constant RESERVE_X_SLOT = 3 + offset; -uint256 constant RESERVE_Y_SLOT = 4 + offset; -uint256 constant TOTAL_LIQUIDITY_SLOT = 5 + offset; -uint256 constant STRIKE_SLOT = 6 + offset; -uint256 constant SIGMA_SLOT = 7 + offset; -uint256 constant FEE_SLOT = 8 + offset; -uint256 constant MATURITY_SLOT = 9 + offset; -uint256 constant INIT_TIMESTAMP_SLOT = 10 + offset; -uint256 constant LAST_TIMESTAMP_SLOT = 11 + offset; -uint256 constant CURATOR_SLOT = 12 + offset; -uint256 constant LOCK_SLOT = 13 + offset; - -uint256 constant eps = 0.005 ether; - -uint256 constant impliedRateTime = 365 * 86400; - -IPAllActionV3 constant router = IPAllActionV3(0x00000000005BBB0EF59571E58418F9a4357b68A0); -IPMarket constant market = IPMarket(0x9eC4c502D989F04FfA9312C9D6E3F872EC91A0F9); -address constant wstETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; //real wsteth -address constant WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; - -contract ForkRMMTest is Test { - using MarketMathCore for MarketState; - using MarketMathCore for int256; - using MarketMathCore for uint256; - using FixedPointMathLib for uint256; - using FixedPointMathLib for int256; - using PYIndexLib for IPYieldToken; - using PYIndexLib for PYIndex; - using MarketApproxPtInLib for MarketState; - - RMM public __subject__; - MockERC20 public tokenX; - MockERC20 public tokenY; - - IStandardizedYield public SY; - IPPrincipalToken public PT; - IPYieldToken public YT; - - uint256 timeToExpiry; - - function setUp() public { - vm.createSelectFork({urlOrAlias: "mainnet", blockNumber: 17_162_783}); - - __subject__ = new RMM(WETH_ADDRESS, "LPToken", "LPT"); - vm.label(address(__subject__), "RMM"); - (SY, PT, YT) = IPMarket(market).readTokens(); - (MarketState memory ms,) = getPendleMarketData(); - timeToExpiry = ms.expiry - block.timestamp; - - deal(wstETH, address(this), 1_000_000e18); - - mintSY(100_000 ether); - mintPtYt(50_000 ether); - - IERC20(wstETH).approve(address(subject()), type(uint256).max); - IERC20(SY).approve(address(subject()), type(uint256).max); - IERC20(PT).approve(address(subject()), type(uint256).max); - IERC20(YT).approve(address(subject()), type(uint256).max); - - IERC20(wstETH).approve(address(router), type(uint256).max); - IERC20(SY).approve(address(router), type(uint256).max); - IERC20(PT).approve(address(router), type(uint256).max); - IERC20(YT).approve(address(router), type(uint256).max); - IERC20(market).approve(address(router), type(uint256).max); - IERC20(market).approve(address(router), type(uint256).max); - } - - function getPendleMarketData() public returns (MarketState memory ms, MarketPreCompute memory mp) { - PYIndex index = YT.newIndex(); - ms = market.readState(address(router)); - mp = ms.getMarketPreCompute(index, block.timestamp); - } - - function subject() public view returns (RMM) { - return __subject__; - } - - function balanceNative(address token, address account) internal view returns (uint256) { - if (token == address(0)) { - return address(this).balance; - } - - return MockERC20(token).balanceOf(account); - } - - function getPtExchangeRate() internal returns (int256) { - (MarketState memory ms, MarketPreCompute memory mp) = getPendleMarketData(); - return ms.totalPt._getExchangeRate(mp.totalAsset, mp.rateScalar, mp.rateAnchor, 0); - } - - function balanceWad(address token, address account) internal view returns (uint256) { - return upscale(balanceNative(token, account), scalar(token)); - } - - function mintSY(uint256 amount) public { - IERC20(wstETH).approve(address(SY), type(uint256).max); - SY.deposit(address(this), address(wstETH), amount, 1); - } - - function mintPtYt(uint256 amount) public returns (uint256 amountPY) { - SY.transfer(address(YT), amount); - amountPY = YT.mintPY(address(this), address(this)); - } - - modifier basic_sy() { - (MarketState memory ms, MarketPreCompute memory mp) = getPendleMarketData(); - uint256 price = uint256(getPtExchangeRate()); - subject().init({ - PT_: address(PT), - priceX: price, - amountX: uint256(ms.totalSy - 100 ether), - strike_: uint256(mp.rateAnchor), - sigma_: 0.025 ether, - fee_: 0.0003 ether, - curator_: address(0x55) - }); - console2.log("tau", subject().futureTau(block.timestamp)); - - _; - } - - function test_basic_trading_function_result_sy() public basic_sy { - PYIndex index = YT.newIndex(); - int256 result = subject().tradingFunction(index); - console2.log("rx", subject().reserveX()); - console2.log("ry", subject().reserveY()); - assertTrue(abs(result) <= 10, "Trading function result is not within init epsilon."); - } - - function test_swapSy_over_time_sy() public basic_sy { - PYIndex index = YT.newIndex(); - uint256 deltaSy = 1 ether; - console2.log("maturity", subject().maturity()); - vm.warp(block.timestamp + 5 days); - (,, uint256 minAmountOut,,) = subject().prepareSwapSyIn(deltaSy, block.timestamp, index); - (uint256 amountOut, int256 deltaLiquidity) = subject().swapExactSyForPt(deltaSy, 0, address(this)); - vm.warp(block.timestamp + 5 days); - (,, minAmountOut,,) = subject().prepareSwapSyIn(deltaSy, block.timestamp, index); - (amountOut, deltaLiquidity) = subject().swapExactSyForPt(deltaSy, 0, address(this)); - } - - function test_swap_pt() public basic_sy { - PYIndex index = YT.newIndex(); - uint256 deltaPt = 1 ether; - uint256 balanceSyBefore = SY.balanceOf(address(this)); - subject().prepareSwapPtIn(deltaPt, block.timestamp, index); - (uint256 amtOut,) = subject().swapExactPtForSy(deltaPt, 0, address(this)); - console2.log("amtOut", amtOut); - - uint256 balanceSyAfter = SY.balanceOf(address(this)); - assertEq(balanceSyAfter - balanceSyBefore, amtOut, "SwapY did not return the expected amount of SY."); - } - - // todo: whats the error? - function test_basic_price() public basic_sy { - PYIndex index = YT.newIndex(); - uint256 totalAsset = index.syToAsset(subject().reserveX()); - uint256 price = subject().approxSpotPrice(totalAsset); - assertApproxEqAbs(price, uint256(getPtExchangeRate()), 10_000, "Price is not approximately 1 ether."); - } - - function test_price_impact() public basic_sy { - PYIndex index = YT.newIndex(); - uint256 totalAsset = index.syToAsset(subject().reserveX()); - uint256 price = subject().approxSpotPrice(totalAsset); - console2.log("initialPrice", price); - uint256 deltaPt = 100 ether; - (uint256 amountOut,) = subject().swapExactPtForSy(deltaPt, 0, address(this)); - console2.log("amountOut", amountOut); - uint256 priceAfter = subject().approxSpotPrice(totalAsset); - console2.log("priceAfter", priceAfter); - } - - function test_mintSY_with_wstETH() public basic_sy { - uint256 amountIn = 1 ether; - uint256 expectedShares = amountIn; // 1:1 exchange rate for wstETH to shares - - uint256 sharesOut = subject().mintSY(address(this), wstETH, amountIn, 0); - // assertEq(sharesOut, expectedShares, "Minting with wstETH did not return the expected amount of shares."); - } - - function test_pt_flash_swap_changes_balances() public basic_sy { - SY.transfer(address(0x55), SY.balanceOf(address(this))); - PT.transfer(address(0x55), PT.balanceOf(address(this))); - YT.transfer(address(0x55), YT.balanceOf(address(this))); - mintSY(1 ether); - PYIndex index = YT.newIndex(); - uint256 rPT = subject().reserveX(); - uint256 ytOut = subject().computeSYToYT(index, 1 ether, 500 ether, block.timestamp, 0, 10_000); - - console2.log("got here"); - (uint256 amtOut,) = - subject().swapExactSyForYt(1 ether, ytOut, ytOut.mulDivDown(95, 100), 500 ether, 10_000, address(this)); - } -} From 018ff0028a3a3a3c04c8fefdde352c9e13595739 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 28 Jun 2024 15:06:00 +0400 Subject: [PATCH 056/116] chore: add dev comment to MockRMM --- test/MockRMM.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/test/MockRMM.sol b/test/MockRMM.sol index df1b99d..3dbbbfb 100644 --- a/test/MockRMM.sol +++ b/test/MockRMM.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.13; import "./../src/RMM.sol"; +/// @dev Extends the RMM contract to expose internal functions for testing. contract MockRMM is RMM { constructor(address weth_, string memory name_, string memory symbol_) RMM(weth_, name_, symbol_) { WETH = weth_; From 5fa7efcd430cb2534be91fcd8b79a7280debea46 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 28 Jun 2024 15:14:35 +0400 Subject: [PATCH 057/116] test: add assetToSyUp to SetUp base test file --- test/SetUp.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/SetUp.sol b/test/SetUp.sol index 7621839..208f058 100644 --- a/test/SetUp.sol +++ b/test/SetUp.sol @@ -191,4 +191,8 @@ contract SetUp is Test { function syToAsset(uint256 amount) public returns (uint256) { return newIndex().syToAsset(amount); } + + function assetToSyUp(uint256 amount) public returns (uint256) { + return newIndex().assetToSyUp(amount); + } } From c6b7d197fbde3187f35563524470de027f3ed525 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 28 Jun 2024 15:14:48 +0400 Subject: [PATCH 058/116] test: use newIndex --- test/unit/SwapExactSyForYt.t.sol | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/test/unit/SwapExactSyForYt.t.sol b/test/unit/SwapExactSyForYt.t.sol index 54d1e8f..eb19931 100644 --- a/test/unit/SwapExactSyForYt.t.sol +++ b/test/unit/SwapExactSyForYt.t.sol @@ -9,8 +9,6 @@ import {ExcessInput} from "../../src/lib/RmmErrors.sol"; import {Swap} from "../../src/lib/RmmEvents.sol"; contract SwapExactSyForYtTest is SetUp { - using PYIndexLib for IPYieldToken; - using PYIndexLib for PYIndex; using FixedPointMathLib for uint256; using FixedPointMathLib for int256; @@ -27,7 +25,7 @@ contract SwapExactSyForYtTest is SetUp { State memory state; state.to = address(0xbeef); state.exactSYIn = 1 ether; - state.index = YT.newIndex(); + state.index = newIndex(); uint256[] memory preBalances = new uint256[](3); preBalances[0] = ERC20(address(SY)).balanceOf(address(this)); @@ -51,11 +49,10 @@ contract SwapExactSyForYtTest is SetUp { uint256 preReserveY = rmm.reserveY(); uint256 preTotalLiquidity = rmm.totalLiquidity(); - PYIndex index = YT.newIndex(); uint256 exactSYIn = 1 ether; - uint256 ytOut = rmm.computeSYToYT(index, exactSYIn, 500 ether, block.timestamp, 0, 10_000); + uint256 ytOut = rmm.computeSYToYT(newIndex(), exactSYIn, 500 ether, block.timestamp, 0, 10_000); (uint256 amountInWad, uint256 amountOutWad,, int256 deltaLiquidity,) = - rmm.prepareSwapPtIn(ytOut, block.timestamp, index); + rmm.prepareSwapPtIn(ytOut, block.timestamp, newIndex()); rmm.swapExactSyForYt(exactSYIn, ytOut, ytOut.mulDivDown(95, 100), 500 ether, 10_000, address(this)); assertEq(rmm.reserveX(), preReserveX - amountOutWad); @@ -64,13 +61,12 @@ contract SwapExactSyForYtTest is SetUp { } function test_swapExactSyForYt_EmitsEvent() public useSYPool withSY(address(this), 10 ether) { - PYIndex index = YT.newIndex(); uint256 exactSYIn = 1 ether; - uint256 ytOut = rmm.computeSYToYT(index, exactSYIn, 500 ether, block.timestamp, 0, 10_000); + uint256 ytOut = rmm.computeSYToYT(newIndex(), exactSYIn, 500 ether, block.timestamp, 0, 10_000); (uint256 amountInWad, uint256 amountOutWad, uint256 amountOut, int256 deltaLiquidity,) = - rmm.prepareSwapPtIn(ytOut, block.timestamp, index); + rmm.prepareSwapPtIn(ytOut, block.timestamp, newIndex()); - uint256 delta = index.assetToSyUp(amountInWad) - amountOutWad; + uint256 delta = assetToSyUp(amountInWad) - amountOutWad; vm.expectEmit(); emit Swap(address(this), address(0xbeef), address(SY), address(YT), delta, amountOut, deltaLiquidity); rmm.swapExactSyForYt(exactSYIn, ytOut, ytOut.mulDivDown(95, 100), 500 ether, 10_000, address(0xbeef)); @@ -78,9 +74,8 @@ contract SwapExactSyForYtTest is SetUp { function test_swapExactSyForYt_RevertsWhenExcessInput() public useSYPool withSY(address(this), 10 ether) { uint256 exactSYIn = 1 ether; - PYIndex index = YT.newIndex(); - uint256 ytOut = rmm.computeSYToYT(index, exactSYIn, 500 ether, block.timestamp, 0, 10_000); - (, uint256 amountOutWad,,,) = rmm.prepareSwapPtIn(ytOut, block.timestamp, index); + uint256 ytOut = rmm.computeSYToYT(newIndex(), exactSYIn, 500 ether, block.timestamp, 0, 10_000); + (, uint256 amountOutWad,,,) = rmm.prepareSwapPtIn(ytOut, block.timestamp, newIndex()); vm.expectRevert(); rmm.swapExactSyForYt(exactSYIn - 1 ether, ytOut, amountOutWad, 500 ether, 10_000, address(this)); @@ -88,9 +83,8 @@ contract SwapExactSyForYtTest is SetUp { function test_swapExactSyForYt_RevertsWhenInsufficientOutput() public useSYPool withSY(address(this), 10 ether) { uint256 exactSYIn = 1 ether; - PYIndex index = YT.newIndex(); - uint256 ytOut = rmm.computeSYToYT(index, exactSYIn, 500 ether, block.timestamp, 0, 10_000); - (, uint256 amountOutWad,,,) = rmm.prepareSwapPtIn(ytOut, block.timestamp, index); + uint256 ytOut = rmm.computeSYToYT(newIndex(), exactSYIn, 500 ether, block.timestamp, 0, 10_000); + (, uint256 amountOutWad,,,) = rmm.prepareSwapPtIn(ytOut, block.timestamp, newIndex()); vm.expectRevert(); rmm.swapExactSyForYt(exactSYIn, ytOut, amountOutWad + 1 ether, 500 ether, 10_000, address(this)); From 2dcc8d2d4840eea37445519f06f5ba142644094c Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 28 Jun 2024 15:42:42 +0400 Subject: [PATCH 059/116] chore: remove logs --- test/invariant/RMMInvariants.t.sol | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/test/invariant/RMMInvariants.t.sol b/test/invariant/RMMInvariants.t.sol index 280e1fa..1a7e0eb 100644 --- a/test/invariant/RMMInvariants.t.sol +++ b/test/invariant/RMMInvariants.t.sol @@ -32,19 +32,9 @@ contract RMMInvariantsTest is SetUp { selectors[4] = RMMHandler.swapExactSyForPt.selector; selectors[5] = RMMHandler.swapExactYtForSy.selector; selectors[6] = RMMHandler.swapExactTokenForYt.selector; - - console.logBytes4(selectors[0]); - console.logBytes4(selectors[1]); - console.logBytes4(selectors[2]); - console.logBytes4(selectors[3]); - console.logBytes4(selectors[4]); - console.logBytes4(selectors[5]); - console.logBytes4(selectors[6]); - targetSelector(FuzzSelector({addr: address(handler), selectors: selectors})); - console2.log("got here?"); + targetSelector(FuzzSelector({addr: address(handler), selectors: selectors})); targetContract(address(handler)); - console2.log("here?"); } function afterInvariant() public view { @@ -62,9 +52,7 @@ contract RMMInvariantsTest is SetUp { /// forge-config: default.invariant.depth = 100 /// forge-config: default.invariant.fail-on-revert = true function invariant_TradingFunction() public { - IPYieldToken YT = handler.YT(); - PYIndex index = YT.newIndex(); - assertTrue(abs(rmm.tradingFunction(index)) <= 100, "Invariant out of valid range"); + assertTrue(abs(rmm.tradingFunction(newIndex())) <= 100, "Invariant out of valid range"); } /// forge-config: default.invariant.runs = 10 @@ -78,7 +66,6 @@ contract RMMInvariantsTest is SetUp { /// forge-config: default.invariant.depth = 100 /// forge-config: default.invariant.fail-on-revert = true function invariant_ReserveY() public view { - console2.log("here?"); assertEq(rmm.reserveY(), handler.ghost_reserveY()); } } From 8b2f31bfe2a4f3512ba02304464354d2d1f9dce9 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 28 Jun 2024 16:14:02 +0400 Subject: [PATCH 060/116] test: add RMM label --- test/SetUp.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/test/SetUp.sol b/test/SetUp.sol index 208f058..b54c2dd 100644 --- a/test/SetUp.sol +++ b/test/SetUp.sol @@ -58,6 +58,7 @@ contract SetUp is Test { rmm = new MockRMM(address(weth), "RMM-LP-TOKEN", "RMM-LPT"); SY = new PendleWstEthSY("wstEthSY", "wstEthSY", address(weth), address(wstETH)); + vm.label(address(rmm), "RMM"); vm.label(address(SY), "SY"); vm.label(address(YT), "YT"); vm.label(address(PT), "PT"); From 0dfe4f7bb760dcaa0e92ad733b5c828799b7e76f Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 28 Jun 2024 16:41:12 +0400 Subject: [PATCH 061/116] test: add DEFAULT_NOW parameter, warp to NOW, update default expiry --- test/SetUp.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/SetUp.sol b/test/SetUp.sol index b54c2dd..3eb709d 100644 --- a/test/SetUp.sol +++ b/test/SetUp.sol @@ -46,7 +46,8 @@ contract SetUp is Test { // Some default constants. - uint32 public DEFAULT_EXPIRY = 1_717_214_400; + uint32 public DEFAULT_NOW = 1719578346; + uint32 public DEFAULT_EXPIRY = DEFAULT_NOW + 365 days; uint256 public DEFAULT_AMOUNT = 1_000 ether; // Main setup functions. @@ -89,6 +90,7 @@ contract SetUp is Test { } function setUp() public virtual { + vm.warp(DEFAULT_NOW); setUpContracts(DEFAULT_EXPIRY); } From 87087a4e1e5b5b131e8b209bfe2bc2340ed47f79 Mon Sep 17 00:00:00 2001 From: kinrezc Date: Fri, 28 Jun 2024 13:57:23 -0400 Subject: [PATCH 062/116] approxEq on strike --- test/unit/SwapExactPtForSy.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/SwapExactPtForSy.t.sol b/test/unit/SwapExactPtForSy.t.sol index 3b111d1..1ac50aa 100644 --- a/test/unit/SwapExactPtForSy.t.sol +++ b/test/unit/SwapExactPtForSy.t.sol @@ -38,7 +38,7 @@ contract SwapExactPtForSyTest is SetUp { assertEq(rmm.reserveX(), preReserveX - amountOut); assertEq(rmm.reserveY(), preReserveY + amountIn); assertEq(rmm.totalLiquidity(), preLiquidity + uint256(deltaLiquidity)); - assertEq(rmm.strike(), preStrike); + assertApproxEqAbs(rmm.strike(), preStrike, 100); } function test_swapExactPtForSy_MaintainsTradingFunction() public useDefaultPool { From d37d733ea965545886d9e8b4432c820d6b44d4af Mon Sep 17 00:00:00 2001 From: clemlak Date: Mon, 1 Jul 2024 18:23:59 +0400 Subject: [PATCH 063/116] feat: add SY and PT scalars as storage variables --- src/RMM.sol | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/RMM.sol b/src/RMM.sol index 4b19faa..df345dc 100644 --- a/src/RMM.sol +++ b/src/RMM.sol @@ -12,8 +12,6 @@ import "./lib/RmmErrors.sol"; import "./lib/RmmEvents.sol"; import "./lib/LiquidityLib.sol"; -import "forge-std/console2.sol"; - contract RMM is ERC20 { using FixedPointMathLib for uint256; using FixedPointMathLib for int256; @@ -32,6 +30,9 @@ contract RMM is ERC20 { IStandardizedYield public SY; IPYieldToken public YT; + uint256 public SY_scalar; + uint256 public PT_scalar; + uint256 public reserveX; uint256 public reserveY; uint256 public totalLiquidity; @@ -87,6 +88,9 @@ contract RMM is ERC20 { SY = IStandardizedYield(PT.SY()); YT = IPYieldToken(PT.YT()); + SY_scalar = scalar(address(SY)); + PT_scalar = scalar(address(PT)); + // Sets approvals ahead of time for this contract to handle routing. { // curly braces scope avoids stack too deep @@ -144,6 +148,7 @@ contract RMM is ERC20 { // SY is needed to cover the minted PT, so we need to debit the delta from the msg.sender uint256 delta = index.assetToSyUp(amountInWad) - amountOutWad; + uint256 ytOut = amountOut + delta; if (delta > maxSyIn) { @@ -346,8 +351,7 @@ contract RMM is ERC20 { returns (uint256 deltaX, uint256 deltaY) { (uint256 deltaXWad, uint256 deltaYWad, uint256 lptBurned) = prepareDeallocate(deltaLiquidity); - (deltaX, deltaY) = - (downscaleDown(deltaXWad, scalar(address(SY))), downscaleDown(deltaYWad, scalar(address(PT)))); + (deltaX, deltaY) = (downscaleDown(deltaXWad, SY_scalar), downscaleDown(deltaYWad, PT_scalar)); if (minDeltaXOut > deltaX) { revert InsufficientOutput(deltaLiquidity, minDeltaXOut, deltaX); @@ -549,7 +553,7 @@ contract RMM is ERC20 { view returns (uint256 amountInWad, uint256 ptOutWad, uint256 amountIn, int256 deltaLiquidity, uint256 strike_) { - ptOutWad = upscale(ptOut, scalar(address(PT))); + ptOutWad = upscale(ptOut, PT_scalar); // convert amountIn to assetIn, only for swapping X in PoolPreCompute memory comp = preparePoolPreCompute(index, timestamp); uint256 computedL = solveL(comp, totalLiquidity, reserveY, sigma); @@ -559,7 +563,7 @@ contract RMM is ERC20 { uint256 nextReserveX = solveX(reserveY - ptOutWad, nextLiquidity, comp.strike_, sigma, comp.tau_); amountInWad = index.assetToSy(nextReserveX) - reserveX; - amountIn = downscaleDown(amountInWad, scalar(address(SY))); + amountIn = downscaleDown(amountInWad, SY_scalar); strike_ = comp.strike_; deltaLiquidity = toInt(nextLiquidity) - toInt(totalLiquidity); } @@ -569,7 +573,7 @@ contract RMM is ERC20 { view returns (uint256 amountInWad, uint256 amountOutWad, uint256 amountOut, int256 deltaLiquidity, uint256 strike_) { - amountInWad = upscale(amountIn, scalar(address(SY))); + amountInWad = upscale(amountIn, SY_scalar); // convert amountIn to assetIn, only for swapping X in uint256 amountInAsset = index.syToAsset(amountInWad); @@ -585,7 +589,7 @@ contract RMM is ERC20 { solveY(comp.reserveInAsset + amountInAsset, nextLiquidity, comp.strike_, sigma, comp.tau_); amountOutWad = reserveY - nextReserveY; - amountOut = downscaleDown(amountOutWad, scalar(address(PT))); + amountOut = downscaleDown(amountOutWad, PT_scalar); strike_ = comp.strike_; deltaLiquidity = toInt(nextLiquidity) - toInt(totalLiquidity); } @@ -595,7 +599,7 @@ contract RMM is ERC20 { view returns (uint256 amountInWad, uint256 amountOutWad, uint256 amountOut, int256 deltaLiquidity, uint256 strike_) { - amountInWad = upscale(ptIn, scalar(address(PT))); + amountInWad = upscale(ptIn, PT_scalar); PoolPreCompute memory comp = preparePoolPreCompute(index, timestamp); // compute liquidity @@ -608,7 +612,7 @@ contract RMM is ERC20 { uint256 nextReserveX = solveX(reserveY + amountInWad, nextLiquidity, comp.strike_, sigma, comp.tau_); amountOutWad = reserveX - index.assetToSy(nextReserveX); - amountOut = downscaleDown(amountOutWad, scalar(address(SY))); + amountOut = downscaleDown(amountOutWad, SY_scalar); strike_ = comp.strike_; deltaLiquidity = toInt(nextLiquidity) - toInt(totalLiquidity); } @@ -620,10 +624,10 @@ contract RMM is ERC20 { returns (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity, uint256 lptMinted) { if (inTermsOfX) { - deltaXWad = upscale(amount, scalar(address(SY))); + deltaXWad = upscale(amount, SY_scalar); (deltaYWad, deltaLiquidity) = computeAllocationGivenDeltaX(deltaXWad, reserveX, reserveY, totalLiquidity); } else { - deltaYWad = upscale(amount, scalar(address(PT))); + deltaYWad = upscale(amount, PT_scalar); (deltaXWad, deltaLiquidity) = computeAllocationGivenDeltaY(deltaYWad, reserveX, reserveY, totalLiquidity); } From 08831a241e40d0c8156714ffd0264eea6cb14f7f Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 2 Jul 2024 17:43:02 +0400 Subject: [PATCH 064/116] test: add increaseTime to invariant handler --- test/invariant/RMMHandler.sol | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/test/invariant/RMMHandler.sol b/test/invariant/RMMHandler.sol index dfe17af..f34009a 100644 --- a/test/invariant/RMMHandler.sol +++ b/test/invariant/RMMHandler.sol @@ -91,7 +91,6 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { sigma = bound(sigma, 0.02 ether, 0.05 ether); fee = bound(fee, 0.0001 ether, 0.001 ether); - PYIndex index = IPYieldToken(PT.YT()).newIndex(); (uint256 totalLiquidity, uint256 amountY) = rmm.prepareInit(priceX, amountX, strike, sigma, PT.expiry(), index); @@ -112,7 +111,6 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { function allocate(uint256 deltaX, uint256 deltaY) public createActor countCall(this.allocate.selector) { deltaX = bound(deltaX, 0.1 ether, 10 ether); - vm.startPrank(currentActor); (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = @@ -172,10 +170,12 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { PYIndex index = YT.newIndex(); - (uint256 syMinted, uint256 ytOut) = - rmm.computeTokenToYT(index, address(weth), amountTokenIn, rmm.reserveX().mulDivDown(95, 100), block.timestamp, 0, 1_000); + (uint256 syMinted, uint256 ytOut) = rmm.computeTokenToYT( + index, address(weth), amountTokenIn, rmm.reserveX().mulDivDown(95, 100), block.timestamp, 0, 1_000 + ); - uint256 amountPtIn = rmm.computeSYToYT(index, syMinted, rmm.reserveX().mulDivDown(95, 100), block.timestamp, ytOut, 0.005 ether); + uint256 amountPtIn = + rmm.computeSYToYT(index, syMinted, rmm.reserveX().mulDivDown(95, 100), block.timestamp, ytOut, 0.005 ether); (uint256 amountInWad, uint256 amountOutWad,, int256 deltaLiquidity,) = rmm.prepareSwapPtIn(amountPtIn, block.timestamp, index); @@ -250,4 +250,10 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { ghost_reserveY -= ytIn; // ghost_totalLiquidity += int256(deltaLiquidity); } + + function increaseTime(uint256 amount) public countCall(this.increaseTime.selector) { + amount = bound(amount, 1, 86400); + + vm.warp(block.timestamp + amount); + } } From 1118f08aeeb744bd5e6c399af0e9be6641f5d49f Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 2 Jul 2024 18:17:02 +0400 Subject: [PATCH 065/116] feat: remove init in favor of constructor --- src/RMM.sol | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/RMM.sol b/src/RMM.sol index df345dc..496eeba 100644 --- a/src/RMM.sol +++ b/src/RMM.sol @@ -66,14 +66,27 @@ contract RMM is ERC20 { } } - constructor(address weth_, string memory name_, string memory symbol_) ERC20(name_, symbol_, 18) { + constructor( + address weth_, + string memory name_, + string memory symbol_, + address PT_, + uint256 priceX, + uint256 amountX, + uint256 strike_, + uint256 sigma_, + uint256 fee_, + address curator_ + ) ERC20(name_, symbol_, 18) { WETH = weth_; + + _init(PT_, priceX, amountX, strike_, sigma_, fee_, curator_); } receive() external payable {} /// @dev Initializes the pool with an initial price, amount of `x` tokens, and parameters. - function init( + function _init( address PT_, uint256 priceX, uint256 amountX, @@ -81,8 +94,7 @@ contract RMM is ERC20 { uint256 sigma_, uint256 fee_, address curator_ - ) external lock { - if (strike != 0) revert AlreadyInitialized(); + ) internal { if (strike_ <= 1e18) revert InvalidStrike(); PT = IPPrincipalToken(PT_); SY = IStandardizedYield(PT.SY()); From d2c11eb3c2129a9537f078ce85e4e90643157a44 Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 2 Jul 2024 18:20:02 +0400 Subject: [PATCH 066/116] test: add new constructor params to MockRMM --- test/MockRMM.sol | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/test/MockRMM.sol b/test/MockRMM.sol index 3dbbbfb..b714e33 100644 --- a/test/MockRMM.sol +++ b/test/MockRMM.sol @@ -5,9 +5,18 @@ import "./../src/RMM.sol"; /// @dev Extends the RMM contract to expose internal functions for testing. contract MockRMM is RMM { - constructor(address weth_, string memory name_, string memory symbol_) RMM(weth_, name_, symbol_) { - WETH = weth_; - } + constructor( + address weth_, + string memory name_, + string memory symbol_, + address PT_, + uint256 priceX, + uint256 amountX, + uint256 strike_, + uint256 sigma_, + uint256 fee_, + address curator_ + ) RMM(weth_, name_, symbol_, PT_, priceX, amountX, strike_, sigma_, fee_, curator_) {} function debit(address token, uint256 amountWad) public returns (uint256 paymentNative) { return _debit(token, amountWad); From 0dac70d018dc922ced94d5290b16b7d36136ce6a Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 2 Jul 2024 18:21:59 +0400 Subject: [PATCH 067/116] test: update test base setup with new RMM constructor --- test/SetUp.sol | 55 ++++++++++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/test/SetUp.sol b/test/SetUp.sol index 3eb709d..f8ff726 100644 --- a/test/SetUp.sol +++ b/test/SetUp.sol @@ -18,12 +18,11 @@ import {RMM} from "./../src/RMM.sol"; import {MockRMM} from "./MockRMM.sol"; struct InitParams { + address PT; uint256 priceX; uint256 amountX; uint256 strike; uint256 sigma; - uint256 maturity; - address PT; uint256 fee; address curator; } @@ -46,9 +45,8 @@ contract SetUp is Test { // Some default constants. - uint32 public DEFAULT_NOW = 1719578346; - uint32 public DEFAULT_EXPIRY = DEFAULT_NOW + 365 days; - uint256 public DEFAULT_AMOUNT = 1_000 ether; + uint32 public constant DEFAULT_NOW = 1719578346; + uint32 public constant DEFAULT_EXPIRY = DEFAULT_NOW + 365 days; // Main setup functions. @@ -56,10 +54,8 @@ contract SetUp is Test { weth = new WETH(); stETH = new MockStETH(); wstETH = new MockWstETH(address(stETH)); - rmm = new MockRMM(address(weth), "RMM-LP-TOKEN", "RMM-LPT"); SY = new PendleWstEthSY("wstEthSY", "wstEthSY", address(weth), address(wstETH)); - vm.label(address(rmm), "RMM"); vm.label(address(SY), "SY"); vm.label(address(YT), "YT"); vm.label(address(PT), "PT"); @@ -82,6 +78,23 @@ contract SetUp is Test { YT = IPYieldToken(factory.getYT(address(SY), expiry)); PT = IPPrincipalToken(factory.getPT(address(SY), expiry)); + } + + function setUpRMM(InitParams memory initParams) public { + rmm = new MockRMM( + address(weth), + "RMM-LP-TOKEN", + "RMM-LPT", + initParams.PT, + initParams.priceX, + initParams.amountX, + initParams.strike, + initParams.sigma, + initParams.fee, + initParams.curator + ); + + vm.label(address(rmm), "RMM"); weth.approve(address(rmm), type(uint256).max); SY.approve(address(rmm), type(uint256).max); @@ -126,7 +139,6 @@ contract SetUp is Test { amountX: 100 ether, strike: 1.15 ether, sigma: 0.02 ether, - maturity: PT.expiry(), PT: address(PT), fee: 0.00016 ether, curator: address(0x55) @@ -135,17 +147,11 @@ contract SetUp is Test { // Here are some modifiers, you can use them as hooks to set up the environment before running a test. - modifier useContrats(uint32 expiry) { - setUpContracts(expiry); - _; - } - modifier useDefaultPool() { uint256 amount = 10000 ether; mintSY(address(this), amount); mintPY(address(this), amount / 2); - InitParams memory params = getDefaultParams(); - rmm.init(params.PT, params.priceX, params.amountX, params.strike, params.sigma, params.fee, params.curator); + setUpRMM(getDefaultParams()); _; } @@ -153,15 +159,16 @@ contract SetUp is Test { uint256 amount = 1_000_000 ether; mintSY(address(this), amount); mintPY(address(this), amount / 2); - rmm.init( - address(PT), - 1007488755655417383, - 1311689788256138069842, - 1009671560073979390, - 0.025 ether, - 0.0003 ether, - address(0x55) - ); + InitParams memory initParams = InitParams({ + PT: address(PT), + priceX: 1007488755655417383, + amountX: 1311689788256138069842, + strike: 1009671560073979390, + sigma: 0.023 ether, + fee: 0.0003 ether, + curator: address(0x55) + }); + setUpRMM(initParams); _; } From 7c748609f89edc14acb20882fcdc5db5e5deaad5 Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 2 Jul 2024 18:25:20 +0400 Subject: [PATCH 068/116] test: add RMM default name and symbol --- test/SetUp.sol | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/test/SetUp.sol b/test/SetUp.sol index f8ff726..31c2185 100644 --- a/test/SetUp.sol +++ b/test/SetUp.sol @@ -27,6 +27,12 @@ struct InitParams { address curator; } +uint32 constant DEFAULT_NOW = 1719578346; +uint32 constant DEFAULT_EXPIRY = DEFAULT_NOW + 365 days; + +string constant DEFAULT_NAME = "RMM-LP-TOKEN"; +string constant DEFAULT_SYMBOL = "RMM-LPT"; + // address constant wstETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; //real wsteth // address constant WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; @@ -43,11 +49,6 @@ contract SetUp is Test { MockWstETH public wstETH; MockStETH public stETH; - // Some default constants. - - uint32 public constant DEFAULT_NOW = 1719578346; - uint32 public constant DEFAULT_EXPIRY = DEFAULT_NOW + 365 days; - // Main setup functions. function setUpContracts(uint32 expiry) public { @@ -83,8 +84,8 @@ contract SetUp is Test { function setUpRMM(InitParams memory initParams) public { rmm = new MockRMM( address(weth), - "RMM-LP-TOKEN", - "RMM-LPT", + DEFAULT_NAME, + DEFAULT_SYMBOL, initParams.PT, initParams.priceX, initParams.amountX, From babf2e9831fc906fd2438ceeb678367836309538 Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 2 Jul 2024 18:28:35 +0400 Subject: [PATCH 069/116] test: update test_constructor_InitializesParameters --- test/unit/Constructor.t.sol | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/test/unit/Constructor.t.sol b/test/unit/Constructor.t.sol index fa2cd16..cca8dbe 100644 --- a/test/unit/Constructor.t.sol +++ b/test/unit/Constructor.t.sol @@ -1,18 +1,19 @@ /// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import {Test, RMM} from "../SetUp.sol"; +import {SetUp, RMM, InitParams, DEFAULT_NAME, DEFAULT_SYMBOL} from "../SetUp.sol"; -contract ConstructorTest is Test { - function test_constructor_InitializesParameters() public { - address weth = address(0xbeef); - string memory name = "RMM-LP-TOKEN"; - string memory symbol = "RMM-LPT"; +contract ConstructorTest is SetUp { + function test_constructor_InitializesParameters() public useDefaultPool { + InitParams memory initParams = getDefaultParams(); - RMM rmm = new RMM(weth, name, symbol); - - assertEq(rmm.WETH(), weth); - assertEq(rmm.name(), name); - assertEq(rmm.symbol(), symbol); + assertEq(rmm.WETH(), address(weth)); + assertEq(rmm.name(), DEFAULT_NAME); + assertEq(rmm.symbol(), DEFAULT_SYMBOL); + assertEq(address(rmm.PT()), address(PT)); + assertEq(address(rmm.SY()), address(SY)); + assertEq(address(rmm.YT()), address(YT)); + assertEq(rmm.strike(), initParams.strike); + assertEq(rmm.reserveX(), initParams.amountX); } } From e6233b51d534697180d7ddc8bbafbf8562ccf5d3 Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 2 Jul 2024 18:30:03 +0400 Subject: [PATCH 070/116] test: add more assertions to test_constructor_InitializesParameters --- test/unit/Constructor.t.sol | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/unit/Constructor.t.sol b/test/unit/Constructor.t.sol index cca8dbe..eff9f3a 100644 --- a/test/unit/Constructor.t.sol +++ b/test/unit/Constructor.t.sol @@ -1,7 +1,7 @@ /// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import {SetUp, RMM, InitParams, DEFAULT_NAME, DEFAULT_SYMBOL} from "../SetUp.sol"; +import {SetUp, RMM, InitParams, DEFAULT_NAME, DEFAULT_SYMBOL, DEFAULT_EXPIRY} from "../SetUp.sol"; contract ConstructorTest is SetUp { function test_constructor_InitializesParameters() public useDefaultPool { @@ -15,5 +15,11 @@ contract ConstructorTest is SetUp { assertEq(address(rmm.YT()), address(YT)); assertEq(rmm.strike(), initParams.strike); assertEq(rmm.reserveX(), initParams.amountX); + assertEq(rmm.sigma(), initParams.sigma); + assertEq(rmm.fee(), initParams.fee); + assertEq(rmm.maturity(), DEFAULT_EXPIRY); + assertEq(rmm.curator(), initParams.curator); + assertEq(rmm.lastTimestamp(), block.timestamp); + assertEq(rmm.initTimestamp(), block.timestamp); } } From a9019cf6c5c91544ae0c972498bebf4e852d1886 Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 3 Jul 2024 12:02:28 +0400 Subject: [PATCH 071/116] feat: add prepareInit to RMM Factory --- src/Factory.sol | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/src/Factory.sol b/src/Factory.sol index 68c37a5..c2db466 100644 --- a/src/Factory.sol +++ b/src/Factory.sol @@ -1,9 +1,16 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.13; +import {PYIndexLib, PYIndex} from "pendle/core/StandardizedYield/PYIndex.sol"; +import {IPYieldToken} from "pendle/interfaces/IPYieldToken.sol"; +import {IPPrincipalToken} from "pendle/interfaces/IPPrincipalToken.sol"; +import {computeTauWadYears, PoolPreCompute, computeLGivenX, computeY, solveL} from "./lib/RmmLib.sol"; import {RMM} from "./RMM.sol"; contract Factory { + using PYIndexLib for IPYieldToken; + using PYIndexLib for PYIndex; + event NewPool(address indexed caller, address indexed pool, string name, string symbol); address public immutable WETH; @@ -14,10 +21,39 @@ contract Factory { WETH = weth_; } - function createRMM(string memory poolName, string memory poolSymbol) external returns (RMM) { - RMM rmm = new RMM(WETH, poolName, poolSymbol); + function createRMM( + string memory poolName, + string memory poolSymbol, + address PT_, + uint256 priceX, + uint256 amountX, + uint256 strike_, + uint256 sigma_, + uint256 fee_, + address curator_ + ) external returns (RMM) { + RMM rmm = new RMM(WETH, poolName, poolSymbol, PT_, priceX, amountX, strike_, sigma_, fee_, curator_); emit NewPool(msg.sender, address(rmm), poolName, poolSymbol); pools.push(address(rmm)); return rmm; } + + function prepareInit( + address PT, + uint256 priceX, + uint256 amountX, + uint256 strike_, + uint256 sigma_, + uint256 maturity_ + ) public returns (uint256 totalLiquidity_, uint256 amountY) { + PYIndex index = IPYieldToken(IPPrincipalToken(PT).YT()).newIndex(); + uint256 totalAsset = index.syToAsset(amountX); + uint256 tau_ = computeTauWadYears(maturity_ - block.timestamp); + PoolPreCompute memory comp = PoolPreCompute({reserveInAsset: totalAsset, strike_: strike_, tau_: tau_}); + uint256 initialLiquidity = + computeLGivenX({reserveX_: totalAsset, S: priceX, strike_: strike_, sigma_: sigma_, tau_: tau_}); + amountY = + computeY({reserveX_: totalAsset, liquidity: initialLiquidity, strike_: strike_, sigma_: sigma_, tau_: tau_}); + totalLiquidity_ = solveL(comp, initialLiquidity, amountY, sigma_); + } } From 04a5e7b7e47adee5c711226376870ddd76f78634 Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 3 Jul 2024 12:11:20 +0400 Subject: [PATCH 072/116] feat: change prepareInit parameters (remove PT, add PYIndex) --- src/Factory.sol | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Factory.sol b/src/Factory.sol index c2db466..8b1ca9f 100644 --- a/src/Factory.sol +++ b/src/Factory.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.13; import {PYIndexLib, PYIndex} from "pendle/core/StandardizedYield/PYIndex.sol"; import {IPYieldToken} from "pendle/interfaces/IPYieldToken.sol"; -import {IPPrincipalToken} from "pendle/interfaces/IPPrincipalToken.sol"; import {computeTauWadYears, PoolPreCompute, computeLGivenX, computeY, solveL} from "./lib/RmmLib.sol"; import {RMM} from "./RMM.sol"; @@ -39,14 +38,13 @@ contract Factory { } function prepareInit( - address PT, uint256 priceX, uint256 amountX, uint256 strike_, uint256 sigma_, - uint256 maturity_ - ) public returns (uint256 totalLiquidity_, uint256 amountY) { - PYIndex index = IPYieldToken(IPPrincipalToken(PT).YT()).newIndex(); + uint256 maturity_, + PYIndex index + ) public view returns (uint256 totalLiquidity_, uint256 amountY) { uint256 totalAsset = index.syToAsset(amountX); uint256 tau_ = computeTauWadYears(maturity_ - block.timestamp); PoolPreCompute memory comp = PoolPreCompute({reserveInAsset: totalAsset, strike_: strike_, tau_: tau_}); From 16561723c375b4d8e45da5db2345e58b8bcef34c Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 3 Jul 2024 14:16:26 +0400 Subject: [PATCH 073/116] test: add real WstETH contract --- test/WstETH.sol | 100 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 test/WstETH.sol diff --git a/test/WstETH.sol b/test/WstETH.sol new file mode 100644 index 0000000..af79aa9 --- /dev/null +++ b/test/WstETH.sol @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.13; + +import {ERC20} from "solmate/tokens/ERC20.sol"; + +interface IStETH { + function getPooledEthByShares(uint256 _sharesAmount) external view returns (uint256); + + function getSharesByPooledEth(uint256 _pooledEthAmount) external view returns (uint256); + + function submit(address _referral) external payable returns (uint256); +} + +contract WstETH is ERC20 { + IStETH public stETH; + + /** + * @param _stETH address of the StETH token to wrap + */ + constructor(address _stETH) ERC20("Wrapped liquid staked Ether 2.0", "wstETH", 18) { + stETH = IStETH(_stETH); + } + + /** + * @notice Exchanges stETH to wstETH + * @param _stETHAmount amount of stETH to wrap in exchange for wstETH + * @dev Requirements: + * - `_stETHAmount` must be non-zero + * - msg.sender must approve at least `_stETHAmount` stETH to this + * contract. + * - msg.sender must have at least `_stETHAmount` of stETH. + * User should first approve _stETHAmount to the WstETH contract + * @return Amount of wstETH user receives after wrap + */ + function wrap(uint256 _stETHAmount) external returns (uint256) { + require(_stETHAmount > 0, "wstETH: can't wrap zero stETH"); + uint256 wstETHAmount = stETH.getSharesByPooledEth(_stETHAmount); + _mint(msg.sender, wstETHAmount); + ERC20(address(stETH)).transferFrom(msg.sender, address(this), _stETHAmount); + return wstETHAmount; + } + + /** + * @notice Exchanges wstETH to stETH + * @param _wstETHAmount amount of wstETH to uwrap in exchange for stETH + * @dev Requirements: + * - `_wstETHAmount` must be non-zero + * - msg.sender must have at least `_wstETHAmount` wstETH. + * @return Amount of stETH user receives after unwrap + */ + function unwrap(uint256 _wstETHAmount) external returns (uint256) { + require(_wstETHAmount > 0, "wstETH: zero amount unwrap not allowed"); + uint256 stETHAmount = stETH.getPooledEthByShares(_wstETHAmount); + _burn(msg.sender, _wstETHAmount); + ERC20(address(stETH)).transfer(msg.sender, stETHAmount); + return stETHAmount; + } + + /** + * @notice Shortcut to stake ETH and auto-wrap returned stETH + */ + receive() external payable { + uint256 shares = stETH.submit{value: msg.value}(address(0)); + _mint(msg.sender, shares); + } + + /** + * @notice Get amount of wstETH for a given amount of stETH + * @param _stETHAmount amount of stETH + * @return Amount of wstETH for a given stETH amount + */ + function getWstETHByStETH(uint256 _stETHAmount) external view returns (uint256) { + return stETH.getSharesByPooledEth(_stETHAmount); + } + + /** + * @notice Get amount of stETH for a given amount of wstETH + * @param _wstETHAmount amount of wstETH + * @return Amount of stETH for a given wstETH amount + */ + function getStETHByWstETH(uint256 _wstETHAmount) external view returns (uint256) { + return stETH.getPooledEthByShares(_wstETHAmount); + } + + /** + * @notice Get amount of stETH for a one wstETH + * @return Amount of stETH for 1 wstETH + */ + function stEthPerToken() external view returns (uint256) { + return stETH.getPooledEthByShares(1 ether); + } + + /** + * @notice Get amount of wstETH for a one stETH + * @return Amount of wstETH for a 1 stETH + */ + function tokensPerStEth() external view returns (uint256) { + return stETH.getSharesByPooledEth(1 ether); + } +} From 9d8edab9768b467239b126643886fcf1e7aeaf2e Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 3 Jul 2024 14:44:10 +0400 Subject: [PATCH 074/116] feat: add immutable vars, init them in constructor, remove unused vars, clean up init --- src/RMM.sol | 96 +++++++++++++++++++---------------------------------- 1 file changed, 34 insertions(+), 62 deletions(-) diff --git a/src/RMM.sol b/src/RMM.sol index 496eeba..e63fd00 100644 --- a/src/RMM.sol +++ b/src/RMM.sol @@ -21,32 +21,28 @@ contract RMM is ERC20 { using SafeTransferLib for ERC20; - int256 public constant INIT_UPPER_BOUND = 30; uint256 public constant IMPLIED_RATE_TIME = 365 * 86400; uint256 public constant BURNT_LIQUIDITY = 1000; - address public immutable WETH; - IPPrincipalToken public PT; - IStandardizedYield public SY; - IPYieldToken public YT; + IPPrincipalToken public immutable PT; + IStandardizedYield public immutable SY; + IPYieldToken public immutable YT; - uint256 public SY_scalar; - uint256 public PT_scalar; + uint256 public immutable SY_scalar; + uint256 public immutable PT_scalar; + + uint256 public immutable sigma; + uint256 public immutable fee; + uint256 public immutable maturity; uint256 public reserveX; uint256 public reserveY; uint256 public totalLiquidity; - uint256 public strike; - uint256 public sigma; - uint256 public fee; - uint256 public maturity; - uint256 public initTimestamp; uint256 public lastTimestamp; uint256 public lastImpliedPrice; - address public curator; uint256 private _lock = 1; modifier lock() { @@ -66,42 +62,17 @@ contract RMM is ERC20 { } } - constructor( - address weth_, - string memory name_, - string memory symbol_, - address PT_, - uint256 priceX, - uint256 amountX, - uint256 strike_, - uint256 sigma_, - uint256 fee_, - address curator_ - ) ERC20(name_, symbol_, 18) { - WETH = weth_; - - _init(PT_, priceX, amountX, strike_, sigma_, fee_, curator_); - } - - receive() external payable {} - - /// @dev Initializes the pool with an initial price, amount of `x` tokens, and parameters. - function _init( - address PT_, - uint256 priceX, - uint256 amountX, - uint256 strike_, - uint256 sigma_, - uint256 fee_, - address curator_ - ) internal { - if (strike_ <= 1e18) revert InvalidStrike(); + constructor(string memory name_, string memory symbol_, address PT_, uint256 sigma_, uint256 fee_) + ERC20(name_, symbol_, 18) + { PT = IPPrincipalToken(PT_); SY = IStandardizedYield(PT.SY()); YT = IPYieldToken(PT.YT()); - SY_scalar = scalar(address(SY)); PT_scalar = scalar(address(PT)); + sigma = sigma_; + maturity = PT.expiry(); + fee = fee_; // Sets approvals ahead of time for this contract to handle routing. { @@ -114,16 +85,18 @@ contract RMM is ERC20 { if (address(token) != address(0)) token.approve(address(SY), type(uint256).max); } } + } - PYIndex index = YT.newIndex(); - sigma = sigma_; - maturity = PT.expiry(); - fee = fee_; - - initTimestamp = block.timestamp; - curator = curator_; + function init(uint256 priceX, uint256 amountX, uint256 strike_) + external + lock + returns (uint256 totalLiquidity_, uint256 amountY) + { + if (strike != 0) revert AlreadyInitialized(); + if (strike_ <= 1e18) revert InvalidStrike(); - (uint256 totalLiquidity_, uint256 amountY) = prepareInit(priceX, amountX, strike_, sigma_, maturity, index); + PYIndex index = YT.newIndex(); + (totalLiquidity_, amountY) = prepareInit(priceX, amountX, strike_, sigma, index); _mint(msg.sender, totalLiquidity_ - BURNT_LIQUIDITY); _mint(address(0), BURNT_LIQUIDITY); @@ -132,10 +105,12 @@ contract RMM is ERC20 { _debit(address(PT), reserveY); emit Init( - msg.sender, address(SY), PT_, amountX, amountY, totalLiquidity_, strike_, sigma_, fee_, maturity, curator_ + msg.sender, address(SY), address(PT), amountX, amountY, totalLiquidity_, strike_, sigma, fee, maturity ); } + receive() external payable {} + /// @dev Swaps SY for YT, sending at least `minAmountOut` YT to `to`. /// @notice `amountIn` is an amount of PT that needs to be minted from the SY in and the SY flash swapped from the pool function swapExactSyForYt( @@ -542,16 +517,13 @@ contract RMM is ERC20 { } //prepare calls - function prepareInit( - uint256 priceX, - uint256 amountX, - uint256 strike_, - uint256 sigma_, - uint256 maturity_, - PYIndex index - ) public view returns (uint256 totalLiquidity_, uint256 amountY) { + function prepareInit(uint256 priceX, uint256 amountX, uint256 strike_, uint256 sigma_, PYIndex index) + public + view + returns (uint256 totalLiquidity_, uint256 amountY) + { uint256 totalAsset = index.syToAsset(amountX); - uint256 tau_ = computeTauWadYears(maturity_ - block.timestamp); + uint256 tau_ = computeTauWadYears(maturity - block.timestamp); PoolPreCompute memory comp = PoolPreCompute({reserveInAsset: totalAsset, strike_: strike_, tau_: tau_}); uint256 initialLiquidity = computeLGivenX({reserveX_: totalAsset, S: priceX, strike_: strike_, sigma_: sigma_, tau_: tau_}); From d34cc165a939ebc4fec6ffedc7693314f8978cd4 Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 3 Jul 2024 14:44:36 +0400 Subject: [PATCH 075/116] feat: clean up factory, add new RMM constructor params --- src/Factory.sol | 41 +++++------------------------------------ 1 file changed, 5 insertions(+), 36 deletions(-) diff --git a/src/Factory.sol b/src/Factory.sol index 8b1ca9f..e4cfa09 100644 --- a/src/Factory.sol +++ b/src/Factory.sol @@ -1,15 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.13; -import {PYIndexLib, PYIndex} from "pendle/core/StandardizedYield/PYIndex.sol"; -import {IPYieldToken} from "pendle/interfaces/IPYieldToken.sol"; -import {computeTauWadYears, PoolPreCompute, computeLGivenX, computeY, solveL} from "./lib/RmmLib.sol"; import {RMM} from "./RMM.sol"; contract Factory { - using PYIndexLib for IPYieldToken; - using PYIndexLib for PYIndex; - event NewPool(address indexed caller, address indexed pool, string name, string symbol); address public immutable WETH; @@ -20,38 +14,13 @@ contract Factory { WETH = weth_; } - function createRMM( - string memory poolName, - string memory poolSymbol, - address PT_, - uint256 priceX, - uint256 amountX, - uint256 strike_, - uint256 sigma_, - uint256 fee_, - address curator_ - ) external returns (RMM) { - RMM rmm = new RMM(WETH, poolName, poolSymbol, PT_, priceX, amountX, strike_, sigma_, fee_, curator_); + function createRMM(string memory poolName, string memory poolSymbol, address PT, uint256 sigma, uint256 fee) + external + returns (RMM) + { + RMM rmm = new RMM(poolName, poolSymbol, PT, sigma, fee); emit NewPool(msg.sender, address(rmm), poolName, poolSymbol); pools.push(address(rmm)); return rmm; } - - function prepareInit( - uint256 priceX, - uint256 amountX, - uint256 strike_, - uint256 sigma_, - uint256 maturity_, - PYIndex index - ) public view returns (uint256 totalLiquidity_, uint256 amountY) { - uint256 totalAsset = index.syToAsset(amountX); - uint256 tau_ = computeTauWadYears(maturity_ - block.timestamp); - PoolPreCompute memory comp = PoolPreCompute({reserveInAsset: totalAsset, strike_: strike_, tau_: tau_}); - uint256 initialLiquidity = - computeLGivenX({reserveX_: totalAsset, S: priceX, strike_: strike_, sigma_: sigma_, tau_: tau_}); - amountY = - computeY({reserveX_: totalAsset, liquidity: initialLiquidity, strike_: strike_, sigma_: sigma_, tau_: tau_}); - totalLiquidity_ = solveL(comp, initialLiquidity, amountY, sigma_); - } } From e45f849d901bcb2229aa1ec303c0a45379db8cb0 Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 3 Jul 2024 14:44:53 +0400 Subject: [PATCH 076/116] feat: remove curator from Init event --- src/lib/RmmEvents.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/RmmEvents.sol b/src/lib/RmmEvents.sol index 3254de1..9f73159 100644 --- a/src/lib/RmmEvents.sol +++ b/src/lib/RmmEvents.sol @@ -12,8 +12,7 @@ event Init( uint256 strike, uint256 sigma, uint256 fee, - uint256 maturity, - address indexed curator + uint256 maturity ); /// @dev Emitted on swaps. From 6df39daf549579a173b639dcf7dcfc2e8578a811 Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 3 Jul 2024 14:45:11 +0400 Subject: [PATCH 077/116] test: update MockRMM constructor params --- test/MockRMM.sol | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/test/MockRMM.sol b/test/MockRMM.sol index b714e33..6d54c9a 100644 --- a/test/MockRMM.sol +++ b/test/MockRMM.sol @@ -5,18 +5,9 @@ import "./../src/RMM.sol"; /// @dev Extends the RMM contract to expose internal functions for testing. contract MockRMM is RMM { - constructor( - address weth_, - string memory name_, - string memory symbol_, - address PT_, - uint256 priceX, - uint256 amountX, - uint256 strike_, - uint256 sigma_, - uint256 fee_, - address curator_ - ) RMM(weth_, name_, symbol_, PT_, priceX, amountX, strike_, sigma_, fee_, curator_) {} + constructor(string memory name_, string memory symbol_, address PT_, uint256 sigma_, uint256 fee_) + RMM(name_, symbol_, PT_, sigma_, fee_) + {} function debit(address token, uint256 amountWad) public returns (uint256 paymentNative) { return _debit(token, amountWad); From 13108e7ad36832227232c76890731b10a1e74f6c Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 3 Jul 2024 14:47:24 +0400 Subject: [PATCH 078/116] test: update init tests --- test/unit/Init.t.sol | 144 +++++++------------------------------------ 1 file changed, 23 insertions(+), 121 deletions(-) diff --git a/test/unit/Init.t.sol b/test/unit/Init.t.sol index 5c6908a..1a2ac30 100644 --- a/test/unit/Init.t.sol +++ b/test/unit/Init.t.sol @@ -1,7 +1,7 @@ /// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import {SetUp, RMM, InitParams} from "../SetUp.sol"; +import {SetUp, RMM, InitParams, DEFAULT_EXPIRY} from "../SetUp.sol"; import {Init} from "../../src/lib/RmmEvents.sol"; import {AlreadyInitialized, InvalidStrike} from "../../src/lib/RmmErrors.sol"; import {PYIndex, PYIndexLib, IPYieldToken} from "./../../src/RMM.sol"; @@ -10,75 +10,19 @@ contract InitTest is SetUp { using PYIndexLib for IPYieldToken; using PYIndexLib for PYIndex; - function test_init_StoresInitParams() - public - withSY(address(this), 2000000 ether) - withPY(address(this), 1000000 ether) - { - InitParams memory initParams = getDefaultParams(); - - rmm.init( - initParams.PT, - initParams.priceX, - initParams.amountX, - initParams.strike, - initParams.sigma, - initParams.fee, - initParams.curator - ); - - assertEq(rmm.strike(), initParams.strike); - assertEq(rmm.sigma(), initParams.sigma); - assertEq(rmm.fee(), initParams.fee); - assertEq(rmm.maturity(), initParams.maturity); - assertEq(rmm.curator(), initParams.curator); - assertEq(rmm.lastTimestamp(), block.timestamp); - assertEq(rmm.initTimestamp(), block.timestamp); - } - - function test_init_StoresTokens() - public - withSY(address(this), 2000000 ether) - withPY(address(this), 1000000 ether) - { - InitParams memory initParams = getDefaultParams(); - - rmm.init( - initParams.PT, - initParams.priceX, - initParams.amountX, - initParams.strike, - initParams.sigma, - initParams.fee, - initParams.curator - ); - - assertEq(address(rmm.PT()), address(PT)); - assertEq(address(rmm.YT()), address(YT)); - assertEq(address(rmm.SY()), address(SY)); - } - function test_init_MintsLiquidity() public withSY(address(this), 2000000 ether) withPY(address(this), 1000000 ether) { InitParams memory initParams = getDefaultParams(); + setUpRMM(initParams); PYIndex index = IPYieldToken(PT.YT()).newIndex(); - (uint256 totalLiquidity,) = rmm.prepareInit( - initParams.priceX, initParams.amountX, initParams.strike, initParams.sigma, initParams.maturity, index - ); + (uint256 totalLiquidity,) = + rmm.prepareInit(initParams.priceX, initParams.amountX, initParams.strike, initParams.sigma, index); - rmm.init( - initParams.PT, - initParams.priceX, - initParams.amountX, - initParams.strike, - initParams.sigma, - initParams.fee, - initParams.curator - ); + rmm.init(initParams.priceX, initParams.amountX, initParams.strike); assertEq(rmm.totalLiquidity(), totalLiquidity); assertEq(rmm.balanceOf(address(this)), totalLiquidity - rmm.BURNT_LIQUIDITY()); @@ -87,25 +31,17 @@ contract InitTest is SetUp { function test_init_AdjustsPool() public withSY(address(this), 2000000 ether) withPY(address(this), 1000000 ether) { InitParams memory initParams = getDefaultParams(); + setUpRMM(initParams); PYIndex index = IPYieldToken(PT.YT()).newIndex(); - (, uint256 amountY) = rmm.prepareInit( - initParams.priceX, initParams.amountX, initParams.strike, initParams.sigma, initParams.maturity, index - ); - - rmm.init( - initParams.PT, - initParams.priceX, - initParams.amountX, - initParams.strike, - initParams.sigma, - initParams.fee, - initParams.curator - ); + (, uint256 amountY) = + rmm.prepareInit(initParams.priceX, initParams.amountX, initParams.strike, initParams.sigma, index); + rmm.init(initParams.priceX, initParams.amountX, initParams.strike); assertEq(rmm.lastTimestamp(), block.timestamp, "lastTimestamp"); assertEq(rmm.reserveX(), initParams.amountX, "reserveX"); assertEq(rmm.reserveY(), amountY, "reserveY"); + assertEq(rmm.strike(), initParams.strike); } function test_init_TransfersTokens() @@ -114,26 +50,18 @@ contract InitTest is SetUp { withPY(address(this), 1000000 ether) { InitParams memory initParams = getDefaultParams(); + setUpRMM(initParams); PYIndex index = IPYieldToken(PT.YT()).newIndex(); - (, uint256 amountY) = rmm.prepareInit( - initParams.priceX, initParams.amountX, initParams.strike, initParams.sigma, initParams.maturity, index - ); + (, uint256 amountY) = + rmm.prepareInit(initParams.priceX, initParams.amountX, initParams.strike, initParams.sigma, index); uint256 thisPreBalanceSY = SY.balanceOf(address(this)); uint256 thisPreBalancePT = PT.balanceOf(address(this)); uint256 rmmPreBalanceSY = SY.balanceOf(address(rmm)); uint256 rmmPreBalancePT = PT.balanceOf(address(rmm)); - rmm.init( - initParams.PT, - initParams.priceX, - initParams.amountX, - initParams.strike, - initParams.sigma, - initParams.fee, - initParams.curator - ); + rmm.init(initParams.priceX, initParams.amountX, initParams.strike); assertEq(SY.balanceOf(address(this)), thisPreBalanceSY - initParams.amountX); assertEq(PT.balanceOf(address(this)), thisPreBalancePT - amountY); @@ -147,11 +75,11 @@ contract InitTest is SetUp { withPY(address(this), 1000000 ether) { InitParams memory initParams = getDefaultParams(); + setUpRMM(initParams); PYIndex index = IPYieldToken(PT.YT()).newIndex(); - (uint256 totalLiquidity, uint256 amountY) = rmm.prepareInit( - initParams.priceX, initParams.amountX, initParams.strike, initParams.sigma, initParams.maturity, index - ); + (uint256 totalLiquidity, uint256 amountY) = + rmm.prepareInit(initParams.priceX, initParams.amountX, initParams.strike, initParams.sigma, index); vm.expectEmit(); @@ -165,19 +93,10 @@ contract InitTest is SetUp { initParams.strike, initParams.sigma, initParams.fee, - initParams.maturity, - initParams.curator + DEFAULT_EXPIRY ); - rmm.init( - initParams.PT, - initParams.priceX, - initParams.amountX, - initParams.strike, - initParams.sigma, - initParams.fee, - initParams.curator - ); + rmm.init(initParams.priceX, initParams.amountX, initParams.strike); } function test_init_RevertsIfAlreadyInitialized() @@ -189,16 +108,7 @@ contract InitTest is SetUp { InitParams memory initParams = getDefaultParams(); vm.expectRevert(AlreadyInitialized.selector); - - rmm.init( - initParams.PT, - initParams.priceX, - initParams.amountX, - initParams.strike, - initParams.sigma, - initParams.fee, - initParams.curator - ); + rmm.init(initParams.priceX, initParams.amountX, initParams.strike); } function test_init_RevertsIfInvalidStrike() @@ -207,18 +117,10 @@ contract InitTest is SetUp { withPY(address(this), 1000000 ether) { InitParams memory initParams = getDefaultParams(); + setUpRMM(initParams); - vm.expectRevert(abi.encodeWithSelector(InvalidStrike.selector)); - - rmm.init( - initParams.PT, - initParams.priceX, - initParams.amountX, - 1 ether, - initParams.sigma, - initParams.fee, - initParams.curator - ); + vm.expectRevert(InvalidStrike.selector); + rmm.init(initParams.priceX, initParams.amountX, 1 ether); } function test_init_RevertsWhenLocked() From 42823a6c4a6c7c4eede5104be7d287cba20a040d Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 3 Jul 2024 14:47:32 +0400 Subject: [PATCH 079/116] test: update constructor tests --- test/unit/Constructor.t.sol | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/test/unit/Constructor.t.sol b/test/unit/Constructor.t.sol index eff9f3a..1d96e32 100644 --- a/test/unit/Constructor.t.sol +++ b/test/unit/Constructor.t.sol @@ -4,22 +4,16 @@ pragma solidity ^0.8.13; import {SetUp, RMM, InitParams, DEFAULT_NAME, DEFAULT_SYMBOL, DEFAULT_EXPIRY} from "../SetUp.sol"; contract ConstructorTest is SetUp { - function test_constructor_InitializesParameters() public useDefaultPool { + function test_constructor_InitializesParameters() public view { InitParams memory initParams = getDefaultParams(); - assertEq(rmm.WETH(), address(weth)); assertEq(rmm.name(), DEFAULT_NAME); assertEq(rmm.symbol(), DEFAULT_SYMBOL); assertEq(address(rmm.PT()), address(PT)); assertEq(address(rmm.SY()), address(SY)); assertEq(address(rmm.YT()), address(YT)); - assertEq(rmm.strike(), initParams.strike); - assertEq(rmm.reserveX(), initParams.amountX); assertEq(rmm.sigma(), initParams.sigma); assertEq(rmm.fee(), initParams.fee); assertEq(rmm.maturity(), DEFAULT_EXPIRY); - assertEq(rmm.curator(), initParams.curator); - assertEq(rmm.lastTimestamp(), block.timestamp); - assertEq(rmm.initTimestamp(), block.timestamp); } } From 93bbb032b8d65d8619cb2945cc34249eb28512ca Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 3 Jul 2024 14:47:43 +0400 Subject: [PATCH 080/116] chore: update scripts --- script/Deploy.s.sol | 2 +- script/DeployPool.s.sol | 12 ++---------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index bf77c74..aed650e 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -18,7 +18,7 @@ contract Deploy is Script { address sender = vm.addr(pk); console2.log("Deploying RMM from", sender); - RMM rmm = FACTORY.createRMM("RMM", "RMM"); + // RMM rmm = FACTORY.createRMM("RMM", "RMM"); vm.stopBroadcast(); } diff --git a/script/DeployPool.s.sol b/script/DeployPool.s.sol index 2291d20..cbf8bbd 100644 --- a/script/DeployPool.s.sol +++ b/script/DeployPool.s.sol @@ -87,7 +87,7 @@ contract DeployPool is Script { uint256 pk = vm.envUint(ENV_PRIVATE_KEY); vm.startBroadcast(pk); sender = vm.addr(pk); - rmm = FACTORY.createRMM("Lido Staked ETH 24 Dec 2025", "stETH-24DEC25"); + rmm = FACTORY.createRMM("Lido Staked ETH 24 Dec 2025", "stETH-24DEC25", address(PT), 0.02 ether, fee); console2.log("rmm address: ", address(rmm)); mintSY(10_000 ether); @@ -107,15 +107,7 @@ contract DeployPool is Script { PYIndex index = YT.newIndex(); (MarketState memory ms, MarketPreCompute memory mp) = getPendleMarketData(index); uint256 price = uint256(getPtExchangeRate(index)); - rmm.init({ - PT_: address(PT), - priceX: price, - amountX: uint256(ms.totalSy), - strike_: uint256(mp.rateAnchor), - sigma_: 0.02 ether, - fee_: fee, - curator_: address(0x55) - }); + rmm.init({priceX: price, amountX: uint256(ms.totalSy), strike_: uint256(mp.rateAnchor)}); vm.stopBroadcast(); } From d470f281b3bb2748429e715ca985f8ec26b44ced Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 3 Jul 2024 14:48:22 +0400 Subject: [PATCH 081/116] test: add WstETH to test SetUp, add setUpRMM, initRMM functions --- test/SetUp.sol | 53 +++++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/test/SetUp.sol b/test/SetUp.sol index 31c2185..46d89b3 100644 --- a/test/SetUp.sol +++ b/test/SetUp.sol @@ -10,10 +10,8 @@ import {BaseSplitCodeFactory} from "pendle/core/libraries/BaseSplitCodeFactory.s import {PendleYieldTokenV2} from "pendle/core/YieldContractsV2/PendleYieldTokenV2.sol"; import {PendleYieldContractFactoryV2} from "pendle/core/YieldContractsV2/PendleYieldContractFactoryV2.sol"; import {PendleWstEthSY} from "pendle/core/StandardizedYield/implementations/PendleWstEthSY.sol"; -import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; -import {MockWstETH} from "./mocks/MockWstETH.sol"; +import {WstETH} from "./WstETH.sol"; import {MockStETH} from "./mocks/MockStETH.sol"; - import {RMM} from "./../src/RMM.sol"; import {MockRMM} from "./MockRMM.sol"; @@ -46,7 +44,7 @@ contract SetUp is Test { PendleWstEthSY public SY; IPYieldToken public YT; IPPrincipalToken public PT; - MockWstETH public wstETH; + WstETH public wstETH; MockStETH public stETH; // Main setup functions. @@ -54,7 +52,7 @@ contract SetUp is Test { function setUpContracts(uint32 expiry) public { weth = new WETH(); stETH = new MockStETH(); - wstETH = new MockWstETH(address(stETH)); + wstETH = new WstETH(address(stETH)); SY = new PendleWstEthSY("wstEthSY", "wstEthSY", address(weth), address(wstETH)); vm.label(address(SY), "SY"); @@ -81,19 +79,17 @@ contract SetUp is Test { PT = IPPrincipalToken(factory.getPT(address(SY), expiry)); } + function setUp() public virtual { + vm.warp(DEFAULT_NOW); + setUpContracts(DEFAULT_EXPIRY); + + InitParams memory initParams = getDefaultParams(); + setUpRMM(initParams); + initRMM(initParams); + } + function setUpRMM(InitParams memory initParams) public { - rmm = new MockRMM( - address(weth), - DEFAULT_NAME, - DEFAULT_SYMBOL, - initParams.PT, - initParams.priceX, - initParams.amountX, - initParams.strike, - initParams.sigma, - initParams.fee, - initParams.curator - ); + rmm = new MockRMM(DEFAULT_NAME, DEFAULT_SYMBOL, initParams.PT, initParams.sigma, initParams.fee); vm.label(address(rmm), "RMM"); @@ -103,9 +99,15 @@ contract SetUp is Test { YT.approve(address(rmm), type(uint256).max); } - function setUp() public virtual { - vm.warp(DEFAULT_NOW); - setUpContracts(DEFAULT_EXPIRY); + function initRMM(InitParams memory initParams) public { + (, uint256 amountY) = + rmm.prepareInit(initParams.priceX, initParams.amountX, initParams.strike, initParams.sigma, newIndex()); + + uint256 amount = 10000 ether; + mintSY(address(this), amount); + mintPY(address(this), amount / 2); + + rmm.init(initParams.priceX, initParams.amountX, initParams.strike); } // Here are some utility functions, you can use them to set specific states inside of a test. @@ -149,17 +151,13 @@ contract SetUp is Test { // Here are some modifiers, you can use them as hooks to set up the environment before running a test. modifier useDefaultPool() { - uint256 amount = 10000 ether; - mintSY(address(this), amount); - mintPY(address(this), amount / 2); setUpRMM(getDefaultParams()); + initRMM(getDefaultParams()); + _; } modifier useSYPool() { - uint256 amount = 1_000_000 ether; - mintSY(address(this), amount); - mintPY(address(this), amount / 2); InitParams memory initParams = InitParams({ PT: address(PT), priceX: 1007488755655417383, @@ -169,7 +167,10 @@ contract SetUp is Test { fee: 0.0003 ether, curator: address(0x55) }); + setUpRMM(initParams); + initRMM(initParams); + _; } From 54fc63253e74836c20dc049d61301ba223d97900 Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 3 Jul 2024 14:48:34 +0400 Subject: [PATCH 082/116] test: update Debit tests --- test/unit/Debit.t.sol | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/test/unit/Debit.t.sol b/test/unit/Debit.t.sol index f989bd6..32d8535 100644 --- a/test/unit/Debit.t.sol +++ b/test/unit/Debit.t.sol @@ -1,19 +1,12 @@ /// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import {Test} from "forge-std/Test.sol"; +import {SetUp} from "../SetUp.sol"; import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; import {FeeOnTransferToken} from "../../src/test/FeeOnTransferToken.sol"; -import {MockRMM} from "../MockRMM.sol"; import {InsufficientPayment} from "./../../src/lib/RmmErrors.sol"; -contract DebitTest is Test { - MockRMM rmm; - - function setUp() public { - rmm = new MockRMM(address(0), "", ""); - } - +contract DebitTest is SetUp { function test_debit_TransfersTokens() public { MockERC20 token = new MockERC20("", "", 18); From 62ae5dd9e270ba3b0976e9598ac6ea4791cbc8af Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 3 Jul 2024 15:09:46 +0400 Subject: [PATCH 083/116] test: use newIndex() in InitTest --- test/unit/Init.t.sol | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/test/unit/Init.t.sol b/test/unit/Init.t.sol index 1a2ac30..392a593 100644 --- a/test/unit/Init.t.sol +++ b/test/unit/Init.t.sol @@ -4,12 +4,8 @@ pragma solidity ^0.8.13; import {SetUp, RMM, InitParams, DEFAULT_EXPIRY} from "../SetUp.sol"; import {Init} from "../../src/lib/RmmEvents.sol"; import {AlreadyInitialized, InvalidStrike} from "../../src/lib/RmmErrors.sol"; -import {PYIndex, PYIndexLib, IPYieldToken} from "./../../src/RMM.sol"; contract InitTest is SetUp { - using PYIndexLib for IPYieldToken; - using PYIndexLib for PYIndex; - function test_init_MintsLiquidity() public withSY(address(this), 2000000 ether) @@ -17,10 +13,9 @@ contract InitTest is SetUp { { InitParams memory initParams = getDefaultParams(); setUpRMM(initParams); - PYIndex index = IPYieldToken(PT.YT()).newIndex(); (uint256 totalLiquidity,) = - rmm.prepareInit(initParams.priceX, initParams.amountX, initParams.strike, initParams.sigma, index); + rmm.prepareInit(initParams.priceX, initParams.amountX, initParams.strike, initParams.sigma, newIndex()); rmm.init(initParams.priceX, initParams.amountX, initParams.strike); @@ -32,10 +27,9 @@ contract InitTest is SetUp { function test_init_AdjustsPool() public withSY(address(this), 2000000 ether) withPY(address(this), 1000000 ether) { InitParams memory initParams = getDefaultParams(); setUpRMM(initParams); - PYIndex index = IPYieldToken(PT.YT()).newIndex(); (, uint256 amountY) = - rmm.prepareInit(initParams.priceX, initParams.amountX, initParams.strike, initParams.sigma, index); + rmm.prepareInit(initParams.priceX, initParams.amountX, initParams.strike, initParams.sigma, newIndex()); rmm.init(initParams.priceX, initParams.amountX, initParams.strike); assertEq(rmm.lastTimestamp(), block.timestamp, "lastTimestamp"); @@ -51,10 +45,9 @@ contract InitTest is SetUp { { InitParams memory initParams = getDefaultParams(); setUpRMM(initParams); - PYIndex index = IPYieldToken(PT.YT()).newIndex(); (, uint256 amountY) = - rmm.prepareInit(initParams.priceX, initParams.amountX, initParams.strike, initParams.sigma, index); + rmm.prepareInit(initParams.priceX, initParams.amountX, initParams.strike, initParams.sigma, newIndex()); uint256 thisPreBalanceSY = SY.balanceOf(address(this)); uint256 thisPreBalancePT = PT.balanceOf(address(this)); @@ -76,10 +69,9 @@ contract InitTest is SetUp { { InitParams memory initParams = getDefaultParams(); setUpRMM(initParams); - PYIndex index = IPYieldToken(PT.YT()).newIndex(); (uint256 totalLiquidity, uint256 amountY) = - rmm.prepareInit(initParams.priceX, initParams.amountX, initParams.strike, initParams.sigma, index); + rmm.prepareInit(initParams.priceX, initParams.amountX, initParams.strike, initParams.sigma, newIndex()); vm.expectEmit(); From 9e37591a091d548b37fcb59a068e137dd291f779 Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 3 Jul 2024 15:42:00 +0400 Subject: [PATCH 084/116] test: update init in RMMHandler --- test/invariant/RMMHandler.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/invariant/RMMHandler.sol b/test/invariant/RMMHandler.sol index f34009a..00ded66 100644 --- a/test/invariant/RMMHandler.sol +++ b/test/invariant/RMMHandler.sol @@ -93,13 +93,13 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { PYIndex index = IPYieldToken(PT.YT()).newIndex(); - (uint256 totalLiquidity, uint256 amountY) = rmm.prepareInit(priceX, amountX, strike, sigma, PT.expiry(), index); + (uint256 totalLiquidity, uint256 amountY) = rmm.prepareInit(priceX, amountX, strike, sigma, index); PT.approve(address(rmm), type(uint256).max); SY.approve(address(rmm), type(uint256).max); YT.approve(address(rmm), type(uint256).max); - rmm.init(address(PT), priceX, amountX, strike, sigma, fee, address(0)); + rmm.init(priceX, amountX, strike); ghost_reserveX += amountX; ghost_reserveY += amountY; From 2a9ba05a531a169c40844fc346e609cc16e11dbb Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 3 Jul 2024 15:42:22 +0400 Subject: [PATCH 085/116] test: use setUpRMM in invariant test file --- test/invariant/RMMInvariants.t.sol | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/test/invariant/RMMInvariants.t.sol b/test/invariant/RMMInvariants.t.sol index 1a7e0eb..4f72d35 100644 --- a/test/invariant/RMMInvariants.t.sol +++ b/test/invariant/RMMInvariants.t.sol @@ -2,20 +2,18 @@ pragma solidity ^0.8.13; import {console} from "forge-std/console.sol"; -import {PYIndex, PYIndexLib} from "pendle/core/StandardizedYield/PYIndex.sol"; import {abs} from "./../../src/lib/RmmLib.sol"; -import {IPYieldToken} from "pendle/interfaces/IPYieldToken.sol"; import {SetUp} from "../SetUp.sol"; import {RMMHandler} from "./RMMHandler.sol"; -import "forge-std/console2.sol"; contract RMMInvariantsTest is SetUp { - using PYIndexLib for IPYieldToken; - RMMHandler handler; function setUp() public virtual override { super.setUp(); + + // This will redeploy the RMM contract without initializing the pool + setUpRMM(getDefaultParams()); handler = new RMMHandler(rmm, PT, SY, YT, weth); mintSY(address(handler), 1000 ether); From 7b27936bb7a83e8cbd2e829272e6172e5a225a38 Mon Sep 17 00:00:00 2001 From: clemlak Date: Thu, 4 Jul 2024 18:20:41 +0400 Subject: [PATCH 086/116] test: add test_credit_TransfersTokens --- test/unit/Credit.t.sol | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/test/unit/Credit.t.sol b/test/unit/Credit.t.sol index e49da0f..7798787 100644 --- a/test/unit/Credit.t.sol +++ b/test/unit/Credit.t.sol @@ -1,7 +1,18 @@ /// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import {Test} from "forge-std/Test.sol"; -import {MockRMM} from "../MockRMM.sol"; +import {SetUp} from "../SetUp.sol"; -contract CreditTest is Test {} +contract CreditTest is SetUp { + function test_credit_TransfersTokens() public { + deal(address(weth), address(rmm), 1 ether); + + uint256 preBalanceRMM = weth.balanceOf(address(rmm)); + uint256 preBalanceAccount = weth.balanceOf(address(this)); + + rmm.credit(address(weth), address(this), 1 ether); + + assertEq(weth.balanceOf(address(rmm)), preBalanceRMM - 1 ether); + assertEq(weth.balanceOf(address(this)), preBalanceAccount + 1 ether); + } +} From d19dfb278ee4b20b459ee2c93d1175457e09dafb Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 5 Jul 2024 15:44:38 +0400 Subject: [PATCH 087/116] test: add test_adjust_UpdatesReservesAndPrices --- test/unit/Adjust.t.sol | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/test/unit/Adjust.t.sol b/test/unit/Adjust.t.sol index 46f7cc6..6175133 100644 --- a/test/unit/Adjust.t.sol +++ b/test/unit/Adjust.t.sol @@ -1,19 +1,27 @@ /// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import "forge-std/Test.sol"; -import {SetUp} from "../SetUp.sol"; -import {MockRMM, PYIndex} from "../MockRMM.sol"; +import {console} from "forge-std/console.sol"; +import {DEFAULT_EXPIRY, SetUp} from "../SetUp.sol"; contract AdjustTest is SetUp { - MockRMM mock; + function test_adjust_UpdatesReservesAndPrices() public { + uint256 preLastImpliedPrice = rmm.lastImpliedPrice(); - function test_adjust() public { - vm.skip(true); + uint256 preReserveX = rmm.reserveX(); + uint256 preReserveY = rmm.reserveY(); + uint256 preTotalLiquidity = rmm.totalLiquidity(); - int256 deltaX; - int256 deltaY; - int256 deltaLiquidity; - // mock.adjust(deltaX, deltaY, deltaLiquidity, 0, PYIndex.wrap(0)); + (uint256 deltaX, uint256 deltaY, uint256 deltaLiquidity,) = rmm.prepareAllocate(true, 1 ether, newIndex()); + rmm.adjust(int256(deltaX), int256(deltaY), int256(deltaLiquidity), rmm.strike(), newIndex()); + + assertEq(rmm.reserveX(), preReserveX + deltaX); + assertEq(rmm.reserveY(), preReserveY + deltaY); + assertEq(rmm.totalLiquidity(), preTotalLiquidity + deltaLiquidity); + + uint256 postLastImpliedPrice = rmm.lastImpliedPrice(); + + console.log("preLastImpliedPrice: ", preLastImpliedPrice); + console.log("postLastImpliedPrice: ", postLastImpliedPrice); } } From 94787ce245b1fec36232fb0b7472e72d292174f5 Mon Sep 17 00:00:00 2001 From: clemlak Date: Mon, 8 Jul 2024 15:10:44 +0400 Subject: [PATCH 088/116] test: add test_credit_DownscalesAmount --- test/unit/Credit.t.sol | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/unit/Credit.t.sol b/test/unit/Credit.t.sol index 7798787..dbab1a5 100644 --- a/test/unit/Credit.t.sol +++ b/test/unit/Credit.t.sol @@ -1,6 +1,7 @@ /// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; +import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; import {SetUp} from "../SetUp.sol"; contract CreditTest is SetUp { @@ -15,4 +16,21 @@ contract CreditTest is SetUp { assertEq(weth.balanceOf(address(rmm)), preBalanceRMM - 1 ether); assertEq(weth.balanceOf(address(this)), preBalanceAccount + 1 ether); } + + function test_credit_DownscalesAmount() public { + MockERC20 token = new MockERC20("", "", 6); + + uint256 amountWAD = 1 ether; + uint256 amountNative = 1 * 10 ** 6; + + token.mint(address(rmm), amountNative); + + uint256 preBalanceRMM = token.balanceOf(address(rmm)); + uint256 preBalanceUser = token.balanceOf(address(this)); + + rmm.credit(address(token), address(this), amountWAD); + + assertEq(token.balanceOf(address(rmm)), preBalanceRMM - amountNative); + assertEq(token.balanceOf(address(this)), preBalanceUser + amountNative); + } } From 293273fcb51e5091fdb316f669d390cb40d7c4f6 Mon Sep 17 00:00:00 2001 From: clemlak Date: Mon, 8 Jul 2024 19:03:12 +0400 Subject: [PATCH 089/116] feat: add MaturityReached --- src/lib/RmmErrors.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/RmmErrors.sol b/src/lib/RmmErrors.sol index 6d904ad..1871088 100644 --- a/src/lib/RmmErrors.sol +++ b/src/lib/RmmErrors.sol @@ -29,3 +29,5 @@ error PaymentFailed(address token, address from, address to, uint256 amount); error InvalidTokenIn(address tokenIn); /// @dev Thrown when an external call is made within the same frame as another. error Reentrancy(); + +error MaturityReached(); From fbc9aa6a89996e37891fdecc9f862ecfc7c4e848 Mon Sep 17 00:00:00 2001 From: clemlak Date: Mon, 8 Jul 2024 19:06:23 +0400 Subject: [PATCH 090/116] feat: add MaturityReached error, use updateReserves in deallocate, fix timeToExpiry --- src/RMM.sol | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/RMM.sol b/src/RMM.sol index e63fd00..b501bbe 100644 --- a/src/RMM.sol +++ b/src/RMM.sol @@ -311,6 +311,8 @@ contract RMM is ERC20 { lock returns (uint256) { + if (block.timestamp >= maturity) revert MaturityReached(); + PYIndex index = YT.newIndex(); (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity, uint256 lptMinted) = prepareAllocate(inTermsOfX, amount, index); @@ -348,7 +350,7 @@ contract RMM is ERC20 { } _burn(msg.sender, lptBurned); // uses state totalLiquidity - _adjust(-toInt(deltaXWad), -toInt(deltaYWad), -toInt(deltaLiquidity), strike, YT.newIndex()); + _updateReserves(-toInt(deltaXWad), -toInt(deltaYWad), -toInt(deltaLiquidity), YT.newIndex()); (uint256 creditNativeX) = _credit(address(SY), to, deltaXWad); (uint256 creditNativeY) = _credit(address(PT), to, deltaYWad); @@ -378,12 +380,10 @@ contract RMM is ERC20 { reserveY = sum(reserveY, deltaY); totalLiquidity = sum(totalLiquidity, deltaLiquidity); strike = strike_; - uint256 timeToExpiry = maturity - block.timestamp; + int256 timeToExpiry = int256(maturity) - int256(block.timestamp); + lastImpliedPrice = timeToExpiry > 0 - ? uint256( - int256(approxSpotPrice(index.syToAsset(reserveX))).lnWad() * int256(IMPLIED_RATE_TIME) - / int256(timeToExpiry) - ) + ? uint256(int256(approxSpotPrice(index.syToAsset(reserveX))).lnWad() * int256(IMPLIED_RATE_TIME) / timeToExpiry) : 1 ether; } From 72a1b66e7076d2a6dfb2d4ab4b7217ee53048459 Mon Sep 17 00:00:00 2001 From: clemlak Date: Mon, 8 Jul 2024 19:06:41 +0400 Subject: [PATCH 091/116] feat: remove computeYTToPT --- src/RMM.sol | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/src/RMM.sol b/src/RMM.sol index b501bbe..b33ef5d 100644 --- a/src/RMM.sol +++ b/src/RMM.sol @@ -492,30 +492,6 @@ contract RMM is ERC20 { } } - function computeYTToPT(PYIndex index, uint256 exactYTIn, uint256 blockTime, uint256 initialGuess) - public - view - returns (uint256) - { - uint256 min = exactYTIn; - uint256 max = initialGuess; - for (uint256 iter = 0; iter < 100; ++iter) { - uint256 guess = (min + max) / 2; - (,, uint256 amountOut,,) = prepareSwapPtIn(guess, blockTime, index); - uint256 netPtToAccount = index.assetToSyUp(guess); - - uint256 netPtToPull = netPtToAccount - amountOut; - if (netPtToPull <= exactYTIn) { - if (isASmallerApproxB(netPtToPull, exactYTIn, 10_000)) { - return guess; - } - min = guess; - } else { - max = guess - 1; - } - } - } - //prepare calls function prepareInit(uint256 priceX, uint256 amountX, uint256 strike_, uint256 sigma_, PYIndex index) public From dd7d73e39b41ada061b1d81cd0439dcf7f70004e Mon Sep 17 00:00:00 2001 From: kinrezc Date: Mon, 8 Jul 2024 14:19:18 -0400 Subject: [PATCH 092/116] wip calcSlope --- src/RMM.sol | 74 ++++++++++++++++++++++++++++++-- test/invariant/RMMHandler.sol | 3 +- test/unit/Adjust.t.sol | 2 +- test/unit/Allocate.t.sol | 12 +++--- test/unit/Deallocate.t.sol | 12 +++--- test/unit/SwapExactSyForYt.t.sol | 2 +- test/unit/SwapPt.sol | 8 ++-- test/unit/SwapSy.sol | 8 ++-- 8 files changed, 94 insertions(+), 27 deletions(-) diff --git a/src/RMM.sol b/src/RMM.sol index b33ef5d..9370f9b 100644 --- a/src/RMM.sol +++ b/src/RMM.sol @@ -113,6 +113,12 @@ contract RMM is ERC20 { /// @dev Swaps SY for YT, sending at least `minAmountOut` YT to `to`. /// @notice `amountIn` is an amount of PT that needs to be minted from the SY in and the SY flash swapped from the pool + /// @notice Order of operations is as follows: + /// 1. Compute amount PT to pull from the pool, this PT is destined to be swapped for SY + /// 2. Compute the amount of SY out given that we are swapping in the borrowed PT + /// 3. Adjust the pool state, execute the PT -> SY swap + /// 4. Compute the SY in needed to cover the flash swapped PT + /// 5. Send the YT to the recipient function swapExactSyForYt( uint256 maxSyIn, uint256 amountPtToFlash, @@ -125,7 +131,8 @@ contract RMM is ERC20 { uint256 amountInWad; uint256 amountOutWad; uint256 strike_; - + + // compute amount PT to pull from the pool amountPtToFlash = computeSYToYT(index, maxSyIn, upperBound, block.timestamp, amountPtToFlash, epsilon); (amountInWad, amountOutWad, amountOut, deltaLiquidity, strike_) = @@ -278,6 +285,7 @@ contract RMM is ERC20 { emit Swap(msg.sender, to, address(SY), address(PT), debitNative, creditNative, deltaLiquidity); } + /// function swapExactYtForSy(uint256 ytIn, uint256 maxSyIn, address to) external lock @@ -315,7 +323,7 @@ contract RMM is ERC20 { PYIndex index = YT.newIndex(); (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity, uint256 lptMinted) = - prepareAllocate(inTermsOfX, amount, index); + prepareAllocate(inTermsOfX, amount); if (deltaLiquidity < minLiquidityOut) { revert InsufficientLiquidityOut(inTermsOfX, amount, minLiquidityOut, deltaLiquidity); } @@ -475,6 +483,7 @@ contract RMM is ERC20 { uint256 epsilon ) public view returns (uint256 guess) { uint256 min = exactSYIn; + max = max > 0 ? max : calcMaxPtIn(reserveX, reserveY, totalLiquidity, strike); for (uint256 iter = 0; iter < 256; ++iter) { guess = initialGuess > 0 && iter == 0 ? initialGuess : (min + max) / 2; (,, uint256 amountOut,,) = prepareSwapPtIn(guess, blockTime, index); @@ -492,6 +501,65 @@ contract RMM is ERC20 { } } + // In RMMLib.sol + +function calcMaxPtIn( + uint256 reserveX_, + uint256 reserveY_, + uint256 totalLiquidity_, + uint256 strike_ +) internal pure returns (uint256) { + uint256 low = 0; + uint256 high = type(uint256).max - reserveY_ - 1; + + while (low != high) { + uint256 mid = (low + high + 1) / 2; + if (calcSlope(reserveX_, reserveY_, totalLiquidity_, strike_, int256(mid)) < 0) { + high = mid - 1; + } else { + low = mid; + } + } + + // Return 99.9% of the calculated max to account for potential precision issues + return (low * 999) / 1000; +} + +function calcSlope( + uint256 reserveX_, + uint256 reserveY_, + uint256 totalLiquidity_, + uint256 strike_, + int256 ptToMarket +) internal pure returns (int256) { + uint256 newReserveY = reserveY_ + uint256(ptToMarket); + uint256 b_i = newReserveY * 1e36 / (strike_ * totalLiquidity_); + + int256 b = Gaussian.ppf(toInt(b_i)); + int256 pdf_b = Gaussian.pdf(b); + + // Calculate the slope + int256 slope = int256(1e36) / (int256(strike_ * totalLiquidity_) * pdf_b / 1e18); + + // Adjust for the relationship between X and Y + int256 dXdY = computedXdY(reserveX_, newReserveY); + + // Combine the direct Y effect and the indirect X effect + return slope + dXdY; +} + +function computedXdY( + uint256 reserveX_, + uint256 reserveY_ + // uint256 totalLiquidity_, + // uint256 strike, + // uint256 sigma, + // uint256 tau +) internal pure returns (int256) { + // This is a placeholder. You'll need to implement this based on your RMM model + return -int256(reserveX_) * 1e18 / int256(reserveY_); + } + //prepare calls function prepareInit(uint256 priceX, uint256 amountX, uint256 strike_, uint256 sigma_, PYIndex index) public @@ -578,7 +646,7 @@ contract RMM is ERC20 { } /// @notice uint256 deltaX is expected in SY units, uint256 deltaY is expected in PT units - function prepareAllocate(bool inTermsOfX, uint256 amount, PYIndex index) + function prepareAllocate(bool inTermsOfX, uint256 amount) public view returns (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity, uint256 lptMinted) diff --git a/test/invariant/RMMHandler.sol b/test/invariant/RMMHandler.sol index 00ded66..ac4db24 100644 --- a/test/invariant/RMMHandler.sol +++ b/test/invariant/RMMHandler.sol @@ -113,8 +113,7 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { vm.startPrank(currentActor); - (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = - rmm.prepareAllocate(true, deltaX, PYIndex.wrap(rmm.YT().pyIndexCurrent())); + (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = rmm.prepareAllocate(true, deltaX); deal(address(SY), currentActor, deltaXWad); deal(address(PT), currentActor, deltaYWad); diff --git a/test/unit/Adjust.t.sol b/test/unit/Adjust.t.sol index 6175133..058176e 100644 --- a/test/unit/Adjust.t.sol +++ b/test/unit/Adjust.t.sol @@ -12,7 +12,7 @@ contract AdjustTest is SetUp { uint256 preReserveY = rmm.reserveY(); uint256 preTotalLiquidity = rmm.totalLiquidity(); - (uint256 deltaX, uint256 deltaY, uint256 deltaLiquidity,) = rmm.prepareAllocate(true, 1 ether, newIndex()); + (uint256 deltaX, uint256 deltaY, uint256 deltaLiquidity,) = rmm.prepareAllocate(true, 1 ether); rmm.adjust(int256(deltaX), int256(deltaY), int256(deltaLiquidity), rmm.strike(), newIndex()); assertEq(rmm.reserveX(), preReserveX + deltaX); diff --git a/test/unit/Allocate.t.sol b/test/unit/Allocate.t.sol index a666f65..532b883 100644 --- a/test/unit/Allocate.t.sol +++ b/test/unit/Allocate.t.sol @@ -9,7 +9,7 @@ import {InsufficientLiquidityOut} from "../../src/lib/RmmErrors.sol"; contract AllocateTest is SetUp { function test_allocate_MintsLiquidity() public useDefaultPool { (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = - rmm.prepareAllocate(true, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + rmm.prepareAllocate(true, 0.1 ether); uint256 preTotalLiquidity = rmm.totalLiquidity(); deltaLiquidity = rmm.allocate(true, 0.1 ether, 0, address(this)); @@ -22,7 +22,7 @@ contract AllocateTest is SetUp { uint256 preBalance = rmm.balanceOf(address(this)); uint256 preTotalSupply = rmm.totalSupply(); (uint256 deltaXWad, uint256 deltaYWad,, uint256 lpMinted) = - rmm.prepareAllocate(true, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + rmm.prepareAllocate(true, 0.1 ether); rmm.allocate(true, 0.1 ether, 0, address(this)); assertEq(rmm.balanceOf(address(this)), preBalance + lpMinted); assertEq(rmm.totalSupply(), preTotalSupply + lpMinted); @@ -35,7 +35,7 @@ contract AllocateTest is SetUp { uint256 preReserveY = rmm.reserveY(); (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = - rmm.prepareAllocate(true, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + rmm.prepareAllocate(true, 0.1 ether); rmm.allocate(true, 0.1 ether, deltaLiquidity, address(this)); assertEq(rmm.reserveX(), preReserveX + deltaXWad); @@ -52,7 +52,7 @@ contract AllocateTest is SetUp { uint256 rmmPreBalancePT = PT.balanceOf(address(rmm)); (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = - rmm.prepareAllocate(true, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + rmm.prepareAllocate(true, 0.1 ether); rmm.allocate(true, 0.1 ether, deltaLiquidity, address(this)); assertEq(SY.balanceOf(address(this)), thisPreBalanceSY - deltaXWad); @@ -65,7 +65,7 @@ contract AllocateTest is SetUp { deal(address(SY), address(this), 1_000 ether); (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = - rmm.prepareAllocate(true, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + rmm.prepareAllocate(true, 0.1 ether); vm.expectEmit(true, true, true, true); @@ -77,7 +77,7 @@ contract AllocateTest is SetUp { deal(address(SY), address(this), 1_000 ether); (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = - rmm.prepareAllocate(true, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + rmm.prepareAllocate(true, 0.1 ether); vm.expectRevert( abi.encodeWithSelector( diff --git a/test/unit/Deallocate.t.sol b/test/unit/Deallocate.t.sol index b73e902..e65bd0b 100644 --- a/test/unit/Deallocate.t.sol +++ b/test/unit/Deallocate.t.sol @@ -9,7 +9,7 @@ import {InsufficientOutput} from "../../src/lib/RmmErrors.sol"; contract DeallocateTest is SetUp { function test_deallocate_BurnsLiquidity() public useDefaultPool { (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = - rmm.prepareAllocate(true, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + rmm.prepareAllocate(true, 0.1 ether); deltaLiquidity = rmm.allocate(true, 0.1 ether, deltaLiquidity, address(this)); @@ -23,7 +23,7 @@ contract DeallocateTest is SetUp { function test_deallocate_AdjustsPool() public useDefaultPool withSY(address(this), 1_000 ether) { (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = - rmm.prepareAllocate(true, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + rmm.prepareAllocate(true, 0.1 ether); deltaLiquidity = rmm.allocate(true, 0.1 ether, deltaLiquidity, address(this)); @@ -41,7 +41,7 @@ contract DeallocateTest is SetUp { function test_deallocate_TransfersTokens() public useDefaultPool withSY(address(this), 1_000 ether) { (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = - rmm.prepareAllocate(true, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + rmm.prepareAllocate(true, 0.1 ether); deltaLiquidity = rmm.allocate(true, 0.1 ether, deltaLiquidity, address(this)); uint256 lptBurned; @@ -61,7 +61,7 @@ contract DeallocateTest is SetUp { function test_deallocate_EmitsDeallocate() public useDefaultPool withSY(address(this), 1_000 ether) { (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = - rmm.prepareAllocate(true, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + rmm.prepareAllocate(true, 0.1 ether); deltaLiquidity = rmm.allocate(true, 0.1 ether, deltaLiquidity, address(this)); uint256 lptBurned; @@ -74,7 +74,7 @@ contract DeallocateTest is SetUp { function test_deallocate_RevertsIfInsufficientSYOutput() public useDefaultPool withSY(address(this), 1_000 ether) { (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = - rmm.prepareAllocate(true, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + rmm.prepareAllocate(true, 0.1 ether); deltaLiquidity = rmm.allocate(true, 0.1 ether, deltaLiquidity, address(this)); uint256 lptBurned; @@ -87,7 +87,7 @@ contract DeallocateTest is SetUp { function test_deallocate_RevertsIfInsufficientPTOutput() public useDefaultPool withSY(address(this), 1_000 ether) { (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = - rmm.prepareAllocate(true, 0.1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + rmm.prepareAllocate(true, 0.1 ether); deltaLiquidity = rmm.allocate(true, 0.1 ether, deltaLiquidity, address(this)); uint256 lptBurned; diff --git a/test/unit/SwapExactSyForYt.t.sol b/test/unit/SwapExactSyForYt.t.sol index eb19931..a8d9dd1 100644 --- a/test/unit/SwapExactSyForYt.t.sol +++ b/test/unit/SwapExactSyForYt.t.sol @@ -50,7 +50,7 @@ contract SwapExactSyForYtTest is SetUp { uint256 preTotalLiquidity = rmm.totalLiquidity(); uint256 exactSYIn = 1 ether; - uint256 ytOut = rmm.computeSYToYT(newIndex(), exactSYIn, 500 ether, block.timestamp, 0, 10_000); + uint256 ytOut = rmm.computeSYToYT(newIndex(), exactSYIn, 0, block.timestamp, 0, 10_000); (uint256 amountInWad, uint256 amountOutWad,, int256 deltaLiquidity,) = rmm.prepareSwapPtIn(ytOut, block.timestamp, newIndex()); rmm.swapExactSyForYt(exactSYIn, ytOut, ytOut.mulDivDown(95, 100), 500 ether, 10_000, address(this)); diff --git a/test/unit/SwapPt.sol b/test/unit/SwapPt.sol index 4aee75f..a0d63c0 100644 --- a/test/unit/SwapPt.sol +++ b/test/unit/SwapPt.sol @@ -9,7 +9,7 @@ import {InsufficientOutput} from "../../src/lib/RmmErrors.sol"; contract SwapPtTest is SetUp { function test_swapPt_AdjustsPool() public useDefaultPool withSY(address(this), 1_000 ether) { (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = - rmm.prepareAllocate(true, 1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + rmm.prepareAllocate(true, 1 ether); rmm.allocate(true, 1 ether, deltaLiquidity, address(this)); PYIndex index = PYIndex.wrap(YT.pyIndexCurrent()); @@ -27,7 +27,7 @@ contract SwapPtTest is SetUp { function test_swapPt_TransfersTokens() public useDefaultPool withSY(address(this), 1_000 ether) { (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = - rmm.prepareAllocate(true, 1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + rmm.prepareAllocate(true, 1 ether); rmm.allocate(true, 1 ether, deltaLiquidity, address(this)); PYIndex index = PYIndex.wrap(YT.pyIndexCurrent()); @@ -49,7 +49,7 @@ contract SwapPtTest is SetUp { function test_swapPt_EmitsSwap() public useDefaultPool withSY(address(this), 1_000 ether) { (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = - rmm.prepareAllocate(true, 1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + rmm.prepareAllocate(true, 1 ether); rmm.allocate(true, 1 ether, deltaLiquidity, address(this)); PYIndex index = PYIndex.wrap(YT.pyIndexCurrent()); @@ -63,7 +63,7 @@ contract SwapPtTest is SetUp { function test_swapPt_RevertsWhenInsufficientOutput() public useDefaultPool withSY(address(this), 1_000 ether) { (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = - rmm.prepareAllocate(true, 1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + rmm.prepareAllocate(true, 1 ether); rmm.allocate(true, 1 ether, deltaLiquidity, address(this)); PYIndex index = PYIndex.wrap(YT.pyIndexCurrent()); diff --git a/test/unit/SwapSy.sol b/test/unit/SwapSy.sol index 0d57b11..ffd38ce 100644 --- a/test/unit/SwapSy.sol +++ b/test/unit/SwapSy.sol @@ -9,7 +9,7 @@ import {InsufficientOutput} from "../../src/lib/RmmErrors.sol"; contract SwapSyTest is SetUp { function test_swapSy_AdjustsPool() public useDefaultPool withSY(address(this), 1_000 ether) { (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = - rmm.prepareAllocate(true, 1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + rmm.prepareAllocate(true, 1 ether); rmm.allocate(true, 1 ether, deltaLiquidity, address(this)); PYIndex index = PYIndex.wrap(YT.pyIndexCurrent()); @@ -27,7 +27,7 @@ contract SwapSyTest is SetUp { function test_swapSy_TransfersTokens() public useDefaultPool withSY(address(this), 1_000 ether) { (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = - rmm.prepareAllocate(true, 1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + rmm.prepareAllocate(true, 1 ether); rmm.allocate(true, 1 ether, deltaLiquidity, address(this)); PYIndex index = PYIndex.wrap(YT.pyIndexCurrent()); @@ -49,7 +49,7 @@ contract SwapSyTest is SetUp { function test_swapSy_EmitsSwap() public useDefaultPool withSY(address(this), 1_000 ether) { (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = - rmm.prepareAllocate(true, 1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + rmm.prepareAllocate(true, 1 ether); rmm.allocate(true, 1 ether, deltaLiquidity, address(this)); PYIndex index = PYIndex.wrap(YT.pyIndexCurrent()); @@ -63,7 +63,7 @@ contract SwapSyTest is SetUp { function test_swapSy_RevertsWhenInsufficientOutput() public useDefaultPool withSY(address(this), 1_000 ether) { (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = - rmm.prepareAllocate(true, 1 ether, PYIndex.wrap(YT.pyIndexCurrent())); + rmm.prepareAllocate(true, 1 ether); rmm.allocate(true, 1 ether, deltaLiquidity, address(this)); PYIndex index = PYIndex.wrap(YT.pyIndexCurrent()); From 7a307e9244aae78f439d0b69cbad14d907f68a3d Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 10 Jul 2024 15:08:12 +0400 Subject: [PATCH 093/116] test: fix test_swapExactSyForYt_AdjustsReserves --- test/unit/SwapExactSyForYt.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/SwapExactSyForYt.t.sol b/test/unit/SwapExactSyForYt.t.sol index a8d9dd1..eb19931 100644 --- a/test/unit/SwapExactSyForYt.t.sol +++ b/test/unit/SwapExactSyForYt.t.sol @@ -50,7 +50,7 @@ contract SwapExactSyForYtTest is SetUp { uint256 preTotalLiquidity = rmm.totalLiquidity(); uint256 exactSYIn = 1 ether; - uint256 ytOut = rmm.computeSYToYT(newIndex(), exactSYIn, 0, block.timestamp, 0, 10_000); + uint256 ytOut = rmm.computeSYToYT(newIndex(), exactSYIn, 500 ether, block.timestamp, 0, 10_000); (uint256 amountInWad, uint256 amountOutWad,, int256 deltaLiquidity,) = rmm.prepareSwapPtIn(ytOut, block.timestamp, newIndex()); rmm.swapExactSyForYt(exactSYIn, ytOut, ytOut.mulDivDown(95, 100), 500 ether, 10_000, address(this)); From f0c42a37d44ca1d97dd5303b2c4207e37b47bd4d Mon Sep 17 00:00:00 2001 From: kinrezc Date: Mon, 8 Jul 2024 14:58:22 -0400 Subject: [PATCH 094/116] wip --- src/RMM.sol | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/RMM.sol b/src/RMM.sol index 9370f9b..b780b12 100644 --- a/src/RMM.sol +++ b/src/RMM.sol @@ -6,6 +6,7 @@ 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"; @@ -483,7 +484,9 @@ contract RMM is ERC20 { uint256 epsilon ) public view returns (uint256 guess) { uint256 min = exactSYIn; + console2.log("here"); max = max > 0 ? max : calcMaxPtIn(reserveX, reserveY, totalLiquidity, strike); + console2.log("here2"); for (uint256 iter = 0; iter < 256; ++iter) { guess = initialGuess > 0 && iter == 0 ? initialGuess : (min + max) / 2; (,, uint256 amountOut,,) = prepareSwapPtIn(guess, blockTime, index); @@ -510,10 +513,12 @@ function calcMaxPtIn( uint256 strike_ ) internal pure returns (uint256) { uint256 low = 0; - uint256 high = type(uint256).max - reserveY_ - 1; + uint256 high = reserveY_ - 1; + console2.log("1"); while (low != high) { uint256 mid = (low + high + 1) / 2; + console2.log("2"); if (calcSlope(reserveX_, reserveY_, totalLiquidity_, strike_, int256(mid)) < 0) { high = mid - 1; } else { @@ -532,8 +537,14 @@ function calcSlope( uint256 strike_, int256 ptToMarket ) internal pure returns (int256) { + console2.log("reserveX_", reserveX_); + console2.log("reserveY_", reserveY_); + console2.log("totalLiquidity_", totalLiquidity_); + console2.log("strike_", strike_); + console2.log("ptToMarket", ptToMarket); uint256 newReserveY = reserveY_ + uint256(ptToMarket); uint256 b_i = newReserveY * 1e36 / (strike_ * totalLiquidity_); + console2.log("3"); int256 b = Gaussian.ppf(toInt(b_i)); int256 pdf_b = Gaussian.pdf(b); @@ -557,6 +568,7 @@ function computedXdY( // uint256 tau ) internal pure returns (int256) { // This is a placeholder. You'll need to implement this based on your RMM model + console2.log("3"); return -int256(reserveX_) * 1e18 / int256(reserveY_); } From 826998317939bb35249a26bcef562b971e653a98 Mon Sep 17 00:00:00 2001 From: kinrezc Date: Wed, 10 Jul 2024 12:44:24 -0400 Subject: [PATCH 095/116] working upperbound for sy -> yt --- src/RMM.sol | 123 +++++++++++++++---------------- test/invariant/RMMHandler.sol | 6 +- test/unit/SwapExactSyForYt.t.sol | 18 ++--- 3 files changed, 72 insertions(+), 75 deletions(-) diff --git a/src/RMM.sol b/src/RMM.sol index b780b12..1080a64 100644 --- a/src/RMM.sol +++ b/src/RMM.sol @@ -484,9 +484,7 @@ contract RMM is ERC20 { uint256 epsilon ) public view returns (uint256 guess) { uint256 min = exactSYIn; - console2.log("here"); - max = max > 0 ? max : calcMaxPtIn(reserveX, reserveY, totalLiquidity, strike); - console2.log("here2"); + max = max > 0 ? max : calcMaxPtIn(index.syToAsset(reserveX), reserveY, totalLiquidity, strike); for (uint256 iter = 0; iter < 256; ++iter) { guess = initialGuess > 0 && iter == 0 ? initialGuess : (min + max) / 2; (,, uint256 amountOut,,) = prepareSwapPtIn(guess, blockTime, index); @@ -504,71 +502,70 @@ contract RMM is ERC20 { } } - // In RMMLib.sol - -function calcMaxPtIn( - uint256 reserveX_, - uint256 reserveY_, - uint256 totalLiquidity_, - uint256 strike_ -) internal pure returns (uint256) { - uint256 low = 0; - uint256 high = reserveY_ - 1; - console2.log("1"); - - while (low != high) { - uint256 mid = (low + high + 1) / 2; - console2.log("2"); - if (calcSlope(reserveX_, reserveY_, totalLiquidity_, strike_, int256(mid)) < 0) { - high = mid - 1; - } else { - low = mid; + function calcMaxPtOut( + uint256 reserveX_, + uint256 reserveY_, + uint256 totalLiquidity_, + uint256 strike_, + uint256 sigma_, + uint256 tau_ + ) internal pure returns (uint256) { + int256 currentTF = computeTradingFunction(reserveX_, reserveY_, totalLiquidity_, strike_, sigma_, tau_); + + uint256 maxProportion = uint256(int256(1e18) - currentTF) * 1e18 / (2 * 1e18); + + uint256 maxPtOut = reserveY_ * maxProportion / 1e18; + + return (maxPtOut * 999) / 1000; + } + + function calcMaxPtIn( + uint256 reserveX_, + uint256 reserveY_, + uint256 totalLiquidity_, + uint256 strike_ + ) internal pure returns (uint256) { + uint256 low = 0; + uint256 high = reserveY_ - 1; + + while (low != high) { + uint256 mid = (low + high + 1) / 2; + if (calcSlope(reserveX_, reserveY_, totalLiquidity_, strike_, int256(mid)) < 0) { + high = mid - 1; + } else { + low = mid; + } } + + return low; } - // Return 99.9% of the calculated max to account for potential precision issues - return (low * 999) / 1000; -} -function calcSlope( - uint256 reserveX_, - uint256 reserveY_, - uint256 totalLiquidity_, - uint256 strike_, - int256 ptToMarket -) internal pure returns (int256) { - console2.log("reserveX_", reserveX_); - console2.log("reserveY_", reserveY_); - console2.log("totalLiquidity_", totalLiquidity_); - console2.log("strike_", strike_); - console2.log("ptToMarket", ptToMarket); - uint256 newReserveY = reserveY_ + uint256(ptToMarket); - uint256 b_i = newReserveY * 1e36 / (strike_ * totalLiquidity_); - console2.log("3"); - - int256 b = Gaussian.ppf(toInt(b_i)); - int256 pdf_b = Gaussian.pdf(b); - - // Calculate the slope - int256 slope = int256(1e36) / (int256(strike_ * totalLiquidity_) * pdf_b / 1e18); - - // Adjust for the relationship between X and Y - int256 dXdY = computedXdY(reserveX_, newReserveY); - - // Combine the direct Y effect and the indirect X effect - return slope + dXdY; -} -function computedXdY( - uint256 reserveX_, - uint256 reserveY_ - // uint256 totalLiquidity_, - // uint256 strike, - // uint256 sigma, - // uint256 tau -) internal pure returns (int256) { - // This is a placeholder. You'll need to implement this based on your RMM model - console2.log("3"); + function calcSlope( + uint256 reserveX_, + uint256 reserveY_, + uint256 totalLiquidity_, + uint256 strike_, + int256 ptToMarket + ) internal pure returns (int256) { + uint256 newReserveY = reserveY_ + uint256(ptToMarket); + uint256 b_i = newReserveY * 1e36 / (strike_ * totalLiquidity_); + + int256 b = Gaussian.ppf(toInt(b_i)); + int256 pdf_b = Gaussian.pdf(b); + + int256 slope = (int256(strike_ * totalLiquidity_) * pdf_b / 1e36); + + int256 dxdy = computedXdY(reserveX_, newReserveY); + + return slope + dxdy; + } + + function computedXdY( + uint256 reserveX_, + uint256 reserveY_ + ) internal pure returns (int256) { return -int256(reserveX_) * 1e18 / int256(reserveY_); } diff --git a/test/invariant/RMMHandler.sol b/test/invariant/RMMHandler.sol index ac4db24..86a1c7b 100644 --- a/test/invariant/RMMHandler.sol +++ b/test/invariant/RMMHandler.sol @@ -170,11 +170,11 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { PYIndex index = YT.newIndex(); (uint256 syMinted, uint256 ytOut) = rmm.computeTokenToYT( - index, address(weth), amountTokenIn, rmm.reserveX().mulDivDown(95, 100), block.timestamp, 0, 1_000 + index, address(weth), amountTokenIn, 0, block.timestamp, 0, 1_000 ); uint256 amountPtIn = - rmm.computeSYToYT(index, syMinted, rmm.reserveX().mulDivDown(95, 100), block.timestamp, ytOut, 0.005 ether); + rmm.computeSYToYT(index, syMinted, 0, block.timestamp, ytOut, 0.005 ether); (uint256 amountInWad, uint256 amountOutWad,, int256 deltaLiquidity,) = rmm.prepareSwapPtIn(amountPtIn, block.timestamp, index); @@ -184,7 +184,7 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { amountTokenIn, ytOut, syMinted, - ytOut.mulDivDown(99, 100), + ytOut, 10 ether, 0.005 ether, address(currentActor) diff --git a/test/unit/SwapExactSyForYt.t.sol b/test/unit/SwapExactSyForYt.t.sol index eb19931..24e472e 100644 --- a/test/unit/SwapExactSyForYt.t.sol +++ b/test/unit/SwapExactSyForYt.t.sol @@ -32,11 +32,11 @@ contract SwapExactSyForYtTest is SetUp { preBalances[1] = ERC20(address(YT)).balanceOf(state.to); preBalances[2] = ERC20(address(SY)).balanceOf(address(rmm)); - state.ytOut = rmm.computeSYToYT(state.index, state.exactSYIn, 500 ether, block.timestamp, 0, 10_000); + state.ytOut = rmm.computeSYToYT(state.index, state.exactSYIn, 0, block.timestamp, 0, 10_000); (uint256 amountInWad, uint256 amountOutWad,,,) = rmm.prepareSwapPtIn(state.ytOut, block.timestamp, state.index); state.delta = amountInWad - amountOutWad; (uint256 amtOut,) = rmm.swapExactSyForYt( - state.exactSYIn, state.ytOut, state.ytOut.mulDivDown(95, 100), 500 ether, 10_000, state.to + state.exactSYIn, state.ytOut, state.ytOut.mulDivDown(95, 100), state.ytOut, 10_000, state.to ); assertEq(ERC20(address(SY)).balanceOf(address(this)), preBalances[0] - state.delta); @@ -53,7 +53,7 @@ contract SwapExactSyForYtTest is SetUp { uint256 ytOut = rmm.computeSYToYT(newIndex(), exactSYIn, 500 ether, block.timestamp, 0, 10_000); (uint256 amountInWad, uint256 amountOutWad,, int256 deltaLiquidity,) = rmm.prepareSwapPtIn(ytOut, block.timestamp, newIndex()); - rmm.swapExactSyForYt(exactSYIn, ytOut, ytOut.mulDivDown(95, 100), 500 ether, 10_000, address(this)); + rmm.swapExactSyForYt(exactSYIn, ytOut, ytOut.mulDivDown(95, 100), ytOut, 10_000, address(this)); assertEq(rmm.reserveX(), preReserveX - amountOutWad); assertEq(rmm.reserveY(), preReserveY + amountInWad); @@ -62,31 +62,31 @@ contract SwapExactSyForYtTest is SetUp { function test_swapExactSyForYt_EmitsEvent() public useSYPool withSY(address(this), 10 ether) { uint256 exactSYIn = 1 ether; - uint256 ytOut = rmm.computeSYToYT(newIndex(), exactSYIn, 500 ether, block.timestamp, 0, 10_000); + uint256 ytOut = rmm.computeSYToYT(newIndex(), exactSYIn, 0, block.timestamp, 0, 10_000); (uint256 amountInWad, uint256 amountOutWad, uint256 amountOut, int256 deltaLiquidity,) = rmm.prepareSwapPtIn(ytOut, block.timestamp, newIndex()); uint256 delta = assetToSyUp(amountInWad) - amountOutWad; vm.expectEmit(); emit Swap(address(this), address(0xbeef), address(SY), address(YT), delta, amountOut, deltaLiquidity); - rmm.swapExactSyForYt(exactSYIn, ytOut, ytOut.mulDivDown(95, 100), 500 ether, 10_000, address(0xbeef)); + rmm.swapExactSyForYt(exactSYIn, ytOut, ytOut.mulDivDown(95, 100), ytOut, 10_000, address(0xbeef)); } function test_swapExactSyForYt_RevertsWhenExcessInput() public useSYPool withSY(address(this), 10 ether) { uint256 exactSYIn = 1 ether; - uint256 ytOut = rmm.computeSYToYT(newIndex(), exactSYIn, 500 ether, block.timestamp, 0, 10_000); + uint256 ytOut = rmm.computeSYToYT(newIndex(), exactSYIn, 0, block.timestamp, 0, 10_000); (, uint256 amountOutWad,,,) = rmm.prepareSwapPtIn(ytOut, block.timestamp, newIndex()); vm.expectRevert(); - rmm.swapExactSyForYt(exactSYIn - 1 ether, ytOut, amountOutWad, 500 ether, 10_000, address(this)); + rmm.swapExactSyForYt(exactSYIn - 1 ether, ytOut, amountOutWad, ytOut, 10_000, address(this)); } function test_swapExactSyForYt_RevertsWhenInsufficientOutput() public useSYPool withSY(address(this), 10 ether) { uint256 exactSYIn = 1 ether; - uint256 ytOut = rmm.computeSYToYT(newIndex(), exactSYIn, 500 ether, block.timestamp, 0, 10_000); + uint256 ytOut = rmm.computeSYToYT(newIndex(), exactSYIn, 0, block.timestamp, 0, 10_000); (, uint256 amountOutWad,,,) = rmm.prepareSwapPtIn(ytOut, block.timestamp, newIndex()); vm.expectRevert(); - rmm.swapExactSyForYt(exactSYIn, ytOut, amountOutWad + 1 ether, 500 ether, 10_000, address(this)); + rmm.swapExactSyForYt(exactSYIn, ytOut, amountOutWad + 1 ether, ytOut, 10_000, address(this)); } } From 7aaae18b7af391b71fbc328be327b34dc70454f7 Mon Sep 17 00:00:00 2001 From: kinrezc Date: Wed, 10 Jul 2024 15:43:28 -0400 Subject: [PATCH 096/116] wip invariant tests almost there --- src/RMM.sol | 14 ++++++++++++- test/invariant/RMMHandler.sol | 31 ++++++++++++++++------------- test/invariant/RMMInvariants.t.sol | 14 ++++++------- test/unit/SwapExactSyForYt.t.sol | 2 +- test/unit/SwapExactTokenForYt.t.sol | 10 +++++----- 5 files changed, 43 insertions(+), 28 deletions(-) diff --git a/src/RMM.sol b/src/RMM.sol index 1080a64..1670a8e 100644 --- a/src/RMM.sol +++ b/src/RMM.sol @@ -184,7 +184,7 @@ contract RMM is ERC20 { uint256 upperBound, uint256 epsilon, address to - ) external payable lock returns (uint256 amountOut, int256 deltaLiquidity) { + ) external payable lock returns (uint256 syIn, uint256 amountOut, int256 deltaLiquidity) { SwapToYt memory swap; swap.tokenIn = token; swap.amountTokenIn = token == address(0) ? 0 : amountTokenIn; @@ -194,6 +194,7 @@ contract RMM is ERC20 { swap.minYtOut = minYtOut; swap.to = to; swap.realSyMinted = _mintSYFromNativeAndToken(address(this), swap.tokenIn, swap.amountTokenIn, swap.minSyMinted); + syIn = swap.realSyMinted; PYIndex index = YT.newIndex(); uint256 amountInWad; @@ -484,7 +485,13 @@ contract RMM is ERC20 { uint256 epsilon ) public view returns (uint256 guess) { uint256 min = exactSYIn; + console2.log("here1"); + console2.log("reserveX", reserveX); + console2.log("reserveY", reserveY); + console2.log("totalLiquidity", totalLiquidity); + console2.log("strike", strike); max = max > 0 ? max : calcMaxPtIn(index.syToAsset(reserveX), reserveY, totalLiquidity, strike); + console2.log("here2"); for (uint256 iter = 0; iter < 256; ++iter) { guess = initialGuess > 0 && iter == 0 ? initialGuess : (min + max) / 2; (,, uint256 amountOut,,) = prepareSwapPtIn(guess, blockTime, index); @@ -530,6 +537,7 @@ contract RMM is ERC20 { while (low != high) { uint256 mid = (low + high + 1) / 2; + console2.log("mid", mid); if (calcSlope(reserveX_, reserveY_, totalLiquidity_, strike_, int256(mid)) < 0) { high = mid - 1; } else { @@ -551,6 +559,10 @@ contract RMM is ERC20 { ) internal pure returns (int256) { uint256 newReserveY = reserveY_ + uint256(ptToMarket); uint256 b_i = newReserveY * 1e36 / (strike_ * totalLiquidity_); + + if (b_i > 1e18) { + return -1; + } int256 b = Gaussian.ppf(toInt(b_i)); int256 pdf_b = Gaussian.pdf(b); diff --git a/test/invariant/RMMHandler.sol b/test/invariant/RMMHandler.sol index 86a1c7b..dfcd7e2 100644 --- a/test/invariant/RMMHandler.sol +++ b/test/invariant/RMMHandler.sol @@ -88,7 +88,7 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { priceX = bound(priceX, 1.05 ether, 1.15 ether); amountX = bound(amountX, 100 ether, 1000 ether); strike = bound(strike, 1.05 ether, 1.15 ether); - sigma = bound(sigma, 0.02 ether, 0.05 ether); + sigma = bound(sigma, 0.03 ether, 0.05 ether); fee = bound(fee, 0.0001 ether, 0.001 ether); PYIndex index = IPYieldToken(PT.YT()).newIndex(); @@ -143,20 +143,28 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { uint256 exactSYIn = 1 ether; deal(address(SY), address(currentActor), exactSYIn); - uint256 rX = rmm.reserveX(); - PYIndex index = YT.newIndex(); - uint256 ytOut = rmm.computeSYToYT(index, exactSYIn, rX.mulDivDown(95, 100), block.timestamp, 0, 10_000); + uint256 ytOut = rmm.computeSYToYT(index, exactSYIn, 0, block.timestamp, 0, 1_000); vm.startPrank(currentActor); SY.approve(address(rmm), exactSYIn); (uint256 amountInWad, uint256 amountOutWad,, int256 deltaLiquidity,) = rmm.prepareSwapPtIn(ytOut, block.timestamp, index); - rmm.swapExactSyForYt(exactSYIn, ytOut, ytOut.mulDivDown(95, 100), 500 ether, 10_000, address(msg.sender)); + rmm.swapExactSyForYt(exactSYIn, ytOut, ytOut.mulDivDown(95, 100), ytOut, 1_000, address(msg.sender)); vm.stopPrank(); - + console2.log("h"); + console2.log("ghost_reserveX", ghost_reserveX); + console2.log("rx", rmm.reserveX()); + console2.log("ghost_reserveY", ghost_reserveY); + console2.log("ry", rmm.reserveY()); + console2.log("ghost_totalLiquidity", ghost_totalLiquidity); + console2.log("amountOutWad", amountOutWad); + console2.log("amountInWad", amountInWad); + console2.log("deltaLiquidity", deltaLiquidity); ghost_reserveX -= amountOutWad; + console2.log("h1"); ghost_reserveY += amountInWad; + console2.log("h2"); ghost_totalLiquidity += int256(deltaLiquidity); } @@ -173,13 +181,8 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { index, address(weth), amountTokenIn, 0, block.timestamp, 0, 1_000 ); - uint256 amountPtIn = - rmm.computeSYToYT(index, syMinted, 0, block.timestamp, ytOut, 0.005 ether); - (uint256 amountInWad, uint256 amountOutWad,, int256 deltaLiquidity,) = - rmm.prepareSwapPtIn(amountPtIn, block.timestamp, index); - weth.approve(address(rmm), amountTokenIn); - rmm.swapExactTokenForYt( + (uint256 amountInWad, uint256 amountOutWad, int256 deltaLiquidity) = rmm.swapExactTokenForYt( address(weth), amountTokenIn, ytOut, @@ -194,7 +197,7 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { ghost_reserveX -= amountOutWad; ghost_reserveY += amountInWad; - ghost_totalLiquidity += int256(deltaLiquidity); + ghost_totalLiquidity += deltaLiquidity; } function swapExactPtForSy() public createActor countCall(this.swapExactPtForSy.selector) { @@ -229,7 +232,7 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { deal(address(YT), currentActor, ytIn); vm.startPrank(currentActor); YT.approve(address(rmm), ytIn); - console2.log("rYPre", rmm.reserveY()); + console2.log("YT Balance", YT.balanceOf(currentActor)); (uint256 amountOut, uint256 amountIn, int256 deltaLiquidity) = rmm.swapExactYtForSy(ytIn, 1000 ether, address(currentActor)); vm.stopPrank(); diff --git a/test/invariant/RMMInvariants.t.sol b/test/invariant/RMMInvariants.t.sol index 4f72d35..d29f80f 100644 --- a/test/invariant/RMMInvariants.t.sol +++ b/test/invariant/RMMInvariants.t.sol @@ -22,14 +22,14 @@ contract RMMInvariantsTest is SetUp { handler.init(); - bytes4[] memory selectors = new bytes4[](7); + bytes4[] memory selectors = new bytes4[](5); selectors[0] = RMMHandler.allocate.selector; selectors[1] = RMMHandler.deallocate.selector; selectors[2] = RMMHandler.swapExactSyForYt.selector; selectors[3] = RMMHandler.swapExactPtForSy.selector; selectors[4] = RMMHandler.swapExactSyForPt.selector; - selectors[5] = RMMHandler.swapExactYtForSy.selector; - selectors[6] = RMMHandler.swapExactTokenForYt.selector; + // selectors[5] = RMMHandler.swapExactYtForSy.selector; + // selectors[6] = RMMHandler.swapExactTokenForYt.selector; targetSelector(FuzzSelector({addr: address(handler), selectors: selectors})); targetContract(address(handler)); @@ -47,21 +47,21 @@ contract RMMInvariantsTest is SetUp { } /// forge-config: default.invariant.runs = 10 - /// forge-config: default.invariant.depth = 100 + /// forge-config: default.invariant.depth = 10 /// forge-config: default.invariant.fail-on-revert = true function invariant_TradingFunction() public { - assertTrue(abs(rmm.tradingFunction(newIndex())) <= 100, "Invariant out of valid range"); + assertTrue(abs(rmm.tradingFunction(newIndex())) <= 10, "Invariant out of valid range"); } /// forge-config: default.invariant.runs = 10 - /// forge-config: default.invariant.depth = 100 + /// forge-config: default.invariant.depth = 10 /// forge-config: default.invariant.fail-on-revert = true function invariant_ReserveX() public view { assertEq(rmm.reserveX(), handler.ghost_reserveX()); } /// forge-config: default.invariant.runs = 10 - /// forge-config: default.invariant.depth = 100 + /// forge-config: default.invariant.depth = 10 /// forge-config: default.invariant.fail-on-revert = true function invariant_ReserveY() public view { assertEq(rmm.reserveY(), handler.ghost_reserveY()); diff --git a/test/unit/SwapExactSyForYt.t.sol b/test/unit/SwapExactSyForYt.t.sol index 24e472e..2065e0f 100644 --- a/test/unit/SwapExactSyForYt.t.sol +++ b/test/unit/SwapExactSyForYt.t.sol @@ -50,7 +50,7 @@ contract SwapExactSyForYtTest is SetUp { uint256 preTotalLiquidity = rmm.totalLiquidity(); uint256 exactSYIn = 1 ether; - uint256 ytOut = rmm.computeSYToYT(newIndex(), exactSYIn, 500 ether, block.timestamp, 0, 10_000); + uint256 ytOut = rmm.computeSYToYT(newIndex(), exactSYIn, 0, block.timestamp, 0, 10_000); (uint256 amountInWad, uint256 amountOutWad,, int256 deltaLiquidity,) = rmm.prepareSwapPtIn(ytOut, block.timestamp, newIndex()); rmm.swapExactSyForYt(exactSYIn, ytOut, ytOut.mulDivDown(95, 100), ytOut, 10_000, address(this)); diff --git a/test/unit/SwapExactTokenForYt.t.sol b/test/unit/SwapExactTokenForYt.t.sol index 830adf2..4fd3c93 100644 --- a/test/unit/SwapExactTokenForYt.t.sol +++ b/test/unit/SwapExactTokenForYt.t.sol @@ -14,9 +14,9 @@ contract SwapExactTokenForYtTest is SetUp { uint256 amountIn = 1 ether; PYIndex index = YT.newIndex(); (uint256 syMinted, uint256 ytOut) = - rmm.computeTokenToYT(index, address(weth), amountIn, 500 ether, block.timestamp, 0, 1_000); + rmm.computeTokenToYT(index, address(weth), amountIn, 0, block.timestamp, 0, 1_000); rmm.swapExactTokenForYt( - address(weth), amountIn, ytOut, syMinted, ytOut.mulDivDown(99, 100), 500 ether, 0.005 ether, address(this) + address(weth), amountIn, ytOut, syMinted, ytOut, 0, 0.005 ether, address(this) ); } @@ -27,9 +27,9 @@ contract SwapExactTokenForYtTest is SetUp { uint256 amountIn = 1 ether; PYIndex index = YT.newIndex(); (uint256 syMinted, uint256 ytOut) = - rmm.computeTokenToYT(index, address(0), amountIn, 500 ether, block.timestamp, 0, 1_000); - (uint256 amountOut,) = rmm.swapExactTokenForYt{value: amountIn}( - address(0), amountIn, ytOut, syMinted, ytOut.mulDivDown(99, 100), 500 ether, 0.005 ether, address(this) + rmm.computeTokenToYT(index, address(0), amountIn, 0, block.timestamp, 0, 1_000); + (,uint256 amountOut,) = rmm.swapExactTokenForYt{value: amountIn}( + address(0), amountIn, ytOut, syMinted, ytOut, 0, 0.005 ether, address(this) ); assertEq(address(this).balance, preETHBalance - amountIn); From 63865ed41e252eaf38345e9645604c7c9774c692 Mon Sep 17 00:00:00 2001 From: kinrezc Date: Wed, 10 Jul 2024 16:17:47 -0400 Subject: [PATCH 097/116] fix initialization of ghost reserves --- src/RMM.sol | 2 +- test/invariant/RMMHandler.sol | 29 +++++++++++------------------ test/invariant/RMMInvariants.t.sol | 14 +++++++------- 3 files changed, 19 insertions(+), 26 deletions(-) diff --git a/src/RMM.sol b/src/RMM.sol index 1670a8e..9fecbc9 100644 --- a/src/RMM.sol +++ b/src/RMM.sol @@ -58,7 +58,7 @@ contract RMM is ERC20 { _; int256 terminal = tradingFunction(index); - if (abs(terminal) > 10) { + if (abs(terminal) > 100) { revert OutOfRange(terminal); } } diff --git a/test/invariant/RMMHandler.sol b/test/invariant/RMMHandler.sol index dfcd7e2..445ed1a 100644 --- a/test/invariant/RMMHandler.sol +++ b/test/invariant/RMMHandler.sol @@ -93,7 +93,7 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { PYIndex index = IPYieldToken(PT.YT()).newIndex(); - (uint256 totalLiquidity, uint256 amountY) = rmm.prepareInit(priceX, amountX, strike, sigma, index); + (uint256 totalLiquidity,) = rmm.prepareInit(priceX, amountX, strike, sigma, index); PT.approve(address(rmm), type(uint256).max); SY.approve(address(rmm), type(uint256).max); @@ -101,8 +101,9 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { rmm.init(priceX, amountX, strike); - ghost_reserveX += amountX; - ghost_reserveY += amountY; + + ghost_reserveX += rmm.reserveX(); + ghost_reserveY += rmm.reserveY(); ghost_totalLiquidity += int256(totalLiquidity); } @@ -131,11 +132,10 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { function deallocate(uint256 actorSeed) public useActor(actorSeed) countCall(this.deallocate.selector) { uint256 deltaLiquidity = rmm.totalLiquidity() * rmm.balanceOf(currentActor) / rmm.totalSupply(); - (uint256 deltaXWad, uint256 deltaYWad,) = rmm.prepareDeallocate(deltaLiquidity); - rmm.deallocate(deltaLiquidity, 0, 0, address(this)); + (uint256 deltaX, uint256 deltaY) = rmm.deallocate(deltaLiquidity, 0, 0, address(this)); - ghost_reserveX -= deltaXWad; - ghost_reserveY -= deltaYWad; + ghost_reserveX -= deltaX; + ghost_reserveY -= deltaY; ghost_totalLiquidity -= int256(deltaLiquidity); } @@ -152,19 +152,9 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { rmm.prepareSwapPtIn(ytOut, block.timestamp, index); rmm.swapExactSyForYt(exactSYIn, ytOut, ytOut.mulDivDown(95, 100), ytOut, 1_000, address(msg.sender)); vm.stopPrank(); - console2.log("h"); - console2.log("ghost_reserveX", ghost_reserveX); - console2.log("rx", rmm.reserveX()); - console2.log("ghost_reserveY", ghost_reserveY); - console2.log("ry", rmm.reserveY()); - console2.log("ghost_totalLiquidity", ghost_totalLiquidity); - console2.log("amountOutWad", amountOutWad); - console2.log("amountInWad", amountInWad); - console2.log("deltaLiquidity", deltaLiquidity); + ghost_reserveX -= amountOutWad; - console2.log("h1"); ghost_reserveY += amountInWad; - console2.log("h2"); ghost_totalLiquidity += int256(deltaLiquidity); } @@ -220,6 +210,9 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { SY.approve(address(rmm), amountIn); (uint256 amountOut, int256 deltaLiquidity) = rmm.swapExactSyForPt(amountIn, 0, address(currentActor)); vm.stopPrank(); + + console2.log("ry", rmm.reserveY()); + console2.log("ry - amountOut", rmm.reserveY() - amountOut); ghost_reserveX += amountIn; ghost_reserveY -= amountOut; diff --git a/test/invariant/RMMInvariants.t.sol b/test/invariant/RMMInvariants.t.sol index d29f80f..ec79bd9 100644 --- a/test/invariant/RMMInvariants.t.sol +++ b/test/invariant/RMMInvariants.t.sol @@ -22,12 +22,12 @@ contract RMMInvariantsTest is SetUp { handler.init(); - bytes4[] memory selectors = new bytes4[](5); - selectors[0] = RMMHandler.allocate.selector; - selectors[1] = RMMHandler.deallocate.selector; - selectors[2] = RMMHandler.swapExactSyForYt.selector; - selectors[3] = RMMHandler.swapExactPtForSy.selector; - selectors[4] = RMMHandler.swapExactSyForPt.selector; + bytes4[] memory selectors = new bytes4[](1); + // selectors[0] = RMMHandler.allocate.selector; + selectors[0] = RMMHandler.deallocate.selector; + // selectors[2] = RMMHandler.swapExactSyForYt.selector; + // selectors[3] = RMMHandler.swapExactPtForSy.selector; + // selectors[4] = RMMHandler.swapExactSyForPt.selector; // selectors[5] = RMMHandler.swapExactYtForSy.selector; // selectors[6] = RMMHandler.swapExactTokenForYt.selector; @@ -50,7 +50,7 @@ contract RMMInvariantsTest is SetUp { /// forge-config: default.invariant.depth = 10 /// forge-config: default.invariant.fail-on-revert = true function invariant_TradingFunction() public { - assertTrue(abs(rmm.tradingFunction(newIndex())) <= 10, "Invariant out of valid range"); + assertTrue(abs(rmm.tradingFunction(newIndex())) <= 100, "Invariant out of valid range"); } /// forge-config: default.invariant.runs = 10 From f368afebe8b474b7b0d5dffe5d8475869c9c1a82 Mon Sep 17 00:00:00 2001 From: kinrezc Date: Wed, 10 Jul 2024 16:20:05 -0400 Subject: [PATCH 098/116] fix invariant test runs --- test/invariant/RMMInvariants.t.sol | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/invariant/RMMInvariants.t.sol b/test/invariant/RMMInvariants.t.sol index ec79bd9..677f3ad 100644 --- a/test/invariant/RMMInvariants.t.sol +++ b/test/invariant/RMMInvariants.t.sol @@ -22,10 +22,10 @@ contract RMMInvariantsTest is SetUp { handler.init(); - bytes4[] memory selectors = new bytes4[](1); - // selectors[0] = RMMHandler.allocate.selector; - selectors[0] = RMMHandler.deallocate.selector; - // selectors[2] = RMMHandler.swapExactSyForYt.selector; + bytes4[] memory selectors = new bytes4[](3); + selectors[0] = RMMHandler.allocate.selector; + selectors[1] = RMMHandler.deallocate.selector; + selectors[2] = RMMHandler.swapExactSyForYt.selector; // selectors[3] = RMMHandler.swapExactPtForSy.selector; // selectors[4] = RMMHandler.swapExactSyForPt.selector; // selectors[5] = RMMHandler.swapExactYtForSy.selector; @@ -47,21 +47,21 @@ contract RMMInvariantsTest is SetUp { } /// forge-config: default.invariant.runs = 10 - /// forge-config: default.invariant.depth = 10 + /// forge-config: default.invariant.depth = 10 /// forge-config: default.invariant.fail-on-revert = true function invariant_TradingFunction() public { assertTrue(abs(rmm.tradingFunction(newIndex())) <= 100, "Invariant out of valid range"); } /// forge-config: default.invariant.runs = 10 - /// forge-config: default.invariant.depth = 10 + /// forge-config: default.invariant.depth = 10 /// forge-config: default.invariant.fail-on-revert = true function invariant_ReserveX() public view { assertEq(rmm.reserveX(), handler.ghost_reserveX()); } /// forge-config: default.invariant.runs = 10 - /// forge-config: default.invariant.depth = 10 + /// forge-config: default.invariant.depth = 10 /// forge-config: default.invariant.fail-on-revert = true function invariant_ReserveY() public view { assertEq(rmm.reserveY(), handler.ghost_reserveY()); From c75f2a7ddc3188cf1d9da2230754c8e72c0ce25b Mon Sep 17 00:00:00 2001 From: kinrezc Date: Wed, 10 Jul 2024 16:22:04 -0400 Subject: [PATCH 099/116] invariant tests working except for yt->sy and token->yt --- test/invariant/RMMInvariants.t.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/invariant/RMMInvariants.t.sol b/test/invariant/RMMInvariants.t.sol index 677f3ad..d73da22 100644 --- a/test/invariant/RMMInvariants.t.sol +++ b/test/invariant/RMMInvariants.t.sol @@ -22,12 +22,12 @@ contract RMMInvariantsTest is SetUp { handler.init(); - bytes4[] memory selectors = new bytes4[](3); + bytes4[] memory selectors = new bytes4[](5); selectors[0] = RMMHandler.allocate.selector; selectors[1] = RMMHandler.deallocate.selector; selectors[2] = RMMHandler.swapExactSyForYt.selector; - // selectors[3] = RMMHandler.swapExactPtForSy.selector; - // selectors[4] = RMMHandler.swapExactSyForPt.selector; + selectors[3] = RMMHandler.swapExactPtForSy.selector; + selectors[4] = RMMHandler.swapExactSyForPt.selector; // selectors[5] = RMMHandler.swapExactYtForSy.selector; // selectors[6] = RMMHandler.swapExactTokenForYt.selector; From 8f72ce3c674390fe30c6c247194fa008dbda89d1 Mon Sep 17 00:00:00 2001 From: kinrezc Date: Wed, 10 Jul 2024 16:39:04 -0400 Subject: [PATCH 100/116] increase initial reserves so that call depth can go higher --- test/invariant/RMMHandler.sol | 2 +- test/invariant/RMMInvariants.t.sol | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/invariant/RMMHandler.sol b/test/invariant/RMMHandler.sol index 445ed1a..f9ba9bd 100644 --- a/test/invariant/RMMHandler.sol +++ b/test/invariant/RMMHandler.sol @@ -86,7 +86,7 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { uint256 fee; priceX = bound(priceX, 1.05 ether, 1.15 ether); - amountX = bound(amountX, 100 ether, 1000 ether); + amountX = bound(amountX, 500 ether, 1000 ether); strike = bound(strike, 1.05 ether, 1.15 ether); sigma = bound(sigma, 0.03 ether, 0.05 ether); fee = bound(fee, 0.0001 ether, 0.001 ether); diff --git a/test/invariant/RMMInvariants.t.sol b/test/invariant/RMMInvariants.t.sol index d73da22..29f6e00 100644 --- a/test/invariant/RMMInvariants.t.sol +++ b/test/invariant/RMMInvariants.t.sol @@ -47,21 +47,21 @@ contract RMMInvariantsTest is SetUp { } /// forge-config: default.invariant.runs = 10 - /// forge-config: default.invariant.depth = 10 + /// forge-config: default.invariant.depth = 100 /// forge-config: default.invariant.fail-on-revert = true function invariant_TradingFunction() public { assertTrue(abs(rmm.tradingFunction(newIndex())) <= 100, "Invariant out of valid range"); } /// forge-config: default.invariant.runs = 10 - /// forge-config: default.invariant.depth = 10 + /// forge-config: default.invariant.depth = 100 /// forge-config: default.invariant.fail-on-revert = true function invariant_ReserveX() public view { assertEq(rmm.reserveX(), handler.ghost_reserveX()); } /// forge-config: default.invariant.runs = 10 - /// forge-config: default.invariant.depth = 10 + /// forge-config: default.invariant.depth = 100 /// forge-config: default.invariant.fail-on-revert = true function invariant_ReserveY() public view { assertEq(rmm.reserveY(), handler.ghost_reserveY()); From 58f94ee253b61f48d7e4922763a9f3743b9b6464 Mon Sep 17 00:00:00 2001 From: kinrezc Date: Thu, 11 Jul 2024 12:47:53 -0400 Subject: [PATCH 101/116] fix yt -> sy invariant tests --- src/RMM.sol | 7 +------ test/invariant/RMMHandler.sol | 11 +++-------- test/invariant/RMMInvariants.t.sol | 20 ++++++++++---------- 3 files changed, 14 insertions(+), 24 deletions(-) diff --git a/src/RMM.sol b/src/RMM.sol index 9fecbc9..f24dfd9 100644 --- a/src/RMM.sol +++ b/src/RMM.sol @@ -310,6 +310,7 @@ contract RMM is ERC20 { (uint256 creditNative) = _debit(address(YT), ytIn); uint256 amountSy = redeemPy(ytIn, address(this)); amountOut = amountSy - amountInWad; + amountIn = amountInWad; (uint256 debitNative) = _credit(address(SY), to, amountOut); emit Swap(msg.sender, to, address(PT), address(SY), debitNative, creditNative, deltaLiquidity); @@ -485,13 +486,7 @@ contract RMM is ERC20 { uint256 epsilon ) public view returns (uint256 guess) { uint256 min = exactSYIn; - console2.log("here1"); - console2.log("reserveX", reserveX); - console2.log("reserveY", reserveY); - console2.log("totalLiquidity", totalLiquidity); - console2.log("strike", strike); max = max > 0 ? max : calcMaxPtIn(index.syToAsset(reserveX), reserveY, totalLiquidity, strike); - console2.log("here2"); for (uint256 iter = 0; iter < 256; ++iter) { guess = initialGuess > 0 && iter == 0 ? initialGuess : (min + max) / 2; (,, uint256 amountOut,,) = prepareSwapPtIn(guess, blockTime, index); diff --git a/test/invariant/RMMHandler.sol b/test/invariant/RMMHandler.sol index f9ba9bd..c7e7fd7 100644 --- a/test/invariant/RMMHandler.sol +++ b/test/invariant/RMMHandler.sol @@ -225,8 +225,7 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { deal(address(YT), currentActor, ytIn); vm.startPrank(currentActor); YT.approve(address(rmm), ytIn); - console2.log("YT Balance", YT.balanceOf(currentActor)); - (uint256 amountOut, uint256 amountIn, int256 deltaLiquidity) = + (, uint256 amountIn, int256 deltaLiquidity) = rmm.swapExactYtForSy(ytIn, 1000 ether, address(currentActor)); vm.stopPrank(); @@ -237,13 +236,9 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { // 4. send the SY to rmm to cover the cost of the PT // 5. remainder SY sent to currentActor // in the end the reserves are mutated such that rY = rYStart - ytIn, rX = rXStart + syCreated - sySwapped - // ghost_reserveX += amountIn; - console2.log("ghost_reserveY", ghost_reserveY); - console2.log("amountIn", amountIn); - console2.log("ghost_reserveY - amountIn", ghost_reserveY - amountIn); - console2.log("rmm.reserveY()", rmm.reserveY()); + ghost_reserveX += amountIn; ghost_reserveY -= ytIn; - // ghost_totalLiquidity += int256(deltaLiquidity); + ghost_totalLiquidity += int256(deltaLiquidity); } function increaseTime(uint256 amount) public countCall(this.increaseTime.selector) { diff --git a/test/invariant/RMMInvariants.t.sol b/test/invariant/RMMInvariants.t.sol index 29f6e00..d5789fa 100644 --- a/test/invariant/RMMInvariants.t.sol +++ b/test/invariant/RMMInvariants.t.sol @@ -22,13 +22,13 @@ contract RMMInvariantsTest is SetUp { handler.init(); - bytes4[] memory selectors = new bytes4[](5); - selectors[0] = RMMHandler.allocate.selector; - selectors[1] = RMMHandler.deallocate.selector; - selectors[2] = RMMHandler.swapExactSyForYt.selector; - selectors[3] = RMMHandler.swapExactPtForSy.selector; - selectors[4] = RMMHandler.swapExactSyForPt.selector; - // selectors[5] = RMMHandler.swapExactYtForSy.selector; + bytes4[] memory selectors = new bytes4[](1); + // selectors[0] = RMMHandler.allocate.selector; + // selectors[1] = RMMHandler.deallocate.selector; + // selectors[2] = RMMHandler.swapExactSyForYt.selector; + // selectors[3] = RMMHandler.swapExactPtForSy.selector; + // selectors[4] = RMMHandler.swapExactSyForPt.selector; + selectors[0] = RMMHandler.swapExactYtForSy.selector; // selectors[6] = RMMHandler.swapExactTokenForYt.selector; targetSelector(FuzzSelector({addr: address(handler), selectors: selectors})); @@ -47,21 +47,21 @@ contract RMMInvariantsTest is SetUp { } /// forge-config: default.invariant.runs = 10 - /// forge-config: default.invariant.depth = 100 + /// forge-config: default.invariant.depth = 10 /// forge-config: default.invariant.fail-on-revert = true function invariant_TradingFunction() public { assertTrue(abs(rmm.tradingFunction(newIndex())) <= 100, "Invariant out of valid range"); } /// forge-config: default.invariant.runs = 10 - /// forge-config: default.invariant.depth = 100 + /// forge-config: default.invariant.depth = 10 /// forge-config: default.invariant.fail-on-revert = true function invariant_ReserveX() public view { assertEq(rmm.reserveX(), handler.ghost_reserveX()); } /// forge-config: default.invariant.runs = 10 - /// forge-config: default.invariant.depth = 100 + /// forge-config: default.invariant.depth = 10 /// forge-config: default.invariant.fail-on-revert = true function invariant_ReserveY() public view { assertEq(rmm.reserveY(), handler.ghost_reserveY()); From f2ff3df6cb27003f93596548dcf12b1565b624c9 Mon Sep 17 00:00:00 2001 From: kinrezc Date: Thu, 11 Jul 2024 15:36:18 -0400 Subject: [PATCH 102/116] use custom `fund` instead of `deal` --- src/RMM.sol | 5 ++- test/SetUp.sol | 16 +++---- test/invariant/RMMHandler.sol | 67 +++++++++++++----------------- test/invariant/RMMInvariants.t.sol | 31 ++++++++------ 4 files changed, 59 insertions(+), 60 deletions(-) diff --git a/src/RMM.sol b/src/RMM.sol index f24dfd9..335956a 100644 --- a/src/RMM.sol +++ b/src/RMM.sol @@ -532,7 +532,6 @@ contract RMM is ERC20 { while (low != high) { uint256 mid = (low + high + 1) / 2; - console2.log("mid", mid); if (calcSlope(reserveX_, reserveY_, totalLiquidity_, strike_, int256(mid)) < 0) { high = mid - 1; } else { @@ -752,6 +751,10 @@ contract RMM is ERC20 { } function redeemPy(uint256 amount, address to) internal returns (uint256 amountOut) { + console2.log("PT balance", PT.balanceOf(address(this))); + console2.log("YT balance", YT.balanceOf(address(this))); + console2.log("SY balance", SY.balanceOf(address(YT))); + console2.log("SY address", address(SY)); PT.transfer(address(YT), amount); YT.transfer(address(YT), amount); amountOut = YT.redeemPY(to); diff --git a/test/SetUp.sol b/test/SetUp.sol index 46d89b3..d996f3a 100644 --- a/test/SetUp.sol +++ b/test/SetUp.sol @@ -14,6 +14,7 @@ import {WstETH} from "./WstETH.sol"; import {MockStETH} from "./mocks/MockStETH.sol"; import {RMM} from "./../src/RMM.sol"; import {MockRMM} from "./MockRMM.sol"; +import "forge-std/console2.sol"; struct InitParams { address PT; @@ -55,6 +56,7 @@ contract SetUp is Test { wstETH = new WstETH(address(stETH)); SY = new PendleWstEthSY("wstEthSY", "wstEthSY", address(weth), address(wstETH)); + vm.label(address(SY), "SY"); vm.label(address(YT), "YT"); vm.label(address(PT), "PT"); @@ -94,16 +96,14 @@ contract SetUp is Test { vm.label(address(rmm), "RMM"); weth.approve(address(rmm), type(uint256).max); + wstETH.approve(address(rmm), type(uint256).max); SY.approve(address(rmm), type(uint256).max); PT.approve(address(rmm), type(uint256).max); YT.approve(address(rmm), type(uint256).max); } function initRMM(InitParams memory initParams) public { - (, uint256 amountY) = - rmm.prepareInit(initParams.priceX, initParams.amountX, initParams.strike, initParams.sigma, newIndex()); - - uint256 amount = 10000 ether; + uint256 amount = 100_000 ether; mintSY(address(this), amount); mintPY(address(this), amount / 2); @@ -113,8 +113,9 @@ contract SetUp is Test { // Here are some utility functions, you can use them to set specific states inside of a test. function mintSY(address to, uint256 amount) public { - // SY.deposit(address(to), address(wstETH), amount, 0); - deal(address(SY), address(to), amount); + deal(address(wstETH), address(this), 100_000 ether); + wstETH.approve(address(SY), 100_000 ether); + SY.deposit(address(to), address(wstETH), amount, 0); } function batchMintSY(address[] memory to, uint256[] memory amounts) public { @@ -175,8 +176,7 @@ contract SetUp is Test { } modifier withSY(address to, uint256 amount) { - // SY.deposit(address(to), address(wstETH), amount, 0); - deal(address(SY), address(to), amount); + deal(address(SY), address(to), amount, true); _; } diff --git a/test/invariant/RMMHandler.sol b/test/invariant/RMMHandler.sol index c7e7fd7..4bba40a 100644 --- a/test/invariant/RMMHandler.sol +++ b/test/invariant/RMMHandler.sol @@ -40,17 +40,13 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { AddressSet internal actors; address internal currentActor; - modifier createActor() { - currentActor = msg.sender; - actors.add(currentActor); - _; - } + address baseActor = address(0x1234567890123456789012345678901234567890); - modifier useActor(uint256 actorIndexSeed) { - currentActor = actors.rand(actorIndexSeed); - vm.startPrank(currentActor); + modifier useActor() { + actors.add(baseActor); + currentActor = actors.rand(0); + console2.log("currentActor", currentActor); _; - vm.stopPrank(); } modifier countCall(bytes4 key) { @@ -66,10 +62,9 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { YT = YT_; weth = weth_; } - // Utility functions - function give(address token, address to, uint256 amount) public { + function fund(address token, address to, uint256 amount) public { ERC20(token).transfer(to, amount); } @@ -109,29 +104,32 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { // Target functions - function allocate(uint256 deltaX, uint256 deltaY) public createActor countCall(this.allocate.selector) { - deltaX = bound(deltaX, 0.1 ether, 10 ether); + function allocate(uint256 deltaX, uint256 deltaY) public useActor countCall(this.allocate.selector) { + deltaX = bound(deltaX, 0.1 ether, 1 ether); + console2.log("currentActor", currentActor); vm.startPrank(currentActor); - (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity,) = rmm.prepareAllocate(true, deltaX); + vm.stopPrank(); - deal(address(SY), currentActor, deltaXWad); - deal(address(PT), currentActor, deltaYWad); + fund(address(SY), currentActor, deltaXWad); + fund(address(PT), currentActor, deltaYWad); + + vm.startPrank(currentActor); SY.approve(address(rmm), deltaXWad); PT.approve(address(rmm), deltaYWad); uint256 realDeltaLiquidity = rmm.allocate(true, deltaX, deltaLiquidity, address(currentActor)); - vm.stopPrank(); + ghost_totalLiquidity += int256(realDeltaLiquidity); ghost_reserveX += deltaXWad; ghost_reserveY += deltaYWad; } - function deallocate(uint256 actorSeed) public useActor(actorSeed) countCall(this.deallocate.selector) { - uint256 deltaLiquidity = rmm.totalLiquidity() * rmm.balanceOf(currentActor) / rmm.totalSupply(); + function deallocate(uint256 deltaLiquidity) public useActor countCall(this.deallocate.selector) { + deltaLiquidity = rmm.totalLiquidity() * rmm.balanceOf(currentActor) / rmm.totalSupply(); (uint256 deltaX, uint256 deltaY) = rmm.deallocate(deltaLiquidity, 0, 0, address(this)); ghost_reserveX -= deltaX; @@ -139,9 +137,9 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { ghost_totalLiquidity -= int256(deltaLiquidity); } - function swapExactSyForYt() public createActor countCall(this.swapExactSyForYt.selector) { + function swapExactSyForYt() public useActor countCall(this.swapExactSyForYt.selector) { uint256 exactSYIn = 1 ether; - deal(address(SY), address(currentActor), exactSYIn); + fund(address(SY), address(currentActor), exactSYIn); PYIndex index = YT.newIndex(); uint256 ytOut = rmm.computeSYToYT(index, exactSYIn, 0, block.timestamp, 0, 1_000); @@ -158,9 +156,9 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { ghost_totalLiquidity += int256(deltaLiquidity); } - function swapExactTokenForYt() public createActor countCall(this.swapExactTokenForYt.selector) { + function swapExactTokenForYt() public useActor countCall(this.swapExactTokenForYt.selector) { uint256 amountTokenIn = 1 ether; - deal(currentActor, amountTokenIn); + fund(address(weth), currentActor, amountTokenIn); vm.startPrank(currentActor); weth.deposit{value: amountTokenIn}(); @@ -190,9 +188,10 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { ghost_totalLiquidity += deltaLiquidity; } - function swapExactPtForSy() public createActor countCall(this.swapExactPtForSy.selector) { + function swapExactPtForSy() public useActor countCall(this.swapExactPtForSy.selector) { uint256 amountIn = 1 ether; - deal(address(PT), currentActor, amountIn); + fund(address(PT), currentActor, amountIn); + vm.startPrank(currentActor); PT.approve(address(rmm), amountIn); (uint256 amountOut, int256 deltaLiquidity) = rmm.swapExactPtForSy(amountIn, 0, address(currentActor)); @@ -203,24 +202,23 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { ghost_totalLiquidity += int256(deltaLiquidity); } - function swapExactSyForPt() public createActor countCall(this.swapExactSyForPt.selector) { + function swapExactSyForPt() public useActor countCall(this.swapExactSyForPt.selector) { uint256 amountIn = 1 ether; - deal(address(SY), currentActor, amountIn); + fund(address(SY), currentActor, amountIn); + vm.startPrank(currentActor); SY.approve(address(rmm), amountIn); (uint256 amountOut, int256 deltaLiquidity) = rmm.swapExactSyForPt(amountIn, 0, address(currentActor)); vm.stopPrank(); - - console2.log("ry", rmm.reserveY()); - console2.log("ry - amountOut", rmm.reserveY() - amountOut); ghost_reserveX += amountIn; ghost_reserveY -= amountOut; ghost_totalLiquidity += int256(deltaLiquidity); } - function swapExactYtForSy() public createActor countCall(this.swapExactYtForSy.selector) { + function swapExactYtForSy() public useActor countCall(this.swapExactYtForSy.selector) { uint256 ytIn = 1 ether; + console2.log("YT balance of SY 1", SY.balanceOf(address(YT))); deal(address(YT), currentActor, ytIn); vm.startPrank(currentActor); @@ -229,13 +227,6 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { rmm.swapExactYtForSy(ytIn, 1000 ether, address(currentActor)); vm.stopPrank(); - // the workflow here is: - // 1. YT -> RMM - // 2. Flash amountYt of PT from RMM, so the reserveY should be reduced by ytIn - // 3. recombine the YT and PT into SY - // 4. send the SY to rmm to cover the cost of the PT - // 5. remainder SY sent to currentActor - // in the end the reserves are mutated such that rY = rYStart - ytIn, rX = rXStart + syCreated - sySwapped ghost_reserveX += amountIn; ghost_reserveY -= ytIn; ghost_totalLiquidity += int256(deltaLiquidity); diff --git a/test/invariant/RMMInvariants.t.sol b/test/invariant/RMMInvariants.t.sol index d5789fa..b9720a7 100644 --- a/test/invariant/RMMInvariants.t.sol +++ b/test/invariant/RMMInvariants.t.sol @@ -5,6 +5,7 @@ import {console} from "forge-std/console.sol"; import {abs} from "./../../src/lib/RmmLib.sol"; import {SetUp} from "../SetUp.sol"; import {RMMHandler} from "./RMMHandler.sol"; +import "forge-std/console2.sol"; contract RMMInvariantsTest is SetUp { RMMHandler handler; @@ -16,19 +17,23 @@ contract RMMInvariantsTest is SetUp { setUpRMM(getDefaultParams()); handler = new RMMHandler(rmm, PT, SY, YT, weth); - mintSY(address(handler), 1000 ether); - mintSY(address(this), 2000 ether); - mintPY(address(handler), 2000 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); + handler.init(); - bytes4[] memory selectors = new bytes4[](1); - // selectors[0] = RMMHandler.allocate.selector; - // selectors[1] = RMMHandler.deallocate.selector; - // selectors[2] = RMMHandler.swapExactSyForYt.selector; - // selectors[3] = RMMHandler.swapExactPtForSy.selector; - // selectors[4] = RMMHandler.swapExactSyForPt.selector; - selectors[0] = RMMHandler.swapExactYtForSy.selector; + bytes4[] memory selectors = new bytes4[](6); + selectors[0] = RMMHandler.allocate.selector; + selectors[1] = RMMHandler.deallocate.selector; + selectors[2] = RMMHandler.swapExactSyForYt.selector; + selectors[3] = RMMHandler.swapExactPtForSy.selector; + selectors[4] = RMMHandler.swapExactSyForPt.selector; + selectors[5] = RMMHandler.swapExactYtForSy.selector; // selectors[6] = RMMHandler.swapExactTokenForYt.selector; targetSelector(FuzzSelector({addr: address(handler), selectors: selectors})); @@ -47,21 +52,21 @@ contract RMMInvariantsTest is SetUp { } /// forge-config: default.invariant.runs = 10 - /// forge-config: default.invariant.depth = 10 + /// forge-config: default.invariant.depth = 100 /// forge-config: default.invariant.fail-on-revert = true function invariant_TradingFunction() public { assertTrue(abs(rmm.tradingFunction(newIndex())) <= 100, "Invariant out of valid range"); } /// forge-config: default.invariant.runs = 10 - /// forge-config: default.invariant.depth = 10 + /// forge-config: default.invariant.depth = 100 /// forge-config: default.invariant.fail-on-revert = true function invariant_ReserveX() public view { assertEq(rmm.reserveX(), handler.ghost_reserveX()); } /// forge-config: default.invariant.runs = 10 - /// forge-config: default.invariant.depth = 10 + /// forge-config: default.invariant.depth = 100 /// forge-config: default.invariant.fail-on-revert = true function invariant_ReserveY() public view { assertEq(rmm.reserveY(), handler.ghost_reserveY()); From 8babed396ca4d3b38a9ea0c248b40ef247cd9a39 Mon Sep 17 00:00:00 2001 From: kinrezc Date: Thu, 11 Jul 2024 15:50:30 -0400 Subject: [PATCH 103/116] fix invariant tests --- src/RMM.sol | 6 ++---- test/invariant/RMMHandler.sol | 3 --- test/invariant/RMMInvariants.t.sol | 12 +++++++----- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/RMM.sol b/src/RMM.sol index 335956a..50d9485 100644 --- a/src/RMM.sol +++ b/src/RMM.sol @@ -184,7 +184,7 @@ contract RMM is ERC20 { uint256 upperBound, uint256 epsilon, address to - ) external payable lock returns (uint256 syIn, uint256 amountOut, int256 deltaLiquidity) { + ) external payable lock returns (uint256 amountInWad, uint256 amountOutWad, int256 deltaLiquidity) { SwapToYt memory swap; swap.tokenIn = token; swap.amountTokenIn = token == address(0) ? 0 : amountTokenIn; @@ -194,12 +194,10 @@ contract RMM is ERC20 { swap.minYtOut = minYtOut; swap.to = to; swap.realSyMinted = _mintSYFromNativeAndToken(address(this), swap.tokenIn, swap.amountTokenIn, swap.minSyMinted); - syIn = swap.realSyMinted; PYIndex index = YT.newIndex(); - uint256 amountInWad; - uint256 amountOutWad; uint256 strike_; + uint256 amountOut; swap.amountPtIn = computeSYToYT(index, swap.realSyMinted, upperBound, block.timestamp, swap.amountPtIn, epsilon); diff --git a/test/invariant/RMMHandler.sol b/test/invariant/RMMHandler.sol index 4bba40a..ceed198 100644 --- a/test/invariant/RMMHandler.sol +++ b/test/invariant/RMMHandler.sol @@ -45,7 +45,6 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { modifier useActor() { actors.add(baseActor); currentActor = actors.rand(0); - console2.log("currentActor", currentActor); _; } @@ -161,8 +160,6 @@ contract RMMHandler is CommonBase, StdUtils, StdCheats { fund(address(weth), currentActor, amountTokenIn); vm.startPrank(currentActor); - weth.deposit{value: amountTokenIn}(); - PYIndex index = YT.newIndex(); (uint256 syMinted, uint256 ytOut) = rmm.computeTokenToYT( diff --git a/test/invariant/RMMInvariants.t.sol b/test/invariant/RMMInvariants.t.sol index b9720a7..046ac7e 100644 --- a/test/invariant/RMMInvariants.t.sol +++ b/test/invariant/RMMInvariants.t.sol @@ -17,6 +17,8 @@ contract RMMInvariantsTest is SetUp { setUpRMM(getDefaultParams()); handler = new RMMHandler(rmm, PT, SY, YT, weth); + 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); @@ -27,14 +29,14 @@ contract RMMInvariantsTest is SetUp { handler.init(); - bytes4[] memory selectors = new bytes4[](6); + bytes4[] memory selectors = new bytes4[](7); selectors[0] = RMMHandler.allocate.selector; selectors[1] = RMMHandler.deallocate.selector; selectors[2] = RMMHandler.swapExactSyForYt.selector; selectors[3] = RMMHandler.swapExactPtForSy.selector; selectors[4] = RMMHandler.swapExactSyForPt.selector; selectors[5] = RMMHandler.swapExactYtForSy.selector; - // selectors[6] = RMMHandler.swapExactTokenForYt.selector; + selectors[6] = RMMHandler.swapExactTokenForYt.selector; targetSelector(FuzzSelector({addr: address(handler), selectors: selectors})); targetContract(address(handler)); @@ -52,21 +54,21 @@ contract RMMInvariantsTest is SetUp { } /// forge-config: default.invariant.runs = 10 - /// forge-config: default.invariant.depth = 100 + /// forge-config: default.invariant.depth = 10 /// forge-config: default.invariant.fail-on-revert = true function invariant_TradingFunction() public { assertTrue(abs(rmm.tradingFunction(newIndex())) <= 100, "Invariant out of valid range"); } /// forge-config: default.invariant.runs = 10 - /// forge-config: default.invariant.depth = 100 + /// forge-config: default.invariant.depth = 10 /// forge-config: default.invariant.fail-on-revert = true function invariant_ReserveX() public view { assertEq(rmm.reserveX(), handler.ghost_reserveX()); } /// forge-config: default.invariant.runs = 10 - /// forge-config: default.invariant.depth = 100 + /// forge-config: default.invariant.depth = 10 /// forge-config: default.invariant.fail-on-revert = true function invariant_ReserveY() public view { assertEq(rmm.reserveY(), handler.ghost_reserveY()); From a5e38a57edf0ac13c34d963ad66386136d720418 Mon Sep 17 00:00:00 2001 From: kinrezc Date: Thu, 11 Jul 2024 15:52:21 -0400 Subject: [PATCH 104/116] increase depth to tolerable amount --- test/invariant/RMMInvariants.t.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/invariant/RMMInvariants.t.sol b/test/invariant/RMMInvariants.t.sol index 046ac7e..2b95c71 100644 --- a/test/invariant/RMMInvariants.t.sol +++ b/test/invariant/RMMInvariants.t.sol @@ -54,21 +54,21 @@ contract RMMInvariantsTest is SetUp { } /// forge-config: default.invariant.runs = 10 - /// forge-config: default.invariant.depth = 10 + /// forge-config: default.invariant.depth = 30 /// forge-config: default.invariant.fail-on-revert = true function invariant_TradingFunction() public { assertTrue(abs(rmm.tradingFunction(newIndex())) <= 100, "Invariant out of valid range"); } /// forge-config: default.invariant.runs = 10 - /// forge-config: default.invariant.depth = 10 + /// forge-config: default.invariant.depth = 30 /// forge-config: default.invariant.fail-on-revert = true function invariant_ReserveX() public view { assertEq(rmm.reserveX(), handler.ghost_reserveX()); } /// forge-config: default.invariant.runs = 10 - /// forge-config: default.invariant.depth = 10 + /// forge-config: default.invariant.depth = 30 /// forge-config: default.invariant.fail-on-revert = true function invariant_ReserveY() public view { assertEq(rmm.reserveY(), handler.ghost_reserveY()); From 044641a007f811a06a71fc234d49c57881635d32 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 12 Jul 2024 15:25:48 +0400 Subject: [PATCH 105/116] test: update mint SY and PY functions and modifiers (got rid of deal) --- test/SetUp.sol | 33 ++++++++++----------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/test/SetUp.sol b/test/SetUp.sol index d996f3a..c958205 100644 --- a/test/SetUp.sol +++ b/test/SetUp.sol @@ -56,7 +56,6 @@ contract SetUp is Test { wstETH = new WstETH(address(stETH)); SY = new PendleWstEthSY("wstEthSY", "wstEthSY", address(weth), address(wstETH)); - vm.label(address(SY), "SY"); vm.label(address(YT), "YT"); vm.label(address(PT), "PT"); @@ -112,31 +111,20 @@ contract SetUp is Test { // Here are some utility functions, you can use them to set specific states inside of a test. - function mintSY(address to, uint256 amount) public { - deal(address(wstETH), address(this), 100_000 ether); - wstETH.approve(address(SY), 100_000 ether); - SY.deposit(address(to), address(wstETH), amount, 0); - } - - function batchMintSY(address[] memory to, uint256[] memory amounts) public { - require(to.length == amounts.length, "INVALID_LENGTH"); - for (uint256 i; i < to.length; i++) { - mintSY(to[i], amounts[i]); - } + function mintSY(address to, uint256 amount) public returns (uint256 amountSharesOut) { + deal(address(this), amount); + (uint256 stETHAmount) = stETH.submit{value: amount}(address(0)); + stETH.approve(address(SY), stETHAmount); + amountSharesOut = SY.deposit(address(this), address(stETH), amount, 0); + if (to != address(this)) SY.transfer(address(to), amountSharesOut); } function mintPY(address to, uint256 amount) public { - SY.transfer(address(YT), amount); + uint256 amountSharesOut = mintSY(address(this), amount); + SY.transfer(address(YT), amountSharesOut); YT.mintPY(to, to); } - function batchMintPY(address[] memory to, uint256[] memory amounts) public { - require(to.length == amounts.length, "INVALID_LENGTH"); - for (uint256 i; i < to.length; i++) { - mintPY(to[i], amounts[i]); - } - } - function getDefaultParams() internal view returns (InitParams memory) { return InitParams({ priceX: 1.15 ether, @@ -176,13 +164,12 @@ contract SetUp is Test { } modifier withSY(address to, uint256 amount) { - deal(address(SY), address(to), amount, true); + mintSY(address(to), amount); _; } modifier withPY(address to, uint256 amount) { - SY.transfer(address(YT), amount); - YT.mintPY(to, to); + mintPY(address(to), amount); _; } From d2a6d121426a1431c14205c2a7071a49f467f5ef Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 12 Jul 2024 15:35:38 +0400 Subject: [PATCH 106/116] test: add missing ETH deal in WETH modifier --- test/SetUp.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/SetUp.sol b/test/SetUp.sol index c958205..f13a995 100644 --- a/test/SetUp.sol +++ b/test/SetUp.sol @@ -174,8 +174,9 @@ contract SetUp is Test { } modifier withWETH(address to, uint256 amount) { + deal(address(this), amount); weth.deposit{value: amount}(); - weth.transfer(to, amount); + if (to != address(this)) weth.transfer(to, amount); _; } From 57ae968c9fc55385d13cd64033c6b7ae209cdf7a Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 12 Jul 2024 15:39:20 +0400 Subject: [PATCH 107/116] test: add missing ETH deal in test_swapExactTokenForYt_SwapsETH --- test/unit/SwapExactTokenForYt.t.sol | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/test/unit/SwapExactTokenForYt.t.sol b/test/unit/SwapExactTokenForYt.t.sol index 4fd3c93..ee7a4d3 100644 --- a/test/unit/SwapExactTokenForYt.t.sol +++ b/test/unit/SwapExactTokenForYt.t.sol @@ -15,20 +15,19 @@ contract SwapExactTokenForYtTest is SetUp { PYIndex index = YT.newIndex(); (uint256 syMinted, uint256 ytOut) = rmm.computeTokenToYT(index, address(weth), amountIn, 0, block.timestamp, 0, 1_000); - rmm.swapExactTokenForYt( - address(weth), amountIn, ytOut, syMinted, ytOut, 0, 0.005 ether, address(this) - ); + rmm.swapExactTokenForYt(address(weth), amountIn, ytOut, syMinted, ytOut, 0, 0.005 ether, address(this)); } function test_swapExactTokenForYt_SwapsETH() public useSYPool { + uint256 amountIn = 1 ether; + deal(address(this), amountIn); uint256 preETHBalance = address(this).balance; uint256 preYTBalance = YT.balanceOf(address(this)); - uint256 amountIn = 1 ether; PYIndex index = YT.newIndex(); (uint256 syMinted, uint256 ytOut) = rmm.computeTokenToYT(index, address(0), amountIn, 0, block.timestamp, 0, 1_000); - (,uint256 amountOut,) = rmm.swapExactTokenForYt{value: amountIn}( + (, uint256 amountOut,) = rmm.swapExactTokenForYt{value: amountIn}( address(0), amountIn, ytOut, syMinted, ytOut, 0, 0.005 ether, address(this) ); From 1266326bb6b47f304fcd4c08e223c85173eb43de Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 12 Jul 2024 15:42:36 +0400 Subject: [PATCH 108/116] test: fix test_mintSY_MintsSYUsingWETH --- test/unit/MintSY.t.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/unit/MintSY.t.sol b/test/unit/MintSY.t.sol index 05e96bc..4c36317 100644 --- a/test/unit/MintSY.t.sol +++ b/test/unit/MintSY.t.sol @@ -9,8 +9,7 @@ contract MintSYTest is SetUp { assertEq(SY.balanceOf(address(0xbeef)), 1 ether); } - function test_mintSY_MintsSYUsingWETH() public useDefaultPool { - weth.deposit{value: 1 ether}(); + function test_mintSY_MintsSYUsingWETH() public useDefaultPool withWETH(address(this), 1 ether) { rmm.mintSY(address(0xbeef), address(weth), 1 ether, 0); assertEq(SY.balanceOf(address(0xbeef)), 1 ether); } From a17d30a1149384dc4e4288ecd0547461bcc78014 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 12 Jul 2024 15:43:28 +0400 Subject: [PATCH 109/116] test: add missing ETH deal in test_mintSY_MintsSYUsingETH --- test/unit/MintSY.t.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/test/unit/MintSY.t.sol b/test/unit/MintSY.t.sol index 4c36317..64595bb 100644 --- a/test/unit/MintSY.t.sol +++ b/test/unit/MintSY.t.sol @@ -5,6 +5,7 @@ import {SetUp} from "../SetUp.sol"; contract MintSYTest is SetUp { function test_mintSY_MintsSYUsingETH() public useDefaultPool { + deal(address(this), 1 ether); rmm.mintSY{value: 1 ether}(address(0xbeef), address(0), 1 ether, 0); assertEq(SY.balanceOf(address(0xbeef)), 1 ether); } From 4ad1b0a8d0999107953583bc1c8a704c35ed2f73 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 12 Jul 2024 18:56:03 +0400 Subject: [PATCH 110/116] feat: LiquidityManager update --- src/LiquidityManager.sol | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/LiquidityManager.sol b/src/LiquidityManager.sol index 913783c..90eed3a 100644 --- a/src/LiquidityManager.sol +++ b/src/LiquidityManager.sol @@ -5,6 +5,7 @@ import {IStandardizedYield} from "pendle/interfaces/IStandardizedYield.sol"; import {PYIndexLib, PYIndex} from "pendle/core/StandardizedYield/PYIndex.sol"; import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol"; import {SafeTransferLib, ERC20} from "solmate/utils/SafeTransferLib.sol"; +import {console} from "forge-std/console.sol"; import {RMM, IPYieldToken} from "./RMM.sol"; import {InvalidTokenIn, InsufficientSYMinted} from "./lib/RmmErrors.sol"; @@ -78,10 +79,11 @@ contract LiquidityManager { // 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)); pt.approve(address(args.rmm), ptBal); - liquidity = rmm.allocate(syBal, ptBal, args.minLiquidityDelta, msg.sender); + liquidity = rmm.allocate(true, syBal, args.minLiquidityDelta, msg.sender); } function allocateFromPt(AllocateArgs calldata args) external returns (uint256 liquidity) { @@ -112,12 +114,11 @@ contract LiquidityManager { pt.approve(address(rmm), args.amountIn); // swap ptToSwap for sy - rmm.swapExactPtForSy(ptToSwap, args.minOut, address(this)); - uint256 syBal = sy.balanceOf(address(this)); - uint256 ptBal = pt.balanceOf(address(this)); + (uint256 syOut,) = rmm.swapExactPtForSy(ptToSwap, args.minOut, address(this)); + console.log("syOut", syOut); - sy.approve(address(rmm), syBal); - liquidity = rmm.allocate(syBal, ptBal, args.minLiquidityDelta, msg.sender); + sy.approve(address(rmm), type(uint256).max); + liquidity = rmm.allocate(false, pt.balanceOf(address(this)), args.minLiquidityDelta, msg.sender); } struct ComputeArgs { From 6f0b80248814b51a0ef960c7b9f88fb9a7d497f3 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 12 Jul 2024 18:58:17 +0400 Subject: [PATCH 111/116] test: update LiquidityManager setup --- test/unit/LiquidityManager.t.sol | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/test/unit/LiquidityManager.t.sol b/test/unit/LiquidityManager.t.sol index 635336e..287074b 100644 --- a/test/unit/LiquidityManager.t.sol +++ b/test/unit/LiquidityManager.t.sol @@ -5,10 +5,6 @@ import {Test, console2} from "forge-std/Test.sol"; import {RMM, toInt, toUint, upscale, downscaleDown, scalar, sum, abs, PoolPreCompute} from "../../src/RMM.sol"; import {LiquidityManager, RMM} from "../../src/LiquidityManager.sol"; import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; -import {ReturnsTooLittleToken} from "solmate/test/utils/weird-tokens/ReturnsTooLittleToken.sol"; -import {ReturnsTooMuchToken} from "solmate/test/utils/weird-tokens/ReturnsTooMuchToken.sol"; -import {MissingReturnToken} from "solmate/test/utils/weird-tokens/MissingReturnToken.sol"; -import {ReturnsFalseToken} from "solmate/test/utils/weird-tokens/ReturnsFalseToken.sol"; import {IPMarket} from "pendle/interfaces/IPMarket.sol"; import "pendle/core/Market/MarketMathCore.sol"; import "pendle/interfaces/IPAllActionV3.sol"; @@ -16,7 +12,6 @@ import {IPPrincipalToken} from "pendle/interfaces/IPPrincipalToken.sol"; import {IStandardizedYield} from "pendle/interfaces/IStandardizedYield.sol"; import {IPYieldToken} from "pendle/interfaces/IPYieldToken.sol"; import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol"; -import {ERC20} from "solmate/tokens/ERC20.sol"; IPAllActionV3 constant router = IPAllActionV3(0x00000000005BBB0EF59571E58418F9a4357b68A0); IPMarket constant market = IPMarket(0x9eC4c502D989F04FfA9312C9D6E3F872EC91A0F9); @@ -51,11 +46,11 @@ contract ForkRMMTest is Test { function setUp() public { vm.createSelectFork({urlOrAlias: "mainnet", blockNumber: 17_162_783}); - __subject__ = new RMM(WETH_ADDRESS, "LPToken", "LPT"); __liquidityManager__ = new LiquidityManager(); vm.label(address(__subject__), "RMM"); (SY, PT, YT) = IPMarket(market).readTokens(); + __subject__ = new RMM("LPToken", "LPT", address(PT), 0.025 ether, 0.0003 ether); (MarketState memory ms,) = getPendleMarketData(); timeToExpiry = ms.expiry - block.timestamp; @@ -79,6 +74,10 @@ contract ForkRMMTest is Test { IERC20(SY).approve(address(liquidityManager()), type(uint256).max); IERC20(PT).approve(address(liquidityManager()), type(uint256).max); IERC20(YT).approve(address(liquidityManager()), type(uint256).max); + + vm.label(address(SY), "SY"); + vm.label(address(YT), "YT"); + vm.label(address(PT), "PT"); } function getPendleMarketData() public returns (MarketState memory ms, MarketPreCompute memory mp) { @@ -125,15 +124,7 @@ contract ForkRMMTest is Test { modifier basic_sy() { (MarketState memory ms, MarketPreCompute memory mp) = getPendleMarketData(); uint256 price = uint256(getPtExchangeRate()); - subject().init({ - PT_: address(PT), - priceX: price, - amountX: uint256(ms.totalSy - 100 ether), - strike_: uint256(mp.rateAnchor), - sigma_: 0.025 ether, - fee_: 0.0003 ether, - curator_: address(0x55) - }); + subject().init({priceX: price, amountX: uint256(ms.totalSy - 100 ether), strike_: uint256(mp.rateAnchor)}); _; } @@ -207,7 +198,7 @@ contract ForkRMMTest is Test { uint256 dx = maxSyToSwap - syToSwap; uint256 dy = ptOut; - (,, uint256 minLiquidityDelta,) = subject().prepareAllocate(dx, dy, index); + (,, uint256 minLiquidityDelta,) = subject().prepareAllocate(true, dx); liquidityManager().allocateFromSy( LiquidityManager.AllocateArgs(address(subject()), maxSyToSwap, ptOut, minLiquidityDelta, syToSwap, eps) ); @@ -230,9 +221,11 @@ contract ForkRMMTest is Test { uint256 dy = maxPtToSwap - ptToSwap; uint256 dx = syOut; - (,, uint256 minLiquidityDelta,) = subject().prepareAllocate(dx, dy, index); + (,, uint256 minLiquidityDelta,) = subject().prepareAllocate(false, dy); liquidityManager().allocateFromPt( - LiquidityManager.AllocateArgs(address(subject()), maxPtToSwap, syOut, minLiquidityDelta.mulDivDown(95, 100), ptToSwap, eps) + LiquidityManager.AllocateArgs( + address(subject()), maxPtToSwap, syOut, minLiquidityDelta.mulDivDown(95, 100), ptToSwap, eps + ) ); assertEq(subject().reserveY(), rY + maxPtToSwap, "unexpected rY balance after zap"); assertEq(subject().reserveX(), rX, "unexpected rX balance after zap"); From c5984f7e9062ba5b690ce288e1b8cc0eb8faf625 Mon Sep 17 00:00:00 2001 From: kinrezc Date: Mon, 15 Jul 2024 13:34:14 -0400 Subject: [PATCH 112/116] wip lower contract size --- src/LiquidityManager.sol | 22 +++++- src/RMM.sol | 112 ++++--------------------------- src/lib/BisectionLib.sol | 73 -------------------- src/lib/LiquidityLib.sol | 28 +------- src/lib/RmmErrors.sol | 6 +- src/lib/RmmLib.sol | 98 +++++++++++++++++++++++++-- test/unit/ApproxSpotPrice.t.sol | 20 ------ test/unit/Init.t.sol | 10 +-- test/unit/LiquidityManager.t.sol | 2 +- test/unit/SwapExactPtForSy.t.sol | 6 +- test/unit/SwapExactSyForPt.t.sol | 6 +- 11 files changed, 141 insertions(+), 242 deletions(-) delete mode 100644 src/lib/BisectionLib.sol delete mode 100644 test/unit/ApproxSpotPrice.t.sol diff --git a/src/LiquidityManager.sol b/src/LiquidityManager.sol index 90eed3a..7584d57 100644 --- a/src/LiquidityManager.sol +++ b/src/LiquidityManager.sol @@ -5,9 +5,8 @@ import {IStandardizedYield} from "pendle/interfaces/IStandardizedYield.sol"; import {PYIndexLib, PYIndex} from "pendle/core/StandardizedYield/PYIndex.sol"; import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol"; import {SafeTransferLib, ERC20} from "solmate/utils/SafeTransferLib.sol"; -import {console} from "forge-std/console.sol"; -import {RMM, IPYieldToken} from "./RMM.sol"; +import {RMM, IPYieldToken, Gaussian, computeTradingFunction} from "./RMM.sol"; import {InvalidTokenIn, InsufficientSYMinted} from "./lib/RmmErrors.sol"; contract LiquidityManager { @@ -115,7 +114,6 @@ contract LiquidityManager { // swap ptToSwap for sy (uint256 syOut,) = rmm.swapExactPtForSy(ptToSwap, args.minOut, address(this)); - console.log("syOut", syOut); sy.approve(address(rmm), type(uint256).max); liquidity = rmm.allocate(false, pt.balanceOf(address(this)), args.minLiquidityDelta, msg.sender); @@ -180,4 +178,22 @@ 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); } + + function calcMaxPtOut( + uint256 reserveX_, + uint256 reserveY_, + uint256 totalLiquidity_, + uint256 strike_, + uint256 sigma_, + uint256 tau_ + ) internal pure returns (uint256) { + int256 currentTF = computeTradingFunction(reserveX_, reserveY_, totalLiquidity_, strike_, sigma_, tau_); + + uint256 maxProportion = uint256(int256(1e18) - currentTF) * 1e18 / (2 * 1e18); + + uint256 maxPtOut = reserveY_ * maxProportion / 1e18; + + return (maxPtOut * 999) / 1000; + } + } diff --git a/src/RMM.sol b/src/RMM.sol index 50d9485..80a1a31 100644 --- a/src/RMM.sol +++ b/src/RMM.sol @@ -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"; @@ -22,19 +21,16 @@ contract RMM is ERC20 { using SafeTransferLib for ERC20; - uint256 public constant IMPLIED_RATE_TIME = 365 * 86400; - uint256 public constant BURNT_LIQUIDITY = 1000; + IPPrincipalToken public PT; + IStandardizedYield public SY; + IPYieldToken public YT; - IPPrincipalToken public immutable PT; - IStandardizedYield public immutable SY; - IPYieldToken public immutable YT; + uint256 public SY_scalar; + uint256 public PT_scalar; - uint256 public immutable SY_scalar; - uint256 public immutable PT_scalar; - - uint256 public immutable sigma; - uint256 public immutable fee; - uint256 public immutable maturity; + uint256 public sigma; + uint256 public fee; + uint256 public maturity; uint256 public reserveX; uint256 public reserveY; @@ -93,14 +89,13 @@ contract RMM is ERC20 { lock returns (uint256 totalLiquidity_, uint256 amountY) { - if (strike != 0) revert AlreadyInitialized(); - if (strike_ <= 1e18) revert InvalidStrike(); + if (strike_ <= 1e18 || strike_ != 0) revert InvalidStrikeChange(); PYIndex index = YT.newIndex(); (totalLiquidity_, amountY) = prepareInit(priceX, amountX, strike_, sigma, index); - _mint(msg.sender, totalLiquidity_ - BURNT_LIQUIDITY); - _mint(address(0), BURNT_LIQUIDITY); + _mint(msg.sender, totalLiquidity_ - 1000); + _mint(address(0), 1000); _adjust(toInt(amountX), toInt(amountY), toInt(totalLiquidity_), strike_, index); _debit(address(SY), reserveX); _debit(address(PT), reserveY); @@ -392,7 +387,7 @@ contract RMM is ERC20 { int256 timeToExpiry = int256(maturity) - int256(block.timestamp); lastImpliedPrice = timeToExpiry > 0 - ? uint256(int256(approxSpotPrice(index.syToAsset(reserveX))).lnWad() * int256(IMPLIED_RATE_TIME) / timeToExpiry) + ? uint256(int256(computeSpotPrice(index.syToAsset(reserveX), totalLiquidity, strike, sigma, lastTau())).lnWad() * int256(365 * 86400) / timeToExpiry) : 1 ether; } @@ -437,19 +432,13 @@ contract RMM is ERC20 { return computeTradingFunction(totalAsset, reserveY, totalLiquidity, strike, sigma, lastTau()); } - /// @notice Uses state to approximate the spot price of the X token in terms of the Y token. - /// @dev Do not rely on this for onchain calculations. - function approxSpotPrice(uint256 totalAsset) public view returns (uint256) { - return computeSpotPrice(totalAsset, totalLiquidity, strike, sigma, lastTau()); - } - function computeKGivenLastPrice(uint256 reserveX_, uint256 liquidity, uint256 sigma_, uint256 tau_) public view returns (uint256) { int256 timeToExpiry = int256(maturity - block.timestamp); - int256 rt = int256(lastImpliedPrice) * int256(timeToExpiry) / int256(IMPLIED_RATE_TIME); + int256 rt = int256(lastImpliedPrice) * int256(timeToExpiry) / int256(365 * 86400); int256 lastPrice = rt.expWad(); uint256 a = sigma_.mulWadDown(sigma_).mulWadDown(tau_).mulWadDown(0.5 ether); @@ -502,77 +491,6 @@ contract RMM is ERC20 { } } - function calcMaxPtOut( - uint256 reserveX_, - uint256 reserveY_, - uint256 totalLiquidity_, - uint256 strike_, - uint256 sigma_, - uint256 tau_ - ) internal pure returns (uint256) { - int256 currentTF = computeTradingFunction(reserveX_, reserveY_, totalLiquidity_, strike_, sigma_, tau_); - - uint256 maxProportion = uint256(int256(1e18) - currentTF) * 1e18 / (2 * 1e18); - - uint256 maxPtOut = reserveY_ * maxProportion / 1e18; - - return (maxPtOut * 999) / 1000; - } - - function calcMaxPtIn( - uint256 reserveX_, - uint256 reserveY_, - uint256 totalLiquidity_, - uint256 strike_ - ) internal pure returns (uint256) { - uint256 low = 0; - uint256 high = reserveY_ - 1; - - while (low != high) { - uint256 mid = (low + high + 1) / 2; - if (calcSlope(reserveX_, reserveY_, totalLiquidity_, strike_, int256(mid)) < 0) { - high = mid - 1; - } else { - low = mid; - } - } - - return low; - } - - - - function calcSlope( - uint256 reserveX_, - uint256 reserveY_, - uint256 totalLiquidity_, - uint256 strike_, - int256 ptToMarket - ) internal pure returns (int256) { - uint256 newReserveY = reserveY_ + uint256(ptToMarket); - uint256 b_i = newReserveY * 1e36 / (strike_ * totalLiquidity_); - - if (b_i > 1e18) { - return -1; - } - - int256 b = Gaussian.ppf(toInt(b_i)); - int256 pdf_b = Gaussian.pdf(b); - - int256 slope = (int256(strike_ * totalLiquidity_) * pdf_b / 1e36); - - int256 dxdy = computedXdY(reserveX_, newReserveY); - - return slope + dxdy; - } - - function computedXdY( - uint256 reserveX_, - uint256 reserveY_ - ) internal pure returns (int256) { - return -int256(reserveX_) * 1e18 / int256(reserveY_); - } - //prepare calls function prepareInit(uint256 priceX, uint256 amountX, uint256 strike_, uint256 sigma_, PYIndex index) public @@ -749,10 +667,6 @@ contract RMM is ERC20 { } function redeemPy(uint256 amount, address to) internal returns (uint256 amountOut) { - console2.log("PT balance", PT.balanceOf(address(this))); - console2.log("YT balance", YT.balanceOf(address(this))); - console2.log("SY balance", SY.balanceOf(address(YT))); - console2.log("SY address", address(SY)); PT.transfer(address(YT), amount); YT.transfer(address(YT), amount); amountOut = YT.redeemPY(to); diff --git a/src/lib/BisectionLib.sol b/src/lib/BisectionLib.sol deleted file mode 100644 index 8ad9f34..0000000 --- a/src/lib/BisectionLib.sol +++ /dev/null @@ -1,73 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.8.13; - -/// @dev Thrown when the lower bound is greater than the upper bound. -error BisectionLib_InvalidBounds(uint256 lower, uint256 upper); -/// @dev Thrown when the result of the function `fx` for each input, `upper` and `lower`, is the same sign. -error BisectionLib_RootOutsideBounds(int256 lowerResult, int256 upperResult); - -/** - * @notice - * The function `fx` must be continuous and monotonic. - * - * @dev - * Bisection is a method of finding the root of a function. - * The root is the point where the function crosses the x-axis. - * - * @param args The arguments to pass to the function `fx`. - * @param lower The lower bound of the root to find. - * @param upper The upper bound of the root to find. - * @param epsilon The maximum distance between the lower and upper results. - * @param maxIterations The maximum amount of loop iterations to run. - * @param fx The function to find the root of. - * @return root The root of the function `fx`. - */ -function bisection( - bytes memory args, - uint256 lower, - uint256 upper, - uint256 epsilon, - uint256 maxIterations, - function (bytes memory,uint256) pure returns (int256) fx -) pure returns (uint256 root, uint256 upperInput, uint256 lowerInput) { - if (lower > upper) revert BisectionLib_InvalidBounds(lower, upper); - // Passes the lower and upper bounds to the optimized function. - // Reverts if the optimized function `fx` returns both negative or both positive values. - // This means that the root is not between the bounds. - // The root is between the bounds if the product of the two values is negative. - int256 lowerOutput = fx(args, lower); - int256 upperOutput = fx(args, upper); - if (lowerOutput * upperOutput > 0) { - revert BisectionLib_RootOutsideBounds(lowerOutput, upperOutput); - } - - // Distance is optimized to equal `epsilon`. - uint256 distance = upper - lower; - upperInput = upper; - lowerInput = lower; - - uint256 iterations; // Bounds the amount of loops to `maxIterations`. - do { - // Bisection uses the point between the lower and upper bounds. - // The `distance` is halved each iteration. - root = (lowerInput + upperInput) / 2; - - int256 output = fx(args, root); - - // If the product is negative, the root is between the lower and root. - // If the product is positive, the root is between the root and upper. - if (output * lowerOutput <= 0) { - upperInput = root; // Set the new upper bound to the root because we know its between the lower and root. - } else { - lowerInput = root; // Set the new lower bound to the root because we know its between the upper and root. - lowerOutput = output; // root function value becomes new lower output value - } - - // Update the distance with the new bounds. - distance = upper - lower; - - unchecked { - iterations++; // Increment the iterator. - } - } while (distance > epsilon && iterations < maxIterations); -} diff --git a/src/lib/LiquidityLib.sol b/src/lib/LiquidityLib.sol index 03be469..eafa85f 100644 --- a/src/lib/LiquidityLib.sol +++ b/src/lib/LiquidityLib.sol @@ -6,24 +6,6 @@ import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol"; using FixedPointMathLib for uint256; using FixedPointMathLib for int256; -function computeAllocationGivenX(bool add, uint256 amountX, uint256 rx, uint256 L) - pure - returns (uint256 nextRx, uint256 nextL) -{ - uint256 deltaL = amountX.mulDivDown(L, rx); - nextRx = add ? rx + amountX : rx - amountX; - nextL = add ? L + deltaL : L - deltaL; -} - -function computeAllocationGivenY(bool add, uint256 amountY, uint256 ry, uint256 L) - pure - returns (uint256 nextRy, uint256 nextL) -{ - uint256 deltaL = amountY.mulDivDown(L, ry); - nextRy = add ? ry + amountY : ry - amountY; - nextL = add ? L + deltaL : L - deltaL; -} - function computeDeltaLGivenDeltaX(uint256 deltaX, uint256 liquidity, uint256 reserveX) pure returns (uint256 deltaL) { return liquidity.mulDivDown(deltaX, reserveX); } @@ -62,12 +44,4 @@ function computeAllocationGivenDeltaY(uint256 deltaY, uint256 reserveX, uint256 { deltaX = computeDeltaXGivenDeltaY(deltaY, reserveX, reserveY); deltaL = computeDeltaLGivenDeltaY(deltaY, liquidity, reserveY); -} - -function computeAllocationGivenDeltaL(uint256 deltaL, uint256 reserveX, uint256 reserveY, uint256 liquidity) - pure - returns (uint256 deltaX, uint256 deltaY) -{ - deltaX = computeDeltaXGivenDeltaL(deltaL, reserveX, liquidity); - deltaY = computeDeltaYGivenDeltaL(deltaL, reserveY, liquidity); -} +} \ No newline at end of file diff --git a/src/lib/RmmErrors.sol b/src/lib/RmmErrors.sol index 1871088..95f375d 100644 --- a/src/lib/RmmErrors.sol +++ b/src/lib/RmmErrors.sol @@ -2,7 +2,8 @@ pragma solidity ^0.8.13; /// @dev Thrown if trying to initialize a pool with an invalid strike price (strike < 1e18). -error InvalidStrike(); +/// @dev Thrown if trying to change the strike price of an already initialized pool. +error InvalidStrikeChange(); /// @dev Thrown if trying to initialize an already initialized pool. error AlreadyInitialized(); /// @dev Thrown when a `balanceOf` call fails or returns unexpected data. @@ -31,3 +32,6 @@ error InvalidTokenIn(address tokenIn); error Reentrancy(); error MaturityReached(); + +error ToIntOverflow(); +error ToUintOverflow(); diff --git a/src/lib/RmmLib.sol b/src/lib/RmmLib.sol index 415c912..d059bf2 100644 --- a/src/lib/RmmLib.sol +++ b/src/lib/RmmLib.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.13; import {Gaussian} from "solstat/Gaussian.sol"; import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol"; import {ERC20} from "solmate/tokens/ERC20.sol"; +import {ToUintOverflow, ToIntOverflow} from "./RmmErrors.sol"; using FixedPointMathLib for uint256; using FixedPointMathLib for int256; @@ -14,6 +15,7 @@ struct PoolPreCompute { uint256 tau_; } + function computeLnSDivK(uint256 S, uint256 strike_) pure returns (int256) { return int256(S.divWadDown(strike_)).lnWad(); } @@ -356,21 +358,101 @@ function computeTfDReserveY(bytes memory args, uint256 rY) pure returns (int256) return result; } +function calcMaxPtIn( + uint256 reserveX_, + uint256 reserveY_, + uint256 totalLiquidity_, + uint256 strike_ + ) pure returns (uint256) { + uint256 low = 0; + uint256 high = reserveY_ - 1; + + while (low != high) { + uint256 mid = (low + high + 1) / 2; + if (calcSlope(reserveX_, reserveY_, totalLiquidity_, strike_, int256(mid)) < 0) { + high = mid - 1; + } else { + low = mid; + } + } + + return low; + } + +function calcSlope( + uint256 reserveX_, + uint256 reserveY_, + uint256 totalLiquidity_, + uint256 strike_, + int256 ptToMarket +) pure returns (int256) { + uint256 newReserveY = reserveY_ + uint256(ptToMarket); + uint256 b_i = newReserveY * 1e36 / (strike_ * totalLiquidity_); + + if (b_i > 1e18) { + return -1; + } + + int256 b = Gaussian.ppf(toInt(b_i)); + int256 pdf_b = Gaussian.pdf(b); + + int256 slope = (int256(strike_ * totalLiquidity_) * pdf_b / 1e36); + + int256 dxdy = computedXdY(reserveX_, newReserveY); + + return slope + dxdy; +} + +function calcMaxPtOut( + uint256 reserveX_, + uint256 reserveY_, + uint256 totalLiquidity_, + uint256 strike_, + uint256 sigma_, + uint256 tau_ +) pure returns (uint256) { + int256 currentTF = computeTradingFunction(reserveX_, reserveY_, totalLiquidity_, strike_, sigma_, tau_); + + uint256 maxProportion = uint256(int256(1e18) - currentTF) * 1e18 / (2 * 1e18); + + uint256 maxPtOut = reserveY_ * maxProportion / 1e18; + + return (maxPtOut * 999) / 1000; +} + + +function computedXdY( + uint256 reserveX_, + uint256 reserveY_ +) pure returns (int256) { + return -int256(reserveX_) * 1e18 / int256(reserveY_); +} + + /// @dev Casts an unsigned integer to a signed integer, reverting if `x` is too large. function toInt(uint256 x) pure returns (int256) { // Safe cast below because `type(int256).max` is positive. - require(x <= uint256(type(int256).max), "toInt: overflow"); - return int256(x); + if (x <= uint256(type(int256).max)) { + return int256(x); + } else { + revert ToIntOverflow(); + } } /// @dev Sums an unsigned integer with a signed integer, reverting if the result overflows. function sum(uint256 a, int256 b) pure returns (uint256) { if (b < 0) { - require(a >= uint256(-b), "sum: underflow"); - return a - uint256(-b); + if (a >= uint256(-b)) { + return a - uint256(-b); + } else { + revert ToUintOverflow(); + } } else { - require(a + uint256(b) >= a, "sum: overflow"); - return a + uint256(b); + if (a + uint256(b) >= a) { + return a + uint256(b); + } else { + revert ToUintOverflow(); + } } } @@ -391,7 +473,9 @@ function downscaleUp(uint256 amount, uint256 scalar_) pure returns (uint256) { /// @dev Casts a positived signed integer to an unsigned integer, reverting if `x` is negative. function toUint(int256 x) pure returns (uint256) { - require(x >= 0, "toUint: negative"); + if (x < 0) { + revert ToUintOverflow(); + } return uint256(x); } diff --git a/test/unit/ApproxSpotPrice.t.sol b/test/unit/ApproxSpotPrice.t.sol deleted file mode 100644 index dacec5a..0000000 --- a/test/unit/ApproxSpotPrice.t.sol +++ /dev/null @@ -1,20 +0,0 @@ -/// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {SetUp} from "../SetUp.sol"; - -contract ApproxSpotPriceTest is SetUp { - function test_approxSpotPrice_IncreasesOverTime() public useDefaultPool { - uint256 preSpotPrice = rmm.approxSpotPrice(syToAsset(rmm.reserveX())); - rmm.setLastTimestamp(block.timestamp + 10 days); - uint256 postSpotPrice = rmm.approxSpotPrice(syToAsset(rmm.reserveX())); - assertGt(postSpotPrice, preSpotPrice); - } - - function test_approxSpotPrice_OneAtMaturity() public useDefaultPool { - vm.warp(rmm.maturity()); - rmm.swapExactSyForPt(1 ether, 0, address(this)); - uint256 maturityPrice = rmm.approxSpotPrice(syToAsset(rmm.reserveX())); - assertEq(maturityPrice, 1 ether); - } -} diff --git a/test/unit/Init.t.sol b/test/unit/Init.t.sol index 392a593..cd4449b 100644 --- a/test/unit/Init.t.sol +++ b/test/unit/Init.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.13; import {SetUp, RMM, InitParams, DEFAULT_EXPIRY} from "../SetUp.sol"; import {Init} from "../../src/lib/RmmEvents.sol"; -import {AlreadyInitialized, InvalidStrike} from "../../src/lib/RmmErrors.sol"; +import {InvalidStrikeChange} from "../../src/lib/RmmErrors.sol"; contract InitTest is SetUp { function test_init_MintsLiquidity() @@ -20,8 +20,8 @@ contract InitTest is SetUp { rmm.init(initParams.priceX, initParams.amountX, initParams.strike); assertEq(rmm.totalLiquidity(), totalLiquidity); - assertEq(rmm.balanceOf(address(this)), totalLiquidity - rmm.BURNT_LIQUIDITY()); - assertEq(rmm.balanceOf(address(0)), rmm.BURNT_LIQUIDITY()); + assertEq(rmm.balanceOf(address(this)), totalLiquidity - 1000); + assertEq(rmm.balanceOf(address(0)), 1000); } function test_init_AdjustsPool() public withSY(address(this), 2000000 ether) withPY(address(this), 1000000 ether) { @@ -99,7 +99,7 @@ contract InitTest is SetUp { { InitParams memory initParams = getDefaultParams(); - vm.expectRevert(AlreadyInitialized.selector); + vm.expectRevert(InvalidStrikeChange.selector); rmm.init(initParams.priceX, initParams.amountX, initParams.strike); } @@ -111,7 +111,7 @@ contract InitTest is SetUp { InitParams memory initParams = getDefaultParams(); setUpRMM(initParams); - vm.expectRevert(InvalidStrike.selector); + vm.expectRevert(InvalidStrikeChange.selector); rmm.init(initParams.priceX, initParams.amountX, 1 ether); } diff --git a/test/unit/LiquidityManager.t.sol b/test/unit/LiquidityManager.t.sol index 287074b..5683dd9 100644 --- a/test/unit/LiquidityManager.t.sol +++ b/test/unit/LiquidityManager.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.13; import {Test, console2} from "forge-std/Test.sol"; -import {RMM, toInt, toUint, upscale, downscaleDown, scalar, sum, abs, PoolPreCompute} from "../../src/RMM.sol"; +import {RMM, upscale, downscaleDown, scalar, sum, abs, PoolPreCompute} from "../../src/RMM.sol"; import {LiquidityManager, RMM} from "../../src/LiquidityManager.sol"; import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; import {IPMarket} from "pendle/interfaces/IPMarket.sol"; diff --git a/test/unit/SwapExactPtForSy.t.sol b/test/unit/SwapExactPtForSy.t.sol index 1ac50aa..95eb337 100644 --- a/test/unit/SwapExactPtForSy.t.sol +++ b/test/unit/SwapExactPtForSy.t.sol @@ -5,7 +5,7 @@ import {ERC20} from "solmate/tokens/ERC20.sol"; import {InsufficientOutput} from "../../src/lib/RmmErrors.sol"; import {Swap} from "../../src/lib/RmmEvents.sol"; import {SetUp} from "../SetUp.sol"; -import {abs} from "./../../src/lib/RmmLib.sol"; +import {abs, computeSpotPrice} from "./../../src/lib/RmmLib.sol"; contract SwapExactPtForSyTest is SetUp { function test_swapExactPtForSy_TransfersTokens() public useDefaultPool { @@ -49,9 +49,9 @@ contract SwapExactPtForSyTest is SetUp { function test_swapExactPtForSy_MaintainsPrice() public useDefaultPool { uint256 amountIn = 1 ether; - uint256 prevPrice = rmm.approxSpotPrice(syToAsset(rmm.reserveX())); + uint256 prevPrice = computeSpotPrice(syToAsset(rmm.reserveX()), rmm.totalLiquidity(), rmm.strike(), rmm.sigma(), rmm.lastTau()); rmm.swapExactPtForSy(amountIn, 0, address(this)); - assertTrue(rmm.approxSpotPrice(syToAsset(rmm.reserveX())) > prevPrice, "Price did not increase after buying Y."); + assertTrue(computeSpotPrice(syToAsset(rmm.reserveX()), rmm.totalLiquidity(), rmm.strike(), rmm.sigma(), rmm.lastTau()) > prevPrice, "Price did not increase after buying Y."); } function test_swapExactPtForSy_EmitsEvent() public useDefaultPool { diff --git a/test/unit/SwapExactSyForPt.t.sol b/test/unit/SwapExactSyForPt.t.sol index 4788de3..e9b160c 100644 --- a/test/unit/SwapExactSyForPt.t.sol +++ b/test/unit/SwapExactSyForPt.t.sol @@ -5,7 +5,7 @@ import {ERC20} from "solmate/tokens/ERC20.sol"; import {SetUp} from "../SetUp.sol"; import {Swap} from "../../src/lib/RmmEvents.sol"; import {InsufficientOutput} from "../../src/lib/RmmErrors.sol"; -import {abs} from "./../../src/lib/RmmLib.sol"; +import {abs, computeSpotPrice} from "./../../src/lib/RmmLib.sol"; contract SwapExactSyForPtTest is SetUp { function test_swapExactSyForPt_TransfersTokens() public useDefaultPool { @@ -52,9 +52,9 @@ contract SwapExactSyForPtTest is SetUp { function test_swapExactSyForPt_MaintainsPrice() public useDefaultPool { uint256 amountIn = 1 ether; - uint256 prevPrice = rmm.approxSpotPrice(syToAsset(rmm.reserveX())); + uint256 prevPrice = computeSpotPrice(syToAsset(rmm.reserveX()), rmm.totalLiquidity(), rmm.strike(), rmm.sigma(), rmm.lastTau()); rmm.swapExactSyForPt(amountIn, 0, address(this)); - assertTrue(rmm.approxSpotPrice(syToAsset(rmm.reserveX())) < prevPrice, "Price did not increase after buying Y."); + assertTrue(computeSpotPrice(syToAsset(rmm.reserveX()), rmm.totalLiquidity(), rmm.strike(), rmm.sigma(), rmm.lastTau()) < prevPrice, "Price did not increase after buying Y."); } function test_swapExactSyForPt_EmitsEvent() public useDefaultPool { From bd45fa042d350a63f6d2b708b873bff7c7d3717c Mon Sep 17 00:00:00 2001 From: kinrezc Date: Mon, 15 Jul 2024 13:50:38 -0400 Subject: [PATCH 113/116] revert balanceNative impl --- src/RMM.sol | 5 ++++- src/lib/RmmErrors.sol | 3 +-- test/unit/Init.t.sol | 6 +++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/RMM.sol b/src/RMM.sol index 80a1a31..6f6de3d 100644 --- a/src/RMM.sol +++ b/src/RMM.sol @@ -6,6 +6,7 @@ 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"; @@ -89,7 +90,8 @@ contract RMM is ERC20 { lock returns (uint256 totalLiquidity_, uint256 amountY) { - if (strike_ <= 1e18 || strike_ != 0) revert InvalidStrikeChange(); + if (strike_ <= 1e18) revert InvalidStrike(); + if (strike != 0) revert AlreadyInitialized(); PYIndex index = YT.newIndex(); (totalLiquidity_, amountY) = prepareInit(priceX, amountX, strike_, sigma, index); @@ -425,6 +427,7 @@ contract RMM is ERC20 { return abi.decode(data, (uint256)); } + /// @dev Computes the trading function result using the current state. function tradingFunction(PYIndex index) public view returns (int256) { if (totalLiquidity == 0) return 0; // Not initialized. diff --git a/src/lib/RmmErrors.sol b/src/lib/RmmErrors.sol index 95f375d..b90b3c2 100644 --- a/src/lib/RmmErrors.sol +++ b/src/lib/RmmErrors.sol @@ -2,8 +2,7 @@ pragma solidity ^0.8.13; /// @dev Thrown if trying to initialize a pool with an invalid strike price (strike < 1e18). -/// @dev Thrown if trying to change the strike price of an already initialized pool. -error InvalidStrikeChange(); +error InvalidStrike(); /// @dev Thrown if trying to initialize an already initialized pool. error AlreadyInitialized(); /// @dev Thrown when a `balanceOf` call fails or returns unexpected data. diff --git a/test/unit/Init.t.sol b/test/unit/Init.t.sol index cd4449b..ace6c90 100644 --- a/test/unit/Init.t.sol +++ b/test/unit/Init.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.13; import {SetUp, RMM, InitParams, DEFAULT_EXPIRY} from "../SetUp.sol"; import {Init} from "../../src/lib/RmmEvents.sol"; -import {InvalidStrikeChange} from "../../src/lib/RmmErrors.sol"; +import {InvalidStrike, AlreadyInitialized} from "../../src/lib/RmmErrors.sol"; contract InitTest is SetUp { function test_init_MintsLiquidity() @@ -99,7 +99,7 @@ contract InitTest is SetUp { { InitParams memory initParams = getDefaultParams(); - vm.expectRevert(InvalidStrikeChange.selector); + vm.expectRevert(AlreadyInitialized.selector); rmm.init(initParams.priceX, initParams.amountX, initParams.strike); } @@ -111,7 +111,7 @@ contract InitTest is SetUp { InitParams memory initParams = getDefaultParams(); setUpRMM(initParams); - vm.expectRevert(InvalidStrikeChange.selector); + vm.expectRevert(InvalidStrike.selector); rmm.init(initParams.priceX, initParams.amountX, 1 ether); } From 9e6b556fc45d98ea5a415d87c9fec291040adec0 Mon Sep 17 00:00:00 2001 From: kinrezc Date: Mon, 15 Jul 2024 13:55:19 -0400 Subject: [PATCH 114/116] rm unused currentTau fn --- src/RMM.sol | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/RMM.sol b/src/RMM.sol index 6f6de3d..712d5a8 100644 --- a/src/RMM.sol +++ b/src/RMM.sol @@ -624,15 +624,6 @@ contract RMM is ERC20 { return computeTauWadYears(maturity - lastTimestamp); } - /// @dev Computes the time to maturity based on the current `block.timestamp` and converts it to units of WAD years. - function currentTau() public view returns (uint256) { - if (maturity < block.timestamp) { - return 0; - } - - return computeTauWadYears(maturity - block.timestamp); - } - function futureTau(uint256 timestamp) public view returns (uint256) { if (maturity < timestamp) { return 0; From a41bbac4f50eec53e3dd71087da40e7d313bbe4b Mon Sep 17 00:00:00 2001 From: kinrezc Date: Mon, 15 Jul 2024 14:12:57 -0400 Subject: [PATCH 115/116] 19 bytes off --- src/RMM.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/RMM.sol b/src/RMM.sol index 712d5a8..d95794b 100644 --- a/src/RMM.sol +++ b/src/RMM.sol @@ -26,9 +26,6 @@ contract RMM is ERC20 { IStandardizedYield public SY; IPYieldToken public YT; - uint256 public SY_scalar; - uint256 public PT_scalar; - uint256 public sigma; uint256 public fee; uint256 public maturity; @@ -41,6 +38,9 @@ contract RMM is ERC20 { uint256 public lastTimestamp; uint256 public lastImpliedPrice; + uint256 private SY_scalar; + uint256 private PT_scalar; + uint256 private _lock = 1; modifier lock() { @@ -124,7 +124,7 @@ contract RMM is ERC20 { uint256 upperBound, uint256 epsilon, address to - ) public lock returns (uint256 amountOut, int256 deltaLiquidity) { + ) external lock returns (uint256 amountOut, int256 deltaLiquidity) { PYIndex index = YT.newIndex(); uint256 amountInWad; uint256 amountOutWad; From 52aba25a83e275bc3347042a1e25a3d2d105951e Mon Sep 17 00:00:00 2001 From: kinrezc Date: Mon, 15 Jul 2024 14:16:23 -0400 Subject: [PATCH 116/116] remove custom error selector for allocate --- src/RMM.sol | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/RMM.sol b/src/RMM.sol index d95794b..83012f5 100644 --- a/src/RMM.sol +++ b/src/RMM.sol @@ -90,8 +90,7 @@ contract RMM is ERC20 { lock returns (uint256 totalLiquidity_, uint256 amountY) { - if (strike_ <= 1e18) revert InvalidStrike(); - if (strike != 0) revert AlreadyInitialized(); + if (strike_ <= 1e18 || strike != 0) revert InvalidStrike(); PYIndex index = YT.newIndex(); (totalLiquidity_, amountY) = prepareInit(priceX, amountX, strike_, sigma, index); @@ -323,7 +322,7 @@ contract RMM is ERC20 { (uint256 deltaXWad, uint256 deltaYWad, uint256 deltaLiquidity, uint256 lptMinted) = prepareAllocate(inTermsOfX, amount); if (deltaLiquidity < minLiquidityOut) { - revert InsufficientLiquidityOut(inTermsOfX, amount, minLiquidityOut, deltaLiquidity); + revert InsufficientOutput(amount, minLiquidityOut, deltaLiquidity); } _mint(to, lptMinted);