From c24fced8e92f8e5223ad7cb93319997d88366a17 Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Tue, 11 Jun 2024 11:37:24 -0400 Subject: [PATCH 1/5] deployment params --- foundry.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/foundry.toml b/foundry.toml index b26d057..198b419 100644 --- a/foundry.toml +++ b/foundry.toml @@ -13,4 +13,4 @@ mainnet = "${MAINNET_RPC_URL}" op_sepolia = "${OP_SEPOLIA_RPC_URL}" testnet = "${TESTNET_RPC_URL}" -unknown_chain = { key = "${TENDERLY_ACCESS_KEY}", chain = 753712, url = "${TESTNET_RPC_URL}" } +unknown_chain = { key = "", chain = 753712, url = "https://virtual.mainnet.rpc.tenderly.co/ab7a4b34-a4da-4803-8130-01cf2230dbe6" } From 7da435f8d5d2884beaed21343bbdbc8abffab5c6 Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Tue, 11 Jun 2024 13:54:25 -0400 Subject: [PATCH 2/5] bisect over pt flash swap in sy -> yt flow --- src/RMM.sol | 49 ++++++++++++++++++++++++------------------- test/unit/RmmSy.t.sol | 38 +++++++++++++++++++++------------ 2 files changed, 52 insertions(+), 35 deletions(-) diff --git a/src/RMM.sol b/src/RMM.sol index 57a5f4f..d436ef9 100644 --- a/src/RMM.sol +++ b/src/RMM.sol @@ -125,8 +125,8 @@ 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 - function swapExactSyForYt(uint256 amountIn, uint256 minAmountOut, address to) - public + function swapExactSyForYt(uint256 amountIn, uint256 minAmountOut, uint256 epsilon, address to) + public lock returns (uint256 amountOut, int256 deltaLiquidity) { @@ -135,8 +135,10 @@ contract RMM is ERC20 { uint256 amountOutWad; uint256 strike_; + uint256 bestAmountIn = computeSYToYT(index, amountIn, block.timestamp, amountIn, epsilon); + (amountInWad, amountOutWad, amountOut, deltaLiquidity, strike_) = - prepareSwapPtIn(amountIn, block.timestamp, index); + prepareSwapPtIn(bestAmountIn, block.timestamp, index); _adjust(-toInt(amountOutWad), toInt(amountInWad), deltaLiquidity, strike_, index); @@ -145,7 +147,6 @@ contract RMM is ERC20 { uint256 ytOut = amountOut + delta; (uint256 debitNative) = _debit(address(SY), delta); - amountOut = mintPtYt(ytOut, address(this)); if (amountOut < minAmountOut) { @@ -157,12 +158,14 @@ contract RMM is ERC20 { emit Swap(msg.sender, to, address(SY), address(YT), debitNative, amountOut, deltaLiquidity); } - function swapExactTokenForYt(address token, uint256 amountTokenIn, uint256 amountPtForFlashSwap, uint256 minSyMinted, uint256 minYtOut, address to) - external - payable - lock - returns (uint256 amountOut, int256 deltaLiquidity) - { + function swapExactTokenForYt( + address token, + uint256 amountTokenIn, + uint256 amountPtForFlashSwap, + uint256 minSyMinted, + uint256 minYtOut, + address to + ) external payable lock returns (uint256 amountOut, int256 deltaLiquidity) { // initialize to msg.value uint256 debitNative = msg.value; uint256 amountSyMinted; @@ -174,7 +177,7 @@ contract RMM is ERC20 { if (msg.value > 0 && SY.isValidTokenIn(address(0))) { amountSyMinted += SY.deposit{value: msg.value}(address(this), address(0), msg.value, 0); - } + } if (token != address(0)) { ERC20(token).transferFrom(msg.sender, address(this), amountTokenIn); @@ -195,7 +198,6 @@ contract RMM is ERC20 { (amountInWad, amountOutWad, amountOut, deltaLiquidity, strike_) = prepareSwapPtIn(amountPtForFlashSwap, block.timestamp, index); - _adjust(-toInt(amountOutWad), toInt(amountInWad), deltaLiquidity, strike_, index); // SY is needed to cover the minted PT, so we need to debit the delta from the msg.sender @@ -436,33 +438,36 @@ contract RMM is ERC20 { return uint256(lastPrice).divWadDown(uint256(exp)); } - function computeTokenToYt(PYIndex index, address token, uint256 exactTokenIn, uint256 blockTime, uint256 initialGuess) - public - view - returns (uint256 amountSyMinted, uint256 amountYtOut) - { + function computeTokenToYT( + PYIndex index, + address token, + uint256 exactTokenIn, + uint256 blockTime, + uint256 initialGuess, + uint256 epsilon + ) public view returns (uint256 amountSyMinted, uint256 amountYtOut) { if (!SY.isValidTokenIn(token)) { revert InvalidTokenIn(token); } amountSyMinted = SY.previewDeposit(token, exactTokenIn); - amountYtOut = computeSYToYT(index, amountSyMinted, blockTime, initialGuess); + amountYtOut = computeSYToYT(index, amountSyMinted, blockTime, initialGuess, epsilon); } - function computeSYToYT(PYIndex index, uint256 exactSYIn, uint256 blockTime, uint256 initialGuess) + function computeSYToYT(PYIndex index, uint256 exactSYIn, uint256 blockTime, uint256 initialGuess, uint256 epsilon) public view - returns (uint256) + returns (uint256 guess) { uint256 min = exactSYIn; uint256 max = initialGuess; for (uint256 iter = 0; iter < 100; ++iter) { - uint256 guess = (min + max) / 2; + guess = (min + max) / 2; (,, uint256 amountOut,,) = prepareSwapPtIn(guess, blockTime, index); uint256 netSyToPt = index.assetToSyUp(guess); uint256 netSyToPull = netSyToPt - amountOut; if (netSyToPull <= exactSYIn) { - if (isASmallerApproxB(netSyToPull, exactSYIn, 10_000)) { + if (isASmallerApproxB(netSyToPull, exactSYIn, epsilon)) { return guess; } min = guess; diff --git a/test/unit/RmmSy.t.sol b/test/unit/RmmSy.t.sol index 7a6c321..4a730c7 100644 --- a/test/unit/RmmSy.t.sol +++ b/test/unit/RmmSy.t.sol @@ -33,6 +33,8 @@ 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); @@ -259,11 +261,11 @@ contract ForkRMMTest is Test { uint256 rPT = subject().reserveX(); uint256 rSY = subject().reserveY(); console2.log("SY balance before", SY.balanceOf(address(this))); - uint256 ytOut = subject().computeSYToYT(index, 1 ether, block.timestamp, 500 ether); + uint256 ytOut = subject().computeSYToYT(index, 1 ether, block.timestamp, 500 ether, 0.005 ether); console2.log("ytOut", ytOut); console2.log("rPT", rPT); console2.log("rSY", rSY); - (uint256 amtOut,) = subject().swapExactSyForYt(ytOut, ytOut.mulDivDown(95, 100), address(this)); + (uint256 amtOut,) = subject().swapExactSyForYt(ytOut, ytOut.mulDivDown(95, 100), eps, address(this)); console2.log("amtOut", amtOut); console2.log("SY balance after", SY.balanceOf(address(this))); console2.log("YT balance after", YT.balanceOf(address(this))); @@ -286,14 +288,20 @@ contract ForkRMMTest is Test { // mint 1 SY for the flash swap mintSY(1 ether); - uint256 ytOut = subject().computeSYToYT(index, 1 ether, block.timestamp, 500 ether); - (uint256 amtOut,) = subject().swapExactSyForYt(ytOut, 0, address(this)); + uint256 ytOut = subject().computeSYToYT(index, 1 ether, block.timestamp, 500 ether, 10_000); + (uint256 amtOut,) = subject().swapExactSyForYt(ytOut, 0, 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."); + 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 { @@ -315,7 +323,7 @@ contract ForkRMMTest is Test { uint256 rSY = subject().reserveY(); vm.warp(block.timestamp + 30 days); uint256 k = getRmmStrikePrice(); - uint256 ytOut = subject().computeSYToYT(index, 1 ether, block.timestamp, 500 ether); + uint256 ytOut = subject().computeSYToYT(index, 1 ether, block.timestamp, 500 ether, eps); console2.log("k", k); console2.log("ytOut", ytOut); console2.log("rPT", rPT); @@ -385,7 +393,6 @@ contract ForkRMMTest is Test { 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)); @@ -405,9 +412,12 @@ contract ForkRMMTest is Test { uint256 amountIn = 1 ether; PYIndex index = YT.newIndex(); - (uint256 syMinted, uint256 ytOut) = subject().computeTokenToYt(index, address(0), amountIn, block.timestamp, 500 ether); + (uint256 syMinted, uint256 ytOut) = + subject().computeTokenToYT(index, address(0), amountIn, block.timestamp, 500 ether, eps); subject().swapExactTokenForYt{value: amountIn}(address(0), 0, ytOut, syMinted, ytOut, address(this)); - assertApproxEqAbs(YT.balanceOf(address(this)), ytOut, 1_000, "YT balance of address(this) is not equal to ytOut."); + 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 { @@ -422,14 +432,16 @@ contract ForkRMMTest is Test { uint256 amountIn = 1 ether; PYIndex index = YT.newIndex(); - (uint256 syMinted, uint256 ytOut) = subject().computeTokenToYt(index, address(subject().WETH()), amountIn, block.timestamp, 500 ether); + (uint256 syMinted, uint256 ytOut) = + subject().computeTokenToYT(index, address(subject().WETH()), amountIn, block.timestamp, 500 ether, eps); deal(subject().WETH(), address(this), amountIn); IERC20(subject().WETH()).approve(address(subject()), amountIn); subject().swapExactTokenForYt(address(subject().WETH()), amountIn, ytOut, syMinted, ytOut, address(this)); - assertApproxEqAbs(YT.balanceOf(address(this)), ytOut, 1_000, "YT balance of address(this) is not equal to ytOut."); + 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)); From 837f85cec64f2e4da039c57ff9459ca84a15b785 Mon Sep 17 00:00:00 2001 From: Kinrezc Date: Tue, 11 Jun 2024 16:04:38 -0400 Subject: [PATCH 3/5] add fallbacks for token/sy -> yt swaps --- src/RMM.sol | 58 +++++++++++++++++++++++++++++-------------- test/unit/RmmSy.t.sol | 45 +++++++++++++++++---------------- 2 files changed, 64 insertions(+), 39 deletions(-) diff --git a/src/RMM.sol b/src/RMM.sol index d436ef9..3c18702 100644 --- a/src/RMM.sol +++ b/src/RMM.sol @@ -8,6 +8,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"; @@ -125,26 +126,34 @@ 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 - function swapExactSyForYt(uint256 amountIn, uint256 minAmountOut, uint256 epsilon, address to) - public - lock - returns (uint256 amountOut, int256 deltaLiquidity) - { + function swapExactSyForYt( + uint256 maxSyIn, + uint256 amountPtToFlash, + uint256 minAmountOut, + uint256 upperBound, + uint256 epsilon, + address to + ) public lock returns (uint256 amountOut, int256 deltaLiquidity) { PYIndex index = YT.newIndex(); uint256 amountInWad; uint256 amountOutWad; uint256 strike_; - uint256 bestAmountIn = computeSYToYT(index, amountIn, block.timestamp, amountIn, epsilon); + amountPtToFlash = computeSYToYT(index, maxSyIn, upperBound, block.timestamp, amountPtToFlash, epsilon); (amountInWad, amountOutWad, amountOut, deltaLiquidity, strike_) = - prepareSwapPtIn(bestAmountIn, block.timestamp, index); + prepareSwapPtIn(amountPtToFlash, block.timestamp, index); _adjust(-toInt(amountOutWad), toInt(amountInWad), deltaLiquidity, strike_, index); // SY is needed to cover the minted PT, so we need to debit the delta from the msg.sender uint256 delta = index.assetToSyUp(amountInWad) - amountOutWad; uint256 ytOut = amountOut + delta; + + if (delta > maxSyIn) { + revert ExcessInput(maxSyIn, maxSyIn, delta); + } + (uint256 debitNative) = _debit(address(SY), delta); amountOut = mintPtYt(ytOut, address(this)); @@ -164,6 +173,8 @@ contract RMM is ERC20 { uint256 amountPtForFlashSwap, uint256 minSyMinted, uint256 minYtOut, + uint256 upperBound, + uint256 epsilon, address to ) external payable lock returns (uint256 amountOut, int256 deltaLiquidity) { // initialize to msg.value @@ -176,13 +187,13 @@ contract RMM is ERC20 { } if (msg.value > 0 && SY.isValidTokenIn(address(0))) { - amountSyMinted += SY.deposit{value: msg.value}(address(this), address(0), msg.value, 0); + amountSyMinted += SY.deposit{value: msg.value}(address(this), address(0), msg.value, minSyMinted); } if (token != address(0)) { ERC20(token).transferFrom(msg.sender, address(this), amountTokenIn); ERC20(token).approve(address(SY), amountTokenIn); - amountSyMinted += SY.deposit(address(this), token, amountTokenIn, 0); + amountSyMinted += SY.deposit(address(this), token, amountTokenIn, minSyMinted); debitNative += amountTokenIn; } @@ -195,6 +206,9 @@ contract RMM is ERC20 { uint256 amountOutWad; uint256 strike_; + amountPtForFlashSwap = + computeSYToYT(index, amountSyMinted, upperBound, block.timestamp, amountPtForFlashSwap, epsilon); + (amountInWad, amountOutWad, amountOut, deltaLiquidity, strike_) = prepareSwapPtIn(amountPtForFlashSwap, block.timestamp, index); @@ -442,6 +456,7 @@ contract RMM is ERC20 { PYIndex index, address token, uint256 exactTokenIn, + uint256 max, uint256 blockTime, uint256 initialGuess, uint256 epsilon @@ -450,23 +465,30 @@ contract RMM is ERC20 { revert InvalidTokenIn(token); } amountSyMinted = SY.previewDeposit(token, exactTokenIn); - amountYtOut = computeSYToYT(index, amountSyMinted, blockTime, initialGuess, epsilon); + amountYtOut = computeSYToYT(index, amountSyMinted, max, blockTime, initialGuess, epsilon); } - function computeSYToYT(PYIndex index, uint256 exactSYIn, uint256 blockTime, uint256 initialGuess, uint256 epsilon) - public - view - returns (uint256 guess) - { + function computeSYToYT( + PYIndex index, + uint256 exactSYIn, + uint256 max, + uint256 blockTime, + uint256 initialGuess, + uint256 epsilon + ) public view returns (uint256 guess) { uint256 min = exactSYIn; - uint256 max = initialGuess; - for (uint256 iter = 0; iter < 100; ++iter) { - guess = (min + max) / 2; + for (uint256 iter = 0; iter < 256; ++iter) { + console2.log("iter", iter); + guess = initialGuess > 0 && iter == 0 ? initialGuess : (min + max) / 2; + console2.log("guess", guess); (,, uint256 amountOut,,) = prepareSwapPtIn(guess, blockTime, index); + console2.log("amountOut", amountOut); uint256 netSyToPt = index.assetToSyUp(guess); uint256 netSyToPull = netSyToPt - amountOut; + console2.log(isASmallerApproxB(netSyToPull, exactSYIn, epsilon)); if (netSyToPull <= exactSYIn) { + console2.log(isASmallerApproxB(netSyToPull, exactSYIn, epsilon)); if (isASmallerApproxB(netSyToPull, exactSYIn, epsilon)) { return guess; } diff --git a/test/unit/RmmSy.t.sol b/test/unit/RmmSy.t.sol index 4a730c7..91407a5 100644 --- a/test/unit/RmmSy.t.sol +++ b/test/unit/RmmSy.t.sol @@ -256,21 +256,13 @@ contract ForkRMMTest is Test { PT.transfer(address(0x55), PT.balanceOf(address(this))); YT.transfer(address(0x55), YT.balanceOf(address(this))); mintSY(1 ether); - uint256 stkBefore = subject().strike(); PYIndex index = YT.newIndex(); uint256 rPT = subject().reserveX(); - uint256 rSY = subject().reserveY(); - console2.log("SY balance before", SY.balanceOf(address(this))); - uint256 ytOut = subject().computeSYToYT(index, 1 ether, block.timestamp, 500 ether, 0.005 ether); - console2.log("ytOut", ytOut); - console2.log("rPT", rPT); - console2.log("rSY", rSY); - (uint256 amtOut,) = subject().swapExactSyForYt(ytOut, ytOut.mulDivDown(95, 100), eps, address(this)); - console2.log("amtOut", amtOut); - console2.log("SY balance after", SY.balanceOf(address(this))); - console2.log("YT balance after", YT.balanceOf(address(this))); - console2.log("stk before", stkBefore); - console2.log("stk after", subject().strike()); + 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)); } function test_pt_flash_swap_adjusts_balances_correctly() public basic_sy { @@ -288,8 +280,8 @@ contract ForkRMMTest is Test { // mint 1 SY for the flash swap mintSY(1 ether); - uint256 ytOut = subject().computeSYToYT(index, 1 ether, block.timestamp, 500 ether, 10_000); - (uint256 amtOut,) = subject().swapExactSyForYt(ytOut, 0, eps, address(this)); + 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."); @@ -305,7 +297,7 @@ contract ForkRMMTest is Test { } function test_approx_sy_pendle() public basic_sy { - (MarketState memory ms, MarketPreCompute memory mp) = getPendleMarketData(); + (MarketState memory ms,) = getPendleMarketData(); console2.log("market sy", ms.totalSy); console2.log("market pt", ms.totalPt); vm.warp(block.timestamp + 30 days); @@ -323,7 +315,7 @@ contract ForkRMMTest is Test { uint256 rSY = subject().reserveY(); vm.warp(block.timestamp + 30 days); uint256 k = getRmmStrikePrice(); - uint256 ytOut = subject().computeSYToYT(index, 1 ether, block.timestamp, 500 ether, eps); + 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); @@ -413,8 +405,10 @@ contract ForkRMMTest is Test { uint256 amountIn = 1 ether; PYIndex index = YT.newIndex(); (uint256 syMinted, uint256 ytOut) = - subject().computeTokenToYT(index, address(0), amountIn, block.timestamp, 500 ether, eps); - subject().swapExactTokenForYt{value: amountIn}(address(0), 0, ytOut, syMinted, ytOut, address(this)); + 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." ); @@ -433,10 +427,19 @@ contract ForkRMMTest is Test { uint256 amountIn = 1 ether; PYIndex index = YT.newIndex(); (uint256 syMinted, uint256 ytOut) = - subject().computeTokenToYT(index, address(subject().WETH()), amountIn, block.timestamp, 500 ether, eps); + 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, address(this)); + 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." ); From 3292810c65cac5dc3d7933282be3f51ba15cf77f Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 11 Jun 2024 15:01:28 -0700 Subject: [PATCH 4/5] defeats stack too deep and spurious dragon in 1 hit --- src/RMM.sol | 122 +++++++++++++++++++++++++++++----------------------- 1 file changed, 69 insertions(+), 53 deletions(-) diff --git a/src/RMM.sol b/src/RMM.sol index 3c18702..65fb2d3 100644 --- a/src/RMM.sol +++ b/src/RMM.sol @@ -8,7 +8,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"; @@ -68,10 +67,6 @@ contract RMM is ERC20 { WETH = weth_; } - function version() public pure returns (string memory) { - return "0.1.1-rc0"; - } - receive() external payable {} /// @dev Initializes the pool with an initial price, amount of `x` tokens, and parameters. @@ -90,6 +85,16 @@ contract RMM is ERC20 { SY = IStandardizedYield(PT.SY()); YT = IPYieldToken(PT.YT()); + // Sets approvals ahead of time for this contract to handle routing. + { + // curly braces scope avoids stack too deep + address[] memory tokensIn = SY.getTokensIn(); + for (uint256 i; i < tokensIn.length; ++i) { + ERC20 token = ERC20(tokensIn[i]); + token.approve(address(SY), type(uint256).max); + } + } + PYIndex index = YT.newIndex(); uint256 totalAsset = index.syToAsset(amountX); @@ -167,6 +172,18 @@ contract RMM is ERC20 { emit Swap(msg.sender, to, address(SY), address(YT), debitNative, amountOut, deltaLiquidity); } + struct SwapToYt { + address tokenIn; + uint256 amountTokenIn; + uint256 amountNativeIn; + uint256 amountPtIn; + uint256 minSyMinted; + uint256 realSyMinted; + uint256 minYtOut; + uint256 realYtOut; + address to; + } + function swapExactTokenForYt( address token, uint256 amountTokenIn, @@ -177,62 +194,55 @@ contract RMM is ERC20 { uint256 epsilon, address to ) external payable lock returns (uint256 amountOut, int256 deltaLiquidity) { - // initialize to msg.value - uint256 debitNative = msg.value; - uint256 amountSyMinted; - - // Convert the eth to SY via minting. - if (!SY.isValidTokenIn(token)) { - revert InvalidTokenIn(token); - } - - if (msg.value > 0 && SY.isValidTokenIn(address(0))) { - amountSyMinted += SY.deposit{value: msg.value}(address(this), address(0), msg.value, minSyMinted); - } - - if (token != address(0)) { - ERC20(token).transferFrom(msg.sender, address(this), amountTokenIn); - ERC20(token).approve(address(SY), amountTokenIn); - amountSyMinted += SY.deposit(address(this), token, amountTokenIn, minSyMinted); - debitNative += amountTokenIn; - } - - if (amountSyMinted < minSyMinted) { - revert InsufficientSYMinted(amountSyMinted, minSyMinted); - } + SwapToYt memory swap; + swap.tokenIn = token; + swap.amountTokenIn = token == address(0) ? 0 : amountTokenIn; + swap.amountNativeIn = msg.value; + swap.amountPtIn = amountPtForFlashSwap; + swap.minSyMinted = minSyMinted; + swap.minYtOut = minYtOut; + swap.to = to; + swap.realSyMinted = _mintSYFromNativeAndToken(address(this), swap.tokenIn, swap.amountTokenIn, swap.minSyMinted); PYIndex index = YT.newIndex(); uint256 amountInWad; uint256 amountOutWad; uint256 strike_; - amountPtForFlashSwap = - computeSYToYT(index, amountSyMinted, upperBound, block.timestamp, amountPtForFlashSwap, epsilon); + swap.amountPtIn = computeSYToYT(index, swap.realSyMinted, upperBound, block.timestamp, swap.amountPtIn, epsilon); (amountInWad, amountOutWad, amountOut, deltaLiquidity, strike_) = - prepareSwapPtIn(amountPtForFlashSwap, block.timestamp, index); + prepareSwapPtIn(swap.amountPtIn, block.timestamp, index); _adjust(-toInt(amountOutWad), toInt(amountInWad), deltaLiquidity, strike_, index); // SY is needed to cover the minted PT, so we need to debit the delta from the msg.sender - uint256 ytOut = amountOut + (index.assetToSyUp(amountInWad) - amountOutWad); + swap.realYtOut = amountOut + (index.assetToSyUp(amountInWad) - amountOutWad); // Converts the SY received from minting it into its components PT and YT. - amountOut = mintPtYt(ytOut, address(this)); + amountOut = mintPtYt(swap.realYtOut, address(this)); + swap.realYtOut = amountOut; - if (amountOut < minYtOut) { - revert InsufficientOutput(amountInWad, minYtOut, amountOut); + if (swap.realYtOut < swap.minYtOut) { + revert InsufficientOutput(amountInWad, swap.minYtOut, swap.realYtOut); } - _credit(address(YT), to, amountOut); + _credit(address(YT), to, swap.realYtOut); uint256 debitSurplus = address(this).balance; - if (debitSurplus > 0) { - _sendETH(to, debitSurplus); + _sendETH(swap.to, debitSurplus); } - emit Swap(msg.sender, to, address(SY), address(YT), debitNative - debitSurplus, amountOut, deltaLiquidity); + emit Swap( + msg.sender, + swap.to, + address(SY), + address(YT), + swap.amountTokenIn + swap.amountNativeIn - debitSurplus, + swap.realYtOut, + deltaLiquidity + ); } function swapExactPtForSy(uint256 amountIn, uint256 minAmountOut, address to) @@ -478,17 +488,12 @@ contract RMM is ERC20 { ) public view returns (uint256 guess) { uint256 min = exactSYIn; for (uint256 iter = 0; iter < 256; ++iter) { - console2.log("iter", iter); guess = initialGuess > 0 && iter == 0 ? initialGuess : (min + max) / 2; - console2.log("guess", guess); (,, uint256 amountOut,,) = prepareSwapPtIn(guess, blockTime, index); - console2.log("amountOut", amountOut); uint256 netSyToPt = index.assetToSyUp(guess); uint256 netSyToPull = netSyToPt - amountOut; - console2.log(isASmallerApproxB(netSyToPull, exactSYIn, epsilon)); if (netSyToPull <= exactSYIn) { - console2.log(isASmallerApproxB(netSyToPull, exactSYIn, epsilon)); if (isASmallerApproxB(netSyToPull, exactSYIn, epsilon)) { return guess; } @@ -664,16 +669,27 @@ contract RMM is ERC20 { payable returns (uint256 amountOut) { - if (!SY.isValidTokenIn(tokenIn)) { - revert InvalidTokenIn(tokenIn); + return _mintSYFromNativeAndToken(receiver, tokenIn, amountTokenToDeposit, minSharesOut); + } + + function _mintSYFromNativeAndToken(address receiver, address tokenIn, uint256 amountTokenIn, uint256 minSyMinted) + internal + returns (uint256 amountSyOut) + { + if (!SY.isValidTokenIn(tokenIn)) revert InvalidTokenIn(tokenIn); + + if (msg.value > 0 && SY.isValidTokenIn(address(0))) { + // SY minted check is done in this function instead of relying on the SY contract's deposit(). + amountSyOut += SY.deposit{value: msg.value}(address(this), address(0), msg.value, 0); + } + + if (tokenIn != address(0)) { + ERC20(tokenIn).transferFrom(msg.sender, address(this), amountTokenIn); + amountSyOut += SY.deposit(receiver, tokenIn, amountTokenIn, 0); } - if (tokenIn == address(0)) { - amountOut = - SY.deposit{value: amountTokenToDeposit}(receiver, address(0), amountTokenToDeposit, minSharesOut); - } else { - ERC20(tokenIn).transferFrom(msg.sender, address(this), amountTokenToDeposit); - ERC20(tokenIn).approve(address(SY), amountTokenToDeposit); - amountOut = SY.deposit(receiver, tokenIn, amountTokenToDeposit, minSharesOut); + + if (amountSyOut < minSyMinted) { + revert InsufficientSYMinted(amountSyOut, minSyMinted); } } From 6670583e335d0d632d7c25956b9387f92cd4c0bc Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 12 Jun 2024 16:03:49 +0400 Subject: [PATCH 5/5] fix: init failing because of zero address in getTokensIn --- src/RMM.sol | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/RMM.sol b/src/RMM.sol index 65fb2d3..e85c48f 100644 --- a/src/RMM.sol +++ b/src/RMM.sol @@ -89,9 +89,11 @@ contract RMM is ERC20 { { // curly braces scope avoids stack too deep address[] memory tokensIn = SY.getTokensIn(); - for (uint256 i; i < tokensIn.length; ++i) { + uint256 length = tokensIn.length; + + for (uint256 i; i < length; ++i) { ERC20 token = ERC20(tokensIn[i]); - token.approve(address(SY), type(uint256).max); + if (address(token) != address(0)) token.approve(address(SY), type(uint256).max); } }